mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1653 Add functionality to new URI button (#862)
This commit is contained in:
parent
b1cc9a1dd6
commit
d2ffd7bf01
10 changed files with 119 additions and 43 deletions
|
@ -153,12 +153,14 @@ fun LazyListScope.vaultAddEditLoginItems(
|
|||
)
|
||||
}
|
||||
|
||||
item {
|
||||
items(loginState.uriList) { uriItem ->
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextFieldWithActions(
|
||||
label = stringResource(id = R.string.uri),
|
||||
value = loginState.uri,
|
||||
onValueChange = loginItemTypeHandlers.onUriTextChange,
|
||||
value = uriItem.uri.orEmpty(),
|
||||
onValueChange = {
|
||||
loginItemTypeHandlers.onUriTextChange(uriItem.copy(uri = it))
|
||||
},
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
|||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
||||
|
@ -43,6 +44,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Collections
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
@ -545,7 +547,17 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
action: VaultAddEditAction.ItemType.LoginType.UriTextChange,
|
||||
) {
|
||||
updateLoginContent { loginType ->
|
||||
loginType.copy(uri = action.uri)
|
||||
loginType.copy(
|
||||
uriList = loginType
|
||||
.uriList
|
||||
.map { uriItem ->
|
||||
if (uriItem.id == action.uri.id) {
|
||||
action.uri
|
||||
} else {
|
||||
uriItem
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -603,10 +615,12 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleLoginAddNewUriClick() {
|
||||
viewModelScope.launch {
|
||||
sendEvent(
|
||||
event = VaultAddEditEvent.ShowToast(
|
||||
message = "Add New URI".asText(),
|
||||
updateLoginContent { loginType ->
|
||||
loginType.copy(
|
||||
uriList = loginType.uriList + UriItem(
|
||||
id = UUID.randomUUID().toString(),
|
||||
uri = "",
|
||||
match = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1360,7 +1374,7 @@ data class VaultAddEditState(
|
|||
*
|
||||
* @property username The username required for the login item.
|
||||
* @property password The password required for the login item.
|
||||
* @property uri The URI associated with the login item.
|
||||
* @property uriList The list of URIs associated with the login item.
|
||||
* @property totp The current TOTP (if applicable).
|
||||
* @property canViewPassword Indicates whether the current user can view and copy
|
||||
* passwords associated with the login item.
|
||||
|
@ -1369,9 +1383,11 @@ data class VaultAddEditState(
|
|||
data class Login(
|
||||
val username: String = "",
|
||||
val password: String = "",
|
||||
val uri: String = "",
|
||||
val totp: String? = null,
|
||||
val canViewPassword: Boolean = true,
|
||||
val uriList: List<UriItem> = listOf(
|
||||
UriItem(id = UUID.randomUUID().toString(), uri = "", match = null),
|
||||
),
|
||||
) : ItemType() {
|
||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.LOGIN
|
||||
}
|
||||
|
@ -1744,7 +1760,7 @@ sealed class VaultAddEditAction {
|
|||
*
|
||||
* @property uri The new URI text.
|
||||
*/
|
||||
data class UriTextChange(val uri: String) : LoginType()
|
||||
data class UriTextChange(val uri: UriItem) : LoginType()
|
||||
|
||||
/**
|
||||
* Represents the action to set up TOTP.
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit.handlers
|
|||
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
|
||||
/**
|
||||
* A collection of handler functions specifically tailored for managing actions
|
||||
|
@ -27,7 +28,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
|
|||
data class VaultAddEditLoginTypeHandlers(
|
||||
val onUsernameTextChange: (String) -> Unit,
|
||||
val onPasswordTextChange: (String) -> Unit,
|
||||
val onUriTextChange: (String) -> Unit,
|
||||
val onUriTextChange: (UriItem) -> Unit,
|
||||
val onOpenUsernameGeneratorClick: () -> Unit,
|
||||
val onPasswordCheckerClick: () -> Unit,
|
||||
val onOpenPasswordGeneratorClick: () -> Unit,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Represents the URI item being displayed to the user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class UriItem(
|
||||
val id: String,
|
||||
val uri: String?,
|
||||
val match: UriMatchType?,
|
||||
) : Parcelable
|
|
@ -5,10 +5,12 @@ import com.bitwarden.core.CipherType
|
|||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
|
@ -29,7 +31,7 @@ fun CipherView.toViewState(
|
|||
VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = login?.username.orEmpty(),
|
||||
password = login?.password.orEmpty(),
|
||||
uri = login?.uris?.firstOrNull()?.uri.orEmpty(),
|
||||
uriList = login?.uris.toUriItems(),
|
||||
totp = login?.totp,
|
||||
canViewPassword = this.viewPassword,
|
||||
)
|
||||
|
@ -138,3 +140,22 @@ private fun String.appendCloneTextIfRequired(
|
|||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
private fun List<LoginUriView>?.toUriItems(): List<UriItem> =
|
||||
if (this.isNullOrEmpty()) {
|
||||
listOf(
|
||||
UriItem(
|
||||
id = UUID.randomUUID().toString(),
|
||||
uri = "",
|
||||
match = null,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
this.map { loginUriView ->
|
||||
UriItem(
|
||||
id = UUID.randomUUID().toString(),
|
||||
uri = loginUriView.uri,
|
||||
match = loginUriView.match,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ import com.bitwarden.core.LoginUriView
|
|||
import com.bitwarden.core.LoginView
|
||||
import com.bitwarden.core.SecureNoteType
|
||||
import com.bitwarden.core.SecureNoteView
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
|
@ -124,14 +124,7 @@ private fun VaultAddEditState.ViewState.Content.ItemType.toLoginView(
|
|||
username = it.username,
|
||||
password = it.password,
|
||||
passwordRevisionDate = common.originalCipher?.login?.passwordRevisionDate,
|
||||
uris = listOf(
|
||||
// TODO Implement URI list (BIT-1094)
|
||||
LoginUriView(
|
||||
uri = it.uri,
|
||||
// TODO Implement URI settings in (BIT-1094)
|
||||
match = UriMatchType.DOMAIN,
|
||||
),
|
||||
),
|
||||
uris = it.uriList.toLoginUriView(),
|
||||
totp = it.totp,
|
||||
autofillOnPageLoad = common.originalCipher?.login?.autofillOnPageLoad,
|
||||
)
|
||||
|
@ -190,3 +183,9 @@ private fun VaultAddEditState.Custom.toFieldView(): FieldView =
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<UriItem>?.toLoginUriView(): List<LoginUriView>? =
|
||||
this
|
||||
?.filter { it.uri?.isNotBlank() == true }
|
||||
?.map { LoginUriView(uri = it.uri.orEmpty(), match = it.match) }
|
||||
.takeUnless { it.isNullOrEmpty() }
|
||||
|
|
|
@ -43,6 +43,7 @@ import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll
|
|||
import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
|
@ -720,13 +721,21 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
@Test
|
||||
fun `in ItemType_Login state changing URI text field should trigger UriTextChange`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) {
|
||||
copy(uriList = listOf(UriItem("TestId", "URI", null)))
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("URI")
|
||||
.performTextInput("TestURI")
|
||||
.performTextInput("Test")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.UriTextChange("TestURI"),
|
||||
VaultAddEditAction.ItemType.LoginType.UriTextChange(
|
||||
UriItem("TestId", "TestURI", null),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -738,7 +747,9 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
.assertTextContains("")
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) { copy(uri = "NewURI") }
|
||||
updateLoginType(currentState) {
|
||||
copy(uriList = listOf(UriItem("TestId", "NewURI", null)))
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
|
@ -24,6 +25,7 @@ import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
|||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
|
@ -306,7 +308,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
typeContentViewState = createLoginTypeContentViewState(
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
uri = "www.mockuri1.com",
|
||||
uri = listOf(UriItem("testId", "www.mockuri1.com", UriMatchType.HOST)),
|
||||
totpCode = "mockTotp-1",
|
||||
canViewPassword = false,
|
||||
),
|
||||
|
@ -755,13 +757,15 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `UriTextChange should update uri in LoginItem`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.LoginType.UriTextChange("newUri")
|
||||
val action = VaultAddEditAction.ItemType.LoginType.UriTextChange(
|
||||
UriItem("testId", "TestUri", null),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = createLoginTypeContentViewState(
|
||||
uri = "newUri",
|
||||
uri = listOf(UriItem("testId", "TestUri", null)),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -985,18 +989,23 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `AddNewUriClick should emit ShowToast with 'Add New URI' message`() = runTest {
|
||||
fun `AddNewUriClick should update state with another empty UriItem`() = runTest {
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
every { UUID.randomUUID().toString() } returns "testId2"
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
.actionChannel
|
||||
.trySend(
|
||||
VaultAddEditAction.ItemType.LoginType.AddNewUriClick,
|
||||
)
|
||||
viewModel.trySendAction(VaultAddEditAction.ItemType.LoginType.AddNewUriClick)
|
||||
|
||||
assertEquals(VaultAddEditEvent.ShowToast("Add New URI".asText()), awaitItem())
|
||||
}
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = createLoginTypeContentViewState().copy(
|
||||
uriList = listOf(UriItem("testId", "", null), UriItem("testId2", "", null)),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expectedState,
|
||||
viewModel.stateFlow.value,
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1901,14 +1910,14 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
private fun createLoginTypeContentViewState(
|
||||
username: String = "",
|
||||
password: String = "",
|
||||
uri: String = "",
|
||||
uri: List<UriItem> = listOf(UriItem("testId", "", null)),
|
||||
totpCode: String? = null,
|
||||
canViewPassword: Boolean = true,
|
||||
): VaultAddEditState.ViewState.Content.ItemType.Login =
|
||||
VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = username,
|
||||
password = password,
|
||||
uri = uri,
|
||||
uriList = uri,
|
||||
totp = totpCode,
|
||||
canViewPassword = canViewPassword,
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import io.mockk.every
|
||||
|
@ -174,7 +175,7 @@ class CipherViewExtensionsTest {
|
|||
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = "username",
|
||||
password = "password",
|
||||
uri = "www.example.com",
|
||||
uriList = listOf(UriItem(TEST_ID, "www.example.com", null)),
|
||||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
canViewPassword = false,
|
||||
),
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.bitwarden.core.SecureNoteView
|
|||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import io.mockk.every
|
||||
|
@ -50,7 +51,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
uri = "mockUri-1",
|
||||
uriList = listOf(UriItem("testId", "mockUri-1", UriMatchType.DOMAIN)),
|
||||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
),
|
||||
)
|
||||
|
@ -127,7 +128,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
uri = "mockUri-1",
|
||||
uriList = listOf(UriItem("TestId", "mockUri-1", null)),
|
||||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
),
|
||||
)
|
||||
|
@ -147,7 +148,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
uris = listOf(
|
||||
LoginUriView(
|
||||
uri = "mockUri-1",
|
||||
match = UriMatchType.DOMAIN,
|
||||
match = null,
|
||||
),
|
||||
),
|
||||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
|
|
Loading…
Add table
Reference in a new issue