mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
I just wanted to add a single property...
This commit is contained in:
parent
7aab846244
commit
eea032d0a7
32 changed files with 792 additions and 9 deletions
|
@ -31,6 +31,7 @@ sealed class FlagKey<out T : Any> {
|
||||||
OnboardingFlow,
|
OnboardingFlow,
|
||||||
OnboardingCarousel,
|
OnboardingCarousel,
|
||||||
ImportLoginsFlow,
|
ImportLoginsFlow,
|
||||||
|
SshKeyCipherItems,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,4 +108,10 @@ sealed class FlagKey<out T : Any> {
|
||||||
override val defaultValue: String = "defaultValue"
|
override val defaultValue: String = "defaultValue"
|
||||||
override val isRemotelyConfigured: Boolean = true
|
override val isRemotelyConfigured: Boolean = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data object SshKeyCipherItems : FlagKey<Boolean>() {
|
||||||
|
override val keyName: String = "ssh-key-cipher-items"
|
||||||
|
override val defaultValue: Boolean = false
|
||||||
|
override val isRemotelyConfigured: Boolean = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,9 @@ val CipherView.subtitle: String?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Return SSH key subtitle (PM-10405)
|
||||||
|
CipherType.SSH_KEY -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -94,6 +94,7 @@ class VaultDiskSourceImpl(
|
||||||
shouldHidePasswords = collection.shouldHidePasswords,
|
shouldHidePasswords = collection.shouldHidePasswords,
|
||||||
externalId = collection.externalId,
|
externalId = collection.externalId,
|
||||||
isReadOnly = collection.isReadOnly,
|
isReadOnly = collection.isReadOnly,
|
||||||
|
canManage = collection.canManage,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -114,6 +115,7 @@ class VaultDiskSourceImpl(
|
||||||
shouldHidePasswords = entity.shouldHidePasswords,
|
shouldHidePasswords = entity.shouldHidePasswords,
|
||||||
externalId = entity.externalId,
|
externalId = entity.externalId,
|
||||||
isReadOnly = entity.isReadOnly,
|
isReadOnly = entity.isReadOnly,
|
||||||
|
canManage = entity.canManage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -229,6 +231,7 @@ class VaultDiskSourceImpl(
|
||||||
shouldHidePasswords = collection.shouldHidePasswords,
|
shouldHidePasswords = collection.shouldHidePasswords,
|
||||||
externalId = collection.externalId,
|
externalId = collection.externalId,
|
||||||
isReadOnly = collection.isReadOnly,
|
isReadOnly = collection.isReadOnly,
|
||||||
|
canManage = collection.canManage,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,7 +26,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
||||||
FolderEntity::class,
|
FolderEntity::class,
|
||||||
SendEntity::class,
|
SendEntity::class,
|
||||||
],
|
],
|
||||||
version = 3,
|
version = 4,
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
||||||
|
|
|
@ -30,4 +30,7 @@ data class CollectionEntity(
|
||||||
|
|
||||||
@ColumnInfo(name = "read_only")
|
@ColumnInfo(name = "read_only")
|
||||||
val isReadOnly: Boolean,
|
val isReadOnly: Boolean,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "manage")
|
||||||
|
val canManage: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
@ -51,6 +51,9 @@ data class CipherJsonRequest(
|
||||||
@SerialName("secureNote")
|
@SerialName("secureNote")
|
||||||
val secureNote: SyncResponseJson.Cipher.SecureNote?,
|
val secureNote: SyncResponseJson.Cipher.SecureNote?,
|
||||||
|
|
||||||
|
@SerialName("sshKey")
|
||||||
|
val sshKey: SyncResponseJson.Cipher.SshKey?,
|
||||||
|
|
||||||
@SerialName("folderId")
|
@SerialName("folderId")
|
||||||
val folderId: String?,
|
val folderId: String?,
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ enum class CipherTypeJson {
|
||||||
*/
|
*/
|
||||||
@SerialName("4")
|
@SerialName("4")
|
||||||
IDENTITY,
|
IDENTITY,
|
||||||
|
|
||||||
|
@SerialName("5")
|
||||||
|
SSH_KEY,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|
|
@ -718,6 +718,16 @@ data class SyncResponseJson(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SshKey(
|
||||||
|
@SerialName("publicKey")
|
||||||
|
val publicKey: String?,
|
||||||
|
@SerialName("privateKey")
|
||||||
|
val privateKey: String?,
|
||||||
|
@SerialName("keyFingerprint")
|
||||||
|
val fingerprint: String?,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents password history in the vault response.
|
* Represents password history in the vault response.
|
||||||
*
|
*
|
||||||
|
@ -927,6 +937,7 @@ data class SyncResponseJson(
|
||||||
* @property externalId The external ID of the collection (nullable).
|
* @property externalId The external ID of the collection (nullable).
|
||||||
* @property isReadOnly If the collection is marked as read only.
|
* @property isReadOnly If the collection is marked as read only.
|
||||||
* @property id The ID of the collection.
|
* @property id The ID of the collection.
|
||||||
|
* @property canManage If the collection can be managed.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Collection(
|
data class Collection(
|
||||||
|
@ -947,5 +958,8 @@ data class SyncResponseJson(
|
||||||
|
|
||||||
@SerialName("id")
|
@SerialName("id")
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
||||||
|
@SerialName("manage")
|
||||||
|
val canManage: Boolean,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,12 @@ import com.bitwarden.sdk.Fido2CredentialStore
|
||||||
import com.bitwarden.send.Send
|
import com.bitwarden.send.Send
|
||||||
import com.bitwarden.send.SendType
|
import com.bitwarden.send.SendType
|
||||||
import com.bitwarden.send.SendView
|
import com.bitwarden.send.SendView
|
||||||
|
import com.bitwarden.vault.CipherRepromptType
|
||||||
import com.bitwarden.vault.CipherType
|
import com.bitwarden.vault.CipherType
|
||||||
import com.bitwarden.vault.CipherView
|
import com.bitwarden.vault.CipherView
|
||||||
import com.bitwarden.vault.CollectionView
|
import com.bitwarden.vault.CollectionView
|
||||||
import com.bitwarden.vault.FolderView
|
import com.bitwarden.vault.FolderView
|
||||||
|
import com.bitwarden.vault.SshKeyView
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||||
|
@ -21,8 +23,10 @@ import com.x8bit.bitwarden.data.auth.repository.util.toUpdatedUserStateJson
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
|
import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
||||||
|
@ -113,6 +117,7 @@ import kotlinx.coroutines.launch
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A "stop timeout delay" in milliseconds used to let a shared coroutine continue to run for the
|
* A "stop timeout delay" in milliseconds used to let a shared coroutine continue to run for the
|
||||||
|
@ -138,6 +143,7 @@ class VaultRepositoryImpl(
|
||||||
private val vaultLockManager: VaultLockManager,
|
private val vaultLockManager: VaultLockManager,
|
||||||
private val totpCodeManager: TotpCodeManager,
|
private val totpCodeManager: TotpCodeManager,
|
||||||
private val userLogoutManager: UserLogoutManager,
|
private val userLogoutManager: UserLogoutManager,
|
||||||
|
private val featureFlagManager: FeatureFlagManager,
|
||||||
pushManager: PushManager,
|
pushManager: PushManager,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
|
@ -176,15 +182,52 @@ class VaultRepositoryImpl(
|
||||||
foldersStateFlow,
|
foldersStateFlow,
|
||||||
collectionsStateFlow,
|
collectionsStateFlow,
|
||||||
sendDataStateFlow,
|
sendDataStateFlow,
|
||||||
) { ciphersDataState, foldersDataState, collectionsDataState, sendsDataState ->
|
featureFlagManager.getFeatureFlagFlow(FlagKey.SshKeyCipherItems),
|
||||||
|
) { ciphersDataState, foldersDataState, collectionsDataState, sendsDataState, sshKeyCipherItemsEnabled ->
|
||||||
combineDataStates(
|
combineDataStates(
|
||||||
ciphersDataState,
|
ciphersDataState,
|
||||||
foldersDataState,
|
foldersDataState,
|
||||||
collectionsDataState,
|
collectionsDataState,
|
||||||
sendsDataState,
|
sendsDataState,
|
||||||
) { ciphersData, foldersData, collectionsData, sendsData ->
|
) { ciphersData, foldersData, collectionsData, sendsData ->
|
||||||
|
//{"Cipher":{"reprompt":0,"lastKnownRevisionDate":"2024-10-22T22:05:29.167Z","type":5,"sshKey":{"publicKey":"2.Rt3CPEUQCkax35wOV9dErA==|nocAOVCjoi0hh4PZwXOEIg==|h3WkP+gDNixsiNxAWMBqqBEwpZJRRORB+eX8DNo+EPU=","privateKey":"2.DRnXPdhLeiC14NoRjzmpQg==|a+L20fnV4tpmPm/+l6JEYQ==|CMc0MbQTXkTOayHEeL1G06H30DDOIic3l8wjUARgNIA=","keyFingerprint":"2.MiBuM5TNmWCiMufi3imBTw==|R6R6+dXw8CS5BV5Gk4plAg==|cupnpiK6rCbYaLLVXR8RqA/Iv+PJyIvPgJPwbwEQjrA="},"name":"2.0FJqtenxtVG8zh0AvVbwYA==|wU4BOVzSmEwEf5td1HuJ5g==|0K5wEQ/tMM7PlxBsz5EsZT0tP5fJSj/0hG5SYfUlTq4=","fields":[],"favorite":false,"key":"2.7XaYYaz2ONX/XmESHLj9Ag==|oAtEvfWpU9/47e7L+M4ob3/2D8I6z+0PXdiset/n+QUsCgEIgR3t3M7sQ3sYbH9Ui43Gb1NRNBLqx72Vo5JccfptTrjD2JJ1mffW3ZKNCTc=|hPQP4lKhrmDxwUz6yxwtp9/F2B5eL0aUWqSkDDb+vzY="},"CollectionIds":[]}
|
||||||
|
val mutableCipherStateData = ciphersData.toMutableList()
|
||||||
|
mutableCipherStateData.add(
|
||||||
|
CipherView(
|
||||||
|
id = UUID.randomUUID().toString(),
|
||||||
|
organizationId = null,
|
||||||
|
folderId = null,
|
||||||
|
collectionIds = emptyList(),
|
||||||
|
key = "2.7XaYYaz2ONX/XmESHLj9Ag==|oAtEvfWpU9/47e7L+M4ob3/2D8I6z+0PXdiset/n+QUsCgEIgR3t3M7sQ3sYbH9Ui43Gb1NRNBLqx72Vo5JccfptTrjD2JJ1mffW3ZKNCTc=|hPQP4lKhrmDxwUz6yxwtp9/F2B5eL0aUWqSkDDb+vzY=",
|
||||||
|
name = "Unlocks doorz",
|
||||||
|
notes = null,
|
||||||
|
type = CipherType.SSH_KEY,
|
||||||
|
login = null,
|
||||||
|
identity = null,
|
||||||
|
card = null,
|
||||||
|
secureNote = null,
|
||||||
|
sshKey = SshKeyView(
|
||||||
|
publicKey = "publicKey",
|
||||||
|
privateKey = "privateKey",
|
||||||
|
fingerprint = "fingerprint",
|
||||||
|
),
|
||||||
|
favorite = false,
|
||||||
|
reprompt = CipherRepromptType.NONE,
|
||||||
|
organizationUseTotp = false,
|
||||||
|
edit = true,
|
||||||
|
viewPassword = true,
|
||||||
|
localData = null,
|
||||||
|
attachments = emptyList(),
|
||||||
|
fields = null,
|
||||||
|
passwordHistory = null,
|
||||||
|
creationDate = DateTime.parse("2024-10-22T22:05:29.167Z"),
|
||||||
|
deletedDate = null,
|
||||||
|
revisionDate = DateTime.parse("2024-10-22T22:05:29.167Z"),
|
||||||
|
),
|
||||||
|
)
|
||||||
VaultData(
|
VaultData(
|
||||||
cipherViewList = ciphersData,
|
cipherViewList = mutableCipherStateData
|
||||||
|
.filterSshKeyCiphersIfNecessary(sshKeyCipherItemsEnabled),
|
||||||
fido2CredentialAutofillViewList = null,
|
fido2CredentialAutofillViewList = null,
|
||||||
folderViewList = foldersData,
|
folderViewList = foldersData,
|
||||||
collectionViewList = collectionsData,
|
collectionViewList = collectionsData,
|
||||||
|
@ -926,6 +969,8 @@ class VaultRepositoryImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//{"Cipher":{"reprompt":0,"lastKnownRevisionDate":"2024-10-22T21:15:02.098Z","type":5,"sshKey":{"publicKey":"2.e4Hv/B+xZqgB0LMRfL5Oow==|LbSjpCAxZyOVpiAb+xdIqQ==|MSDMmwQ5rrbtxru4hFFKrzdbHLNiRUpQh8/xX7//2bw=","privateKey":"2./CmD8GAJ4DU5hOCm8GnJMA==|qcJcSSonXLQQcci0ION4/g==|EwXB1bx0yQkY86RW9N2bmrtswC6+cdt6P7wrw+lyR8c=","keyFingerprint":"2.mx/OnN5fJMses2/HG/Q4ag==|cdsSFuXfD6Q8nQ+1sP8F7Q==|tMMUQ0jAGgYwbaEnqm9oMpIL2r1rDBtxDIIt6GN3dto="},"name":"2.TS++PBphUVR5Hrb4p2Vpiw==|hX5D0ZU3sZ455im8nSKxQg==|DvMxpDCbkDtL9TMNJDOt8fuYF1q69Apxke6s08u72JY=","fields":[],"favorite":false,"key":"2.ff7G7cYSv/8CH902tK0iFg==|2Jtmaylw6H6wQezZQepMTNie+jlmSQOWJIScx6I96AFCIP2yX29kLn9XCbFwTLblwkgVDGAGPofz7n7LDk6jY4NxjaiUqEhm/HZJ29oSP1Y=|T/Mj/aIZbW9YovgkTnUrpmCKcW/uwFKH1lHYJd0J3KA="},"CollectionIds":[]}
|
||||||
|
|
||||||
private fun observeVaultDiskCiphers(
|
private fun observeVaultDiskCiphers(
|
||||||
userId: String,
|
userId: String,
|
||||||
): Flow<DataState<List<CipherView>>> =
|
): Flow<DataState<List<CipherView>>> =
|
||||||
|
@ -947,6 +992,13 @@ class VaultRepositoryImpl(
|
||||||
.map { it.orLoadingIfNotSynced(userId = userId) }
|
.map { it.orLoadingIfNotSynced(userId = userId) }
|
||||||
.onEach { mutableCiphersStateFlow.value = it }
|
.onEach { mutableCiphersStateFlow.value = it }
|
||||||
|
|
||||||
|
private fun List<CipherView>.filterSshKeyCiphersIfNecessary(enabled: Boolean): List<CipherView> =
|
||||||
|
if (!enabled) {
|
||||||
|
filter { it.type != CipherType.SSH_KEY }
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeVaultDiskDomains(
|
private fun observeVaultDiskDomains(
|
||||||
userId: String,
|
userId: String,
|
||||||
): Flow<DataState<DomainsData>> =
|
): Flow<DataState<DomainsData>> =
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.repository.di
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||||
|
@ -49,6 +50,7 @@ object VaultRepositoryModule {
|
||||||
totpCodeManager: TotpCodeManager,
|
totpCodeManager: TotpCodeManager,
|
||||||
pushManager: PushManager,
|
pushManager: PushManager,
|
||||||
userLogoutManager: UserLogoutManager,
|
userLogoutManager: UserLogoutManager,
|
||||||
|
featureFlagManager: FeatureFlagManager,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
): VaultRepository = VaultRepositoryImpl(
|
): VaultRepository = VaultRepositoryImpl(
|
||||||
syncService = syncService,
|
syncService = syncService,
|
||||||
|
@ -66,6 +68,7 @@ object VaultRepositoryModule {
|
||||||
totpCodeManager = totpCodeManager,
|
totpCodeManager = totpCodeManager,
|
||||||
pushManager = pushManager,
|
pushManager = pushManager,
|
||||||
userLogoutManager = userLogoutManager,
|
userLogoutManager = userLogoutManager,
|
||||||
|
featureFlagManager = featureFlagManager,
|
||||||
clock = clock,
|
clock = clock,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import com.bitwarden.vault.LoginUri
|
||||||
import com.bitwarden.vault.PasswordHistory
|
import com.bitwarden.vault.PasswordHistory
|
||||||
import com.bitwarden.vault.SecureNote
|
import com.bitwarden.vault.SecureNote
|
||||||
import com.bitwarden.vault.SecureNoteType
|
import com.bitwarden.vault.SecureNoteType
|
||||||
|
import com.bitwarden.vault.SshKey
|
||||||
import com.bitwarden.vault.UriMatchType
|
import com.bitwarden.vault.UriMatchType
|
||||||
import com.x8bit.bitwarden.data.platform.util.SpecialCharWithPrecedenceComparator
|
import com.x8bit.bitwarden.data.platform.util.SpecialCharWithPrecedenceComparator
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||||
|
@ -55,6 +56,7 @@ fun Cipher.toEncryptedNetworkCipher(): CipherJsonRequest =
|
||||||
isFavorite = favorite,
|
isFavorite = favorite,
|
||||||
card = card?.toEncryptedNetworkCard(),
|
card = card?.toEncryptedNetworkCard(),
|
||||||
key = key,
|
key = key,
|
||||||
|
sshKey = sshKey?.toEncryptedNetworkSshKey(),
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,6 +104,13 @@ private fun Card.toEncryptedNetworkCard(): SyncResponseJson.Cipher.Card =
|
||||||
brand = brand,
|
brand = brand,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun SshKey.toEncryptedNetworkSshKey(): SyncResponseJson.Cipher.SshKey =
|
||||||
|
SyncResponseJson.Cipher.SshKey(
|
||||||
|
publicKey = publicKey,
|
||||||
|
privateKey = privateKey,
|
||||||
|
fingerprint = fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a list of Bitwarden SDK [Field] objects to a corresponding
|
* Converts a list of Bitwarden SDK [Field] objects to a corresponding
|
||||||
* list of [SyncResponseJson.Cipher.Field] objects.
|
* list of [SyncResponseJson.Cipher.Field] objects.
|
||||||
|
@ -309,6 +318,7 @@ private fun CipherType.toNetworkCipherType(): CipherTypeJson =
|
||||||
CipherType.SECURE_NOTE -> CipherTypeJson.SECURE_NOTE
|
CipherType.SECURE_NOTE -> CipherTypeJson.SECURE_NOTE
|
||||||
CipherType.CARD -> CipherTypeJson.CARD
|
CipherType.CARD -> CipherTypeJson.CARD
|
||||||
CipherType.IDENTITY -> CipherTypeJson.IDENTITY
|
CipherType.IDENTITY -> CipherTypeJson.IDENTITY
|
||||||
|
CipherType.SSH_KEY -> CipherTypeJson.SSH_KEY
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -334,6 +344,8 @@ fun SyncResponseJson.Cipher.toEncryptedSdkCipher(): Cipher =
|
||||||
type = type.toSdkCipherType(),
|
type = type.toSdkCipherType(),
|
||||||
login = login?.toSdkLogin(),
|
login = login?.toSdkLogin(),
|
||||||
identity = identity?.toSdkIdentity(),
|
identity = identity?.toSdkIdentity(),
|
||||||
|
// TODO: Support SSH key (PM-10405)
|
||||||
|
sshKey = null,
|
||||||
card = card?.toSdkCard(),
|
card = card?.toSdkCard(),
|
||||||
secureNote = secureNote?.toSdkSecureNote(),
|
secureNote = secureNote?.toSdkSecureNote(),
|
||||||
favorite = isFavorite,
|
favorite = isFavorite,
|
||||||
|
@ -517,6 +529,7 @@ fun CipherTypeJson.toSdkCipherType(): CipherType =
|
||||||
CipherTypeJson.SECURE_NOTE -> CipherType.SECURE_NOTE
|
CipherTypeJson.SECURE_NOTE -> CipherType.SECURE_NOTE
|
||||||
CipherTypeJson.CARD -> CipherType.CARD
|
CipherTypeJson.CARD -> CipherType.CARD
|
||||||
CipherTypeJson.IDENTITY -> CipherType.IDENTITY
|
CipherTypeJson.IDENTITY -> CipherType.IDENTITY
|
||||||
|
CipherTypeJson.SSH_KEY -> CipherType.SSH_KEY
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,6 +17,7 @@ fun SyncResponseJson.Collection.toEncryptedSdkCollection(): Collection =
|
||||||
externalId = this.externalId,
|
externalId = this.externalId,
|
||||||
hidePasswords = this.shouldHidePasswords,
|
hidePasswords = this.shouldHidePasswords,
|
||||||
readOnly = this.isReadOnly,
|
readOnly = this.isReadOnly,
|
||||||
|
manage = this.canManage,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,6 +27,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
|
||||||
FlagKey.OnboardingCarousel,
|
FlagKey.OnboardingCarousel,
|
||||||
FlagKey.OnboardingFlow,
|
FlagKey.OnboardingFlow,
|
||||||
FlagKey.ImportLoginsFlow,
|
FlagKey.ImportLoginsFlow,
|
||||||
|
FlagKey.SshKeyCipherItems,
|
||||||
-> BooleanFlagItem(
|
-> BooleanFlagItem(
|
||||||
label = flagKey.getDisplayLabel(),
|
label = flagKey.getDisplayLabel(),
|
||||||
key = flagKey as FlagKey<Boolean>,
|
key = flagKey as FlagKey<Boolean>,
|
||||||
|
@ -69,4 +70,5 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
|
||||||
FlagKey.OnboardingCarousel -> stringResource(R.string.onboarding_carousel)
|
FlagKey.OnboardingCarousel -> stringResource(R.string.onboarding_carousel)
|
||||||
FlagKey.OnboardingFlow -> stringResource(R.string.onboarding_flow)
|
FlagKey.OnboardingFlow -> stringResource(R.string.onboarding_flow)
|
||||||
FlagKey.ImportLoginsFlow -> stringResource(R.string.import_logins_flow)
|
FlagKey.ImportLoginsFlow -> stringResource(R.string.import_logins_flow)
|
||||||
|
FlagKey.SshKeyCipherItems -> stringResource(R.string.ssh_key_cipher_item_types)
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,6 +247,7 @@ private val CipherType.iconRes: Int
|
||||||
CipherType.SECURE_NOTE -> R.drawable.ic_note
|
CipherType.SECURE_NOTE -> R.drawable.ic_note
|
||||||
CipherType.CARD -> R.drawable.ic_payment_card
|
CipherType.CARD -> R.drawable.ic_payment_card
|
||||||
CipherType.IDENTITY -> R.drawable.ic_id_card
|
CipherType.IDENTITY -> R.drawable.ic_id_card
|
||||||
|
CipherType.SSH_KEY -> R.drawable.ic_ssh_key
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,11 +31,13 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
fun VaultAddEditContent(
|
fun VaultAddEditContent(
|
||||||
state: VaultAddEditState.ViewState.Content,
|
state: VaultAddEditState.ViewState.Content,
|
||||||
isAddItemMode: Boolean,
|
isAddItemMode: Boolean,
|
||||||
|
typeOptions: List<VaultAddEditState.ItemTypeOption>,
|
||||||
onTypeOptionClicked: (VaultAddEditState.ItemTypeOption) -> Unit,
|
onTypeOptionClicked: (VaultAddEditState.ItemTypeOption) -> Unit,
|
||||||
commonTypeHandlers: VaultAddEditCommonHandlers,
|
commonTypeHandlers: VaultAddEditCommonHandlers,
|
||||||
loginItemTypeHandlers: VaultAddEditLoginTypeHandlers,
|
loginItemTypeHandlers: VaultAddEditLoginTypeHandlers,
|
||||||
identityItemTypeHandlers: VaultAddEditIdentityTypeHandlers,
|
identityItemTypeHandlers: VaultAddEditIdentityTypeHandlers,
|
||||||
cardItemTypeHandlers: VaultAddEditCardTypeHandlers,
|
cardItemTypeHandlers: VaultAddEditCardTypeHandlers,
|
||||||
|
sshKeyItemTypeHandlers: VaultAddEditSshKeyTypeHandlers,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
permissionsManager: PermissionsManager,
|
permissionsManager: PermissionsManager,
|
||||||
) {
|
) {
|
||||||
|
@ -45,6 +47,7 @@ fun VaultAddEditContent(
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> Unit
|
is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> Unit
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.Card -> Unit
|
is VaultAddEditState.ViewState.Content.ItemType.Card -> Unit
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.Identity -> Unit
|
is VaultAddEditState.ViewState.Content.ItemType.Identity -> Unit
|
||||||
|
is VaultAddEditState.ViewState.Content.ItemType.SshKey -> Unit
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.Login -> {
|
is VaultAddEditState.ViewState.Content.ItemType.Login -> {
|
||||||
loginItemTypeHandlers.onSetupTotpClick(isGranted)
|
loginItemTypeHandlers.onSetupTotpClick(isGranted)
|
||||||
}
|
}
|
||||||
|
@ -77,6 +80,7 @@ fun VaultAddEditContent(
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
TypeOptionsItem(
|
TypeOptionsItem(
|
||||||
|
entries = typeOptions,
|
||||||
itemType = state.type,
|
itemType = state.type,
|
||||||
onTypeOptionClicked = onTypeOptionClicked,
|
onTypeOptionClicked = onTypeOptionClicked,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -131,6 +135,15 @@ fun VaultAddEditContent(
|
||||||
commonTypeHandlers = commonTypeHandlers,
|
commonTypeHandlers = commonTypeHandlers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultAddEditState.ViewState.Content.ItemType.SshKey -> {
|
||||||
|
vaultAddEditSshKeyItems(
|
||||||
|
commonState = state.common,
|
||||||
|
sshKeyState = state.type,
|
||||||
|
commonTypeHandlers = commonTypeHandlers,
|
||||||
|
sshKeyTypeHandlers = sshKeyItemTypeHandlers,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
|
@ -141,12 +154,12 @@ fun VaultAddEditContent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TypeOptionsItem(
|
private fun TypeOptionsItem(
|
||||||
|
entries: List<VaultAddEditState.ItemTypeOption>,
|
||||||
itemType: VaultAddEditState.ViewState.Content.ItemType,
|
itemType: VaultAddEditState.ViewState.Content.ItemType,
|
||||||
onTypeOptionClicked: (VaultAddEditState.ItemTypeOption) -> Unit,
|
onTypeOptionClicked: (VaultAddEditState.ItemTypeOption) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val possibleMainStates = VaultAddEditState.ItemTypeOption.entries.toList()
|
val optionsWithStrings = entries.associateWith { stringResource(id = it.labelRes) }
|
||||||
val optionsWithStrings = possibleMainStates.associateWith { stringResource(id = it.labelRes) }
|
|
||||||
|
|
||||||
BitwardenMultiSelectButton(
|
BitwardenMultiSelectButton(
|
||||||
label = stringResource(id = R.string.type),
|
label = stringResource(id = R.string.type),
|
||||||
|
|
|
@ -155,6 +155,10 @@ fun VaultAddEditScreen(
|
||||||
VaultAddEditCardTypeHandlers.create(viewModel = viewModel)
|
VaultAddEditCardTypeHandlers.create(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sshKeyItemTypeHandlers = remember(viewModel) {
|
||||||
|
VaultAddEditSshKeyTypeHandlers.create(viewModel = viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
val confirmDeleteClickAction = remember(viewModel) {
|
val confirmDeleteClickAction = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(VaultAddEditAction.Common.ConfirmDeleteClick) }
|
{ viewModel.trySendAction(VaultAddEditAction.Common.ConfirmDeleteClick) }
|
||||||
}
|
}
|
||||||
|
@ -321,6 +325,7 @@ fun VaultAddEditScreen(
|
||||||
VaultAddEditContent(
|
VaultAddEditContent(
|
||||||
state = viewState,
|
state = viewState,
|
||||||
isAddItemMode = state.isAddItemMode,
|
isAddItemMode = state.isAddItemMode,
|
||||||
|
typeOptions = state.supportedItemTypes,
|
||||||
onTypeOptionClicked = remember(viewModel) {
|
onTypeOptionClicked = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(VaultAddEditAction.Common.TypeOptionSelect(it)) }
|
{ viewModel.trySendAction(VaultAddEditAction.Common.TypeOptionSelect(it)) }
|
||||||
},
|
},
|
||||||
|
@ -329,6 +334,7 @@ fun VaultAddEditScreen(
|
||||||
permissionsManager = permissionsManager,
|
permissionsManager = permissionsManager,
|
||||||
identityItemTypeHandlers = identityItemTypeHandlers,
|
identityItemTypeHandlers = identityItemTypeHandlers,
|
||||||
cardItemTypeHandlers = cardItemTypeHandlers,
|
cardItemTypeHandlers = cardItemTypeHandlers,
|
||||||
|
sshKeyItemTypeHandlers = sshKeyItemTypeHandlers,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||||
|
|
||||||
|
fun LazyListScope.vaultAddEditSshKeyItems(
|
||||||
|
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||||
|
sshKeyState: VaultAddEditState.ViewState.Content.ItemType.SshKey,
|
||||||
|
commonTypeHandlers: VaultAddEditCommonHandlers,
|
||||||
|
sshKeyTypeHandlers: VaultAddEditSshKeyTypeHandlers,
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenTextField(
|
||||||
|
label = stringResource(id = R.string.name),
|
||||||
|
value = commonState.name,
|
||||||
|
onValueChange = commonTypeHandlers.onNameTextChange,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag("ItemNameEntry")
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenPasswordField(
|
||||||
|
label = stringResource(id = R.string.public_key),
|
||||||
|
value = sshKeyState.publicKey,
|
||||||
|
onValueChange = sshKeyTypeHandlers.onPublicKeyTextChange,
|
||||||
|
showPassword = sshKeyState.showPublicKey,
|
||||||
|
showPasswordChange = { sshKeyTypeHandlers.onPublicKeyVisibilityChange(it) },
|
||||||
|
showPasswordTestTag = "ViewPublicKeyButton",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenPasswordField(
|
||||||
|
label = stringResource(id = R.string.private_key),
|
||||||
|
value = sshKeyState.privateKey,
|
||||||
|
onValueChange = sshKeyTypeHandlers.onPrivateKeyTextChange,
|
||||||
|
showPassword = sshKeyState.showPrivateKey,
|
||||||
|
showPasswordChange = { sshKeyTypeHandlers.onPrivateKeyVisibilityChange(it) },
|
||||||
|
showPasswordTestTag = "ViewPrivateKeyButton",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenPasswordField(
|
||||||
|
label = stringResource(id = R.string.fingerprint),
|
||||||
|
value = sshKeyState.fingerprint,
|
||||||
|
onValueChange = sshKeyTypeHandlers.onFingerprintTextChange,
|
||||||
|
showPassword = sshKeyState.showFingerprint,
|
||||||
|
showPasswordChange = { sshKeyTypeHandlers.onFingerprintVisibilityChange(it) },
|
||||||
|
showPasswordTestTag = "ViewFingerprintButton",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class VaultAddEditSshKeyTypeHandlers(
|
||||||
|
val onPublicKeyTextChange: (String) -> Unit,
|
||||||
|
val onPublicKeyVisibilityChange: (Boolean) -> Unit,
|
||||||
|
val onPrivateKeyTextChange: (String) -> Unit,
|
||||||
|
val onPrivateKeyVisibilityChange: (Boolean) -> Unit,
|
||||||
|
val onFingerprintTextChange: (String) -> Unit,
|
||||||
|
val onFingerprintVisibilityChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun create(viewModel: VaultAddEditViewModel): VaultAddEditSshKeyTypeHandlers =
|
||||||
|
VaultAddEditSshKeyTypeHandlers(
|
||||||
|
onPublicKeyTextChange = { newPublicKey ->
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange(
|
||||||
|
publicKey = newPublicKey,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onPublicKeyVisibilityChange = {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultAddEditAction.ItemType.SshKeyType.PublicKeyVisibilityChange(
|
||||||
|
isVisible = it
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onPrivateKeyTextChange = { newPrivateKey ->
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange(
|
||||||
|
privateKey = newPrivateKey,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onPrivateKeyVisibilityChange = {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange(
|
||||||
|
isVisible = it
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onFingerprintTextChange = { newFingerprint ->
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange(
|
||||||
|
fingerprint = newFingerprint,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onFingerprintVisibilityChange = {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultAddEditAction.ItemType.SshKeyType.FingerprintVisibilityChange(
|
||||||
|
isVisible = it
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,12 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
|
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
|
||||||
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
|
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
||||||
|
@ -101,6 +103,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
private val resourceManager: ResourceManager,
|
private val resourceManager: ResourceManager,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val organizationEventManager: OrganizationEventManager,
|
private val organizationEventManager: OrganizationEventManager,
|
||||||
|
private val featureFlagManager: FeatureFlagManager,
|
||||||
) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
|
) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
|
||||||
// We load the state from the savedStateHandle for testing purposes.
|
// We load the state from the savedStateHandle for testing purposes.
|
||||||
initialState = savedStateHandle[KEY_STATE]
|
initialState = savedStateHandle[KEY_STATE]
|
||||||
|
@ -136,18 +139,31 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val supportedItemTypes =
|
||||||
|
if (featureFlagManager.getFeatureFlag(key = FlagKey.SshKeyCipherItems)) {
|
||||||
|
VaultAddEditState.ItemTypeOption.entries.toList()
|
||||||
|
} else {
|
||||||
|
VaultAddEditState.ItemTypeOption.entries.filterNot {
|
||||||
|
it == VaultAddEditState.ItemTypeOption.SSH_KEYS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VaultAddEditState(
|
VaultAddEditState(
|
||||||
vaultAddEditType = vaultAddEditType,
|
vaultAddEditType = vaultAddEditType,
|
||||||
viewState = when (vaultAddEditType) {
|
viewState = when (vaultAddEditType) {
|
||||||
is VaultAddEditType.AddItem -> {
|
is VaultAddEditType.AddItem -> {
|
||||||
autofillSelectionData
|
autofillSelectionData
|
||||||
?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
||||||
?: autofillSaveItem?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
?: autofillSaveItem?.toDefaultAddTypeContent(
|
||||||
|
isIndividualVaultDisabled
|
||||||
|
)
|
||||||
?: fido2CreationRequest?.toDefaultAddTypeContent(
|
?: fido2CreationRequest?.toDefaultAddTypeContent(
|
||||||
attestationOptions = fido2AttestationOptions,
|
attestationOptions = fido2AttestationOptions,
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
)
|
)
|
||||||
?: totpData?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
?: totpData?.toDefaultAddTypeContent(
|
||||||
|
isIndividualVaultDisabled
|
||||||
|
)
|
||||||
?: VaultAddEditState.ViewState.Content(
|
?: VaultAddEditState.ViewState.Content(
|
||||||
common = VaultAddEditState.ViewState.Content.Common(),
|
common = VaultAddEditState.ViewState.Content.Common(),
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
|
@ -163,6 +179,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
// Set special conditions for autofill and fido2 save
|
// Set special conditions for autofill and fido2 save
|
||||||
shouldShowCloseButton = autofillSaveItem == null && fido2AttestationOptions == null,
|
shouldShowCloseButton = autofillSaveItem == null && fido2AttestationOptions == null,
|
||||||
shouldExitOnSave = shouldExitOnSave,
|
shouldExitOnSave = shouldExitOnSave,
|
||||||
|
supportedItemTypes = supportedItemTypes,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -204,6 +221,11 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
featureFlagManager.getFeatureFlagFlow(FlagKey.SshKeyCipherItems)
|
||||||
|
.map { VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive(it) }
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAction(action: VaultAddEditAction) {
|
override fun handleAction(action: VaultAddEditAction) {
|
||||||
|
@ -212,6 +234,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
is VaultAddEditAction.ItemType.LoginType -> handleAddLoginTypeAction(action)
|
is VaultAddEditAction.ItemType.LoginType -> handleAddLoginTypeAction(action)
|
||||||
is VaultAddEditAction.ItemType.IdentityType -> handleIdentityTypeActions(action)
|
is VaultAddEditAction.ItemType.IdentityType -> handleIdentityTypeActions(action)
|
||||||
is VaultAddEditAction.ItemType.CardType -> handleCardTypeActions(action)
|
is VaultAddEditAction.ItemType.CardType -> handleCardTypeActions(action)
|
||||||
|
is VaultAddEditAction.ItemType.SshKeyType -> handleSshKeyTypeActions(action)
|
||||||
is VaultAddEditAction.Internal -> handleInternalActions(action)
|
is VaultAddEditAction.Internal -> handleInternalActions(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,6 +344,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
VaultAddEditState.ItemTypeOption.CARD -> handleSwitchToAddCardItem()
|
VaultAddEditState.ItemTypeOption.CARD -> handleSwitchToAddCardItem()
|
||||||
VaultAddEditState.ItemTypeOption.IDENTITY -> handleSwitchToAddIdentityItem()
|
VaultAddEditState.ItemTypeOption.IDENTITY -> handleSwitchToAddIdentityItem()
|
||||||
VaultAddEditState.ItemTypeOption.SECURE_NOTES -> handleSwitchToAddSecureNotesItem()
|
VaultAddEditState.ItemTypeOption.SECURE_NOTES -> handleSwitchToAddSecureNotesItem()
|
||||||
|
VaultAddEditState.ItemTypeOption.SSH_KEYS -> handleSwitchToSshKeyItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,6 +396,18 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSwitchToSshKeyItem() {
|
||||||
|
updateContent { currentContent ->
|
||||||
|
currentContent.copy(
|
||||||
|
common = currentContent.clearNonSharedData(),
|
||||||
|
type = currentContent.previousItemTypeOrDefault(
|
||||||
|
itemType = VaultAddEditState.ItemTypeOption.SSH_KEYS,
|
||||||
|
),
|
||||||
|
previousItemTypes = currentContent.toUpdatedPreviousItemTypes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
private fun handleSaveClick() = onContent { content ->
|
private fun handleSaveClick() = onContent { content ->
|
||||||
if (content.common.name.isBlank()) {
|
if (content.common.name.isBlank()) {
|
||||||
|
@ -1364,6 +1400,64 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
|
|
||||||
//endregion Card Type Handlers
|
//endregion Card Type Handlers
|
||||||
|
|
||||||
|
//region SSH Key Type Handlers
|
||||||
|
|
||||||
|
private fun handleSshKeyTypeActions(action: VaultAddEditAction.ItemType.SshKeyType) {
|
||||||
|
when (action) {
|
||||||
|
is VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange -> {
|
||||||
|
handlePublicKeyTextChange(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultAddEditAction.ItemType.SshKeyType.PublicKeyVisibilityChange -> {
|
||||||
|
handlePublicKeyVisibilityChange(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange -> {
|
||||||
|
handlePrivateKeyTextChange(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange -> {
|
||||||
|
handlePrivateKeyVisibilityChange(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange -> {
|
||||||
|
handleSshKeyFingerprintTextChange(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultAddEditAction.ItemType.SshKeyType.FingerprintVisibilityChange -> {
|
||||||
|
handleSshKeyFingerprintVisibilityChange(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePublicKeyTextChange(action: VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange) {
|
||||||
|
updateSshKeyContent { it.copy(publicKey = action.publicKey) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePublicKeyVisibilityChange(action: VaultAddEditAction.ItemType.SshKeyType.PublicKeyVisibilityChange) {
|
||||||
|
updateSshKeyContent { it.copy(showPublicKey = action.isVisible) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePrivateKeyTextChange(action: VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange) {
|
||||||
|
updateSshKeyContent { it.copy(privateKey = action.privateKey) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePrivateKeyVisibilityChange(action: VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange) {
|
||||||
|
updateSshKeyContent { it.copy(showPrivateKey = action.isVisible) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSshKeyFingerprintTextChange(action: VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange) {
|
||||||
|
updateSshKeyContent { it.copy(fingerprint = action.fingerprint) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSshKeyFingerprintVisibilityChange(
|
||||||
|
action: VaultAddEditAction.ItemType.SshKeyType.FingerprintVisibilityChange,
|
||||||
|
) {
|
||||||
|
updateSshKeyContent { it.copy(showFingerprint = action.isVisible) }
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion SSH Key Type Handlers
|
||||||
|
|
||||||
//region Internal Type Handlers
|
//region Internal Type Handlers
|
||||||
|
|
||||||
private fun handleInternalActions(action: VaultAddEditAction.Internal) {
|
private fun handleInternalActions(action: VaultAddEditAction.Internal) {
|
||||||
|
@ -1398,6 +1492,10 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
is VaultAddEditAction.Internal.ValidateFido2PinResultReceive -> {
|
is VaultAddEditAction.Internal.ValidateFido2PinResultReceive -> {
|
||||||
handleValidateFido2PinResultReceive(action)
|
handleValidateFido2PinResultReceive(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive -> {
|
||||||
|
handleSshKeyCipherItemsFeatureFlagReceive(action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1708,6 +1806,22 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
getRequestAndRegisterCredential()
|
getRequestAndRegisterCredential()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSshKeyCipherItemsFeatureFlagReceive(
|
||||||
|
action: VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive,
|
||||||
|
) {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
supportedItemTypes = if (action.enabled) {
|
||||||
|
VaultAddEditState.ItemTypeOption.entries
|
||||||
|
} else {
|
||||||
|
VaultAddEditState.ItemTypeOption.entries.filterNot {
|
||||||
|
it == VaultAddEditState.ItemTypeOption.SSH_KEYS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion Internal Type Handlers
|
//endregion Internal Type Handlers
|
||||||
|
|
||||||
//region Utility Functions
|
//region Utility Functions
|
||||||
|
@ -1819,6 +1933,19 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun updateSshKeyContent(
|
||||||
|
crossinline block: (VaultAddEditState.ViewState.Content.ItemType.SshKey) ->
|
||||||
|
VaultAddEditState.ViewState.Content.ItemType.SshKey,
|
||||||
|
) {
|
||||||
|
updateContent { currentContent ->
|
||||||
|
(currentContent.type as? VaultAddEditState.ViewState.Content.ItemType.SshKey)?.let {
|
||||||
|
currentContent.copy(
|
||||||
|
type = block(it),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun VaultAddEditState.ViewState.Content.clearNonSharedData():
|
private fun VaultAddEditState.ViewState.Content.clearNonSharedData():
|
||||||
VaultAddEditState.ViewState.Content.Common =
|
VaultAddEditState.ViewState.Content.Common =
|
||||||
common.copy(
|
common.copy(
|
||||||
|
@ -1853,6 +1980,10 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
VaultAddEditState.ItemTypeOption.SECURE_NOTES -> {
|
VaultAddEditState.ItemTypeOption.SECURE_NOTES -> {
|
||||||
VaultAddEditState.ViewState.Content.ItemType.SecureNotes
|
VaultAddEditState.ViewState.Content.ItemType.SecureNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VaultAddEditState.ItemTypeOption.SSH_KEYS -> {
|
||||||
|
VaultAddEditState.ViewState.Content.ItemType.SshKey()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1915,6 +2046,7 @@ data class VaultAddEditState(
|
||||||
// Internal
|
// Internal
|
||||||
val shouldExitOnSave: Boolean = false,
|
val shouldExitOnSave: Boolean = false,
|
||||||
val totpData: TotpData? = null,
|
val totpData: TotpData? = null,
|
||||||
|
val supportedItemTypes: List<ItemTypeOption>,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1958,6 +2090,7 @@ data class VaultAddEditState(
|
||||||
CARD(R.string.type_card),
|
CARD(R.string.type_card),
|
||||||
IDENTITY(R.string.type_identity),
|
IDENTITY(R.string.type_identity),
|
||||||
SECURE_NOTES(R.string.type_secure_note),
|
SECURE_NOTES(R.string.type_secure_note),
|
||||||
|
SSH_KEYS(R.string.type_ssh_key),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2163,6 +2296,25 @@ data class VaultAddEditState(
|
||||||
data object SecureNotes : ItemType() {
|
data object SecureNotes : ItemType() {
|
||||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SECURE_NOTES
|
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SECURE_NOTES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the `SshKey` item type.
|
||||||
|
*
|
||||||
|
* @property publicKey The public key for the SSH key item.
|
||||||
|
* @property privateKey The private key for the SSH key item.
|
||||||
|
* @property fingerprint The fingerprint for the SSH key item.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class SshKey(
|
||||||
|
val publicKey: String = "",
|
||||||
|
val privateKey: String = "",
|
||||||
|
val fingerprint: String = "",
|
||||||
|
val showPublicKey: Boolean = false,
|
||||||
|
val showPrivateKey: Boolean = false,
|
||||||
|
val showFingerprint: Boolean = false,
|
||||||
|
) : ItemType() {
|
||||||
|
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SSH_KEYS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2929,6 +3081,15 @@ sealed class VaultAddEditAction {
|
||||||
*/
|
*/
|
||||||
data class SecurityCodeVisibilityChange(val isVisible: Boolean) : CardType()
|
data class SecurityCodeVisibilityChange(val isVisible: Boolean) : CardType()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class SshKeyType : ItemType() {
|
||||||
|
data class PublicKeyTextChange(val publicKey: String) : SshKeyType()
|
||||||
|
data class PublicKeyVisibilityChange(val isVisible: Boolean) : SshKeyType()
|
||||||
|
data class PrivateKeyTextChange(val privateKey: String) : SshKeyType()
|
||||||
|
data class PrivateKeyVisibilityChange(val isVisible: Boolean) : SshKeyType()
|
||||||
|
data class FingerprintTextChange(val fingerprint: String) : SshKeyType()
|
||||||
|
data class FingerprintVisibilityChange(val isVisible: Boolean) : SshKeyType()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2953,6 +3114,10 @@ sealed class VaultAddEditAction {
|
||||||
val generatorResult: GeneratorResult,
|
val generatorResult: GeneratorResult,
|
||||||
) : Internal()
|
) : Internal()
|
||||||
|
|
||||||
|
data class SshKeyCipherItemsFeatureFlagReceive(
|
||||||
|
val enabled: Boolean,
|
||||||
|
) : Internal()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the vault item data has been received.
|
* Indicates that the vault item data has been received.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -66,4 +66,5 @@ private val VaultAddEditState.ViewState.Content.ItemType.defaultLinkedFieldTypeO
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.Identity -> VaultLinkedFieldType.TITLE
|
is VaultAddEditState.ViewState.Content.ItemType.Identity -> VaultLinkedFieldType.TITLE
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.Login -> VaultLinkedFieldType.USERNAME
|
is VaultAddEditState.ViewState.Content.ItemType.Login -> VaultLinkedFieldType.USERNAME
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> null
|
is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> null
|
||||||
|
is VaultAddEditState.ViewState.Content.ItemType.SshKey -> null
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,12 @@ fun CipherView.toViewState(
|
||||||
zip = identity?.postalCode.orEmpty(),
|
zip = identity?.postalCode.orEmpty(),
|
||||||
country = identity?.country.orEmpty(),
|
country = identity?.country.orEmpty(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CipherType.SSH_KEY -> VaultAddEditState.ViewState.Content.ItemType.SshKey(
|
||||||
|
publicKey = sshKey?.publicKey.orEmpty(),
|
||||||
|
privateKey = sshKey?.privateKey.orEmpty(),
|
||||||
|
fingerprint = sshKey?.fingerprint.orEmpty(),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
common = VaultAddEditState.ViewState.Content.Common(
|
common = VaultAddEditState.ViewState.Content.Common(
|
||||||
originalCipher = this,
|
originalCipher = this,
|
||||||
|
|
|
@ -45,6 +45,7 @@ import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCardItemTypeHandlers
|
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCardItemTypeHandlers
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCommonItemTypeHandlers
|
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCommonItemTypeHandlers
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultLoginItemTypeHandlers
|
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultLoginItemTypeHandlers
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultSshKeyItemTypeHandlers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the vault item screen.
|
* Displays the vault item screen.
|
||||||
|
@ -265,6 +266,9 @@ fun VaultItemScreen(
|
||||||
vaultCardItemTypeHandlers = remember(viewModel) {
|
vaultCardItemTypeHandlers = remember(viewModel) {
|
||||||
VaultCardItemTypeHandlers.create(viewModel = viewModel)
|
VaultCardItemTypeHandlers.create(viewModel = viewModel)
|
||||||
},
|
},
|
||||||
|
vaultSshKeyItemTypeHandlers = remember(viewModel) {
|
||||||
|
VaultSshKeyItemTypeHandlers.create(viewModel = viewModel)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,6 +346,7 @@ private fun VaultItemContent(
|
||||||
vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers,
|
vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers,
|
||||||
vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers,
|
vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers,
|
||||||
vaultCardItemTypeHandlers: VaultCardItemTypeHandlers,
|
vaultCardItemTypeHandlers: VaultCardItemTypeHandlers,
|
||||||
|
vaultSshKeyItemTypeHandlers: VaultSshKeyItemTypeHandlers,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
when (viewState) {
|
when (viewState) {
|
||||||
|
@ -389,6 +394,15 @@ private fun VaultItemContent(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultItemState.ViewState.Content.ItemType.SshKey -> {
|
||||||
|
VaultItemSshKeyContent(
|
||||||
|
commonState = viewState.common,
|
||||||
|
sshKeyItemState = viewState.type,
|
||||||
|
vaultSshKeyItemTypeHandlers = vaultSshKeyItemTypeHandlers,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.item
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultSshKeyItemTypeHandlers
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VaultItemSshKeyContent(
|
||||||
|
commonState: VaultItemState.ViewState.Content.Common,
|
||||||
|
sshKeyItemState: VaultItemState.ViewState.Content.ItemType.SshKey,
|
||||||
|
vaultSshKeyItemTypeHandlers: VaultSshKeyItemTypeHandlers,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
LazyColumn(modifier = modifier) {
|
||||||
|
item {
|
||||||
|
BitwardenListHeaderText(
|
||||||
|
label = stringResource(id = R.string.item_information),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenTextField(
|
||||||
|
label = stringResource(id = R.string.name),
|
||||||
|
value = commonState.name,
|
||||||
|
onValueChange = { },
|
||||||
|
readOnly = true,
|
||||||
|
singleLine = false,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag("SshKeyItemNameEntry")
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sshKeyItemState.publicKey?.let { publicKey ->
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenPasswordField(
|
||||||
|
label = stringResource(id = R.string.public_key),
|
||||||
|
value = publicKey,
|
||||||
|
onValueChange = { },
|
||||||
|
singleLine = false,
|
||||||
|
readOnly = true,
|
||||||
|
showPassword = sshKeyItemState.showPublicKey,
|
||||||
|
showPasswordTestTag = "ViewPublicKeyButton",
|
||||||
|
showPasswordChange = vaultSshKeyItemTypeHandlers.onShowPublicKeyClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sshKeyItemState.privateKey?.let { privateKey ->
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenPasswordField(
|
||||||
|
label = stringResource(id = R.string.private_key),
|
||||||
|
value = privateKey,
|
||||||
|
onValueChange = { },
|
||||||
|
singleLine = false,
|
||||||
|
readOnly = true,
|
||||||
|
showPassword = sshKeyItemState.showPrivateKey,
|
||||||
|
showPasswordTestTag = "ViewPrivateKeyButton",
|
||||||
|
showPasswordChange = vaultSshKeyItemTypeHandlers.onShowPrivateKeyClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sshKeyItemState.fingerprint?.let { fingerprint ->
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
BitwardenPasswordField(
|
||||||
|
label = stringResource(id = R.string.fingerprint),
|
||||||
|
value = fingerprint,
|
||||||
|
onValueChange = { },
|
||||||
|
singleLine = false,
|
||||||
|
readOnly = true,
|
||||||
|
showPassword = sshKeyItemState.showFingerprint,
|
||||||
|
showPasswordTestTag = "ViewFingerprintButton",
|
||||||
|
showPasswordChange = vaultSshKeyItemTypeHandlers.onShowFingerprintClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -116,6 +116,7 @@ class VaultItemViewModel @Inject constructor(
|
||||||
when (action) {
|
when (action) {
|
||||||
is VaultItemAction.ItemType.Login -> handleLoginTypeActions(action)
|
is VaultItemAction.ItemType.Login -> handleLoginTypeActions(action)
|
||||||
is VaultItemAction.ItemType.Card -> handleCardTypeActions(action)
|
is VaultItemAction.ItemType.Card -> handleCardTypeActions(action)
|
||||||
|
is VaultItemAction.ItemType.SshKey -> handleSshKeyTypeActions(action)
|
||||||
is VaultItemAction.Common -> handleCommonActions(action)
|
is VaultItemAction.Common -> handleCommonActions(action)
|
||||||
is VaultItemAction.Internal -> handleInternalAction(action)
|
is VaultItemAction.Internal -> handleInternalAction(action)
|
||||||
}
|
}
|
||||||
|
@ -753,6 +754,64 @@ class VaultItemViewModel @Inject constructor(
|
||||||
|
|
||||||
//endregion Card Type Handlers
|
//endregion Card Type Handlers
|
||||||
|
|
||||||
|
//region SSH Key Type Handlers
|
||||||
|
|
||||||
|
private fun handleSshKeyTypeActions(action: VaultItemAction.ItemType.SshKey) {
|
||||||
|
when (action) {
|
||||||
|
is VaultItemAction.ItemType.SshKey.PublicKeyVisibilityClicked -> {
|
||||||
|
handlePublicKeyVisibilityClicked(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked -> {
|
||||||
|
handlePrivateKeyVisibilityClicked(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VaultItemAction.ItemType.SshKey.FingerprintVisibilityClicked -> {
|
||||||
|
handleFingerprintVisibilityClicked(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePublicKeyVisibilityClicked(action: VaultItemAction.ItemType.SshKey.PublicKeyVisibilityClicked) {
|
||||||
|
onSshKeyContent { content, sshKey ->
|
||||||
|
mutableStateFlow.update { currentState ->
|
||||||
|
currentState.copy(
|
||||||
|
viewState = content.copy(
|
||||||
|
type = sshKey.copy(showPublicKey = action.isVisible),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePrivateKeyVisibilityClicked(action: VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked) {
|
||||||
|
onSshKeyContent { content, sshKey ->
|
||||||
|
mutableStateFlow.update { currentState ->
|
||||||
|
currentState.copy(
|
||||||
|
viewState = content.copy(
|
||||||
|
type = sshKey.copy(showPrivateKey = action.isVisible),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFingerprintVisibilityClicked(
|
||||||
|
action: VaultItemAction.ItemType.SshKey.FingerprintVisibilityClicked,
|
||||||
|
) {
|
||||||
|
onSshKeyContent { content, sshKey ->
|
||||||
|
mutableStateFlow.update { currentState ->
|
||||||
|
currentState.copy(
|
||||||
|
viewState = content.copy(
|
||||||
|
type = sshKey.copy(showFingerprint = action.isVisible),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion SSH Key Type Handlers
|
||||||
|
|
||||||
//region Internal Type Handlers
|
//region Internal Type Handlers
|
||||||
|
|
||||||
private fun handleInternalAction(action: VaultItemAction.Internal) {
|
private fun handleInternalAction(action: VaultItemAction.Internal) {
|
||||||
|
@ -1057,6 +1116,21 @@ class VaultItemViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun onSshKeyContent(
|
||||||
|
crossinline block: (
|
||||||
|
VaultItemState.ViewState.Content,
|
||||||
|
VaultItemState.ViewState.Content.ItemType.SshKey,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
state.viewState.asContentOrNull()
|
||||||
|
?.let { content ->
|
||||||
|
(content.type as? VaultItemState.ViewState.Content.ItemType.SshKey)
|
||||||
|
?.let { sshKeyContent ->
|
||||||
|
block(content, sshKeyContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1359,6 +1433,24 @@ data class VaultItemState(
|
||||||
val isVisible: Boolean,
|
val isVisible: Boolean,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the data for displaying an `SSHKey` item type.
|
||||||
|
*
|
||||||
|
* @property name The name of the key.
|
||||||
|
* @property publicKey The SSH public key.
|
||||||
|
* @property privateKey The SSH private key.
|
||||||
|
* @property fingerprint The fingerprint of the key.
|
||||||
|
*/
|
||||||
|
data class SshKey(
|
||||||
|
val name: String?,
|
||||||
|
val publicKey: String?,
|
||||||
|
val privateKey: String?,
|
||||||
|
val fingerprint: String?,
|
||||||
|
val showPublicKey: Boolean,
|
||||||
|
val showPrivateKey: Boolean,
|
||||||
|
val showFingerprint: Boolean,
|
||||||
|
) : ItemType()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1697,6 +1789,12 @@ sealed class VaultItemAction {
|
||||||
*/
|
*/
|
||||||
data class NumberVisibilityClick(val isVisible: Boolean) : Card()
|
data class NumberVisibilityClick(val isVisible: Boolean) : Card()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class SshKey : ItemType() {
|
||||||
|
data class PublicKeyVisibilityClicked(val isVisible: Boolean) : SshKey()
|
||||||
|
data class PrivateKeyVisibilityClicked(val isVisible: Boolean) : SshKey()
|
||||||
|
data class FingerprintVisibilityClicked(val isVisible: Boolean) : SshKey()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.item.handlers
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemAction
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemViewModel
|
||||||
|
|
||||||
|
data class VaultSshKeyItemTypeHandlers(
|
||||||
|
val onShowPublicKeyClick: (isVisible: Boolean) -> Unit,
|
||||||
|
val onShowPrivateKeyClick: (isVisible: Boolean) -> Unit,
|
||||||
|
val onShowFingerprintClick: (isVisible: Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(viewModel: VaultItemViewModel): VaultSshKeyItemTypeHandlers =
|
||||||
|
VaultSshKeyItemTypeHandlers(
|
||||||
|
onShowPublicKeyClick = {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultItemAction.ItemType.SshKey.PublicKeyVisibilityClicked(
|
||||||
|
isVisible = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onShowPrivateKeyClick = {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked(
|
||||||
|
isVisible = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onShowFingerprintClick = {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultItemAction.ItemType.SshKey.FingerprintVisibilityClicked(
|
||||||
|
isVisible = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -156,6 +156,25 @@ fun CipherView.toViewState(
|
||||||
address = identity?.identityAddress,
|
address = identity?.identityAddress,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CipherType.SSH_KEY -> {
|
||||||
|
val sshKeyValues = requireNotNull(sshKey)
|
||||||
|
VaultItemState.ViewState.Content.ItemType.SshKey(
|
||||||
|
name = name,
|
||||||
|
publicKey = sshKeyValues.publicKey,
|
||||||
|
privateKey = sshKeyValues.privateKey,
|
||||||
|
fingerprint = sshKeyValues.fingerprint,
|
||||||
|
showPublicKey = (previousState?.type as?
|
||||||
|
VaultItemState.ViewState.Content.ItemType.SshKey)
|
||||||
|
?.showPublicKey == true,
|
||||||
|
showPrivateKey = (previousState?.type as?
|
||||||
|
VaultItemState.ViewState.Content.ItemType.SshKey)
|
||||||
|
?.showPrivateKey == true,
|
||||||
|
showFingerprint = (previousState?.type as?
|
||||||
|
VaultItemState.ViewState.Content.ItemType.SshKey)
|
||||||
|
?.showFingerprint == true,
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -369,6 +369,7 @@ private fun CipherView.toIconTestTag(): String =
|
||||||
CipherType.SECURE_NOTE -> "SecureNoteCipherIcon"
|
CipherType.SECURE_NOTE -> "SecureNoteCipherIcon"
|
||||||
CipherType.CARD -> "CardCipherIcon"
|
CipherType.CARD -> "CardCipherIcon"
|
||||||
CipherType.IDENTITY -> "IdentityCipherIcon"
|
CipherType.IDENTITY -> "IdentityCipherIcon"
|
||||||
|
CipherType.SSH_KEY -> "SshKeyCipherIcon"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CipherView.toIconData(
|
private fun CipherView.toIconData(
|
||||||
|
@ -425,4 +426,5 @@ private val CipherType.iconRes: Int
|
||||||
CipherType.SECURE_NOTE -> R.drawable.ic_note
|
CipherType.SECURE_NOTE -> R.drawable.ic_note
|
||||||
CipherType.CARD -> R.drawable.ic_payment_card
|
CipherType.CARD -> R.drawable.ic_payment_card
|
||||||
CipherType.IDENTITY -> R.drawable.ic_id_card
|
CipherType.IDENTITY -> R.drawable.ic_id_card
|
||||||
|
CipherType.SSH_KEY -> R.drawable.ic_ssh_key
|
||||||
}
|
}
|
||||||
|
|
|
@ -927,6 +927,30 @@ data class VaultState(
|
||||||
) : VaultItem() {
|
) : VaultItem() {
|
||||||
override val supportingLabel: Text? get() = null
|
override val supportingLabel: Text? get() = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a SSH key item within the vault, designed to store SSH keys.
|
||||||
|
*
|
||||||
|
* @property publicKey The public key associated with this SSH key item.
|
||||||
|
* @property privateKey The private key associated with this SSH key item.
|
||||||
|
* @property fingerprint The fingerprint associated with this SSH key item.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class SshKey(
|
||||||
|
override val id: String,
|
||||||
|
override val name: Text,
|
||||||
|
override val startIcon: IconData = IconData.Local(R.drawable.ic_ssh_key),
|
||||||
|
override val startIconTestTag: String = "SshKeyCipherIcon",
|
||||||
|
override val extraIconList: List<IconRes> = emptyList(),
|
||||||
|
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
|
||||||
|
override val shouldShowMasterPasswordReprompt: Boolean,
|
||||||
|
val publicKey: Text?,
|
||||||
|
val privateKey: Text?,
|
||||||
|
val fingerprint: Text?,
|
||||||
|
) : VaultItem() {
|
||||||
|
override val supportingLabel: Text? get() = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.bitwarden.vault.LoginView
|
||||||
import com.bitwarden.vault.PasswordHistoryView
|
import com.bitwarden.vault.PasswordHistoryView
|
||||||
import com.bitwarden.vault.SecureNoteType
|
import com.bitwarden.vault.SecureNoteType
|
||||||
import com.bitwarden.vault.SecureNoteView
|
import com.bitwarden.vault.SecureNoteView
|
||||||
|
import com.bitwarden.vault.SshKeyView
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
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.VaultAddEditState
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||||
|
@ -49,6 +50,7 @@ fun VaultAddEditState.ViewState.Content.toCipherView(): CipherView =
|
||||||
secureNote = type.toSecureNotesView(),
|
secureNote = type.toSecureNotesView(),
|
||||||
login = type.toLoginView(common = common),
|
login = type.toLoginView(common = common),
|
||||||
card = type.toCardView(),
|
card = type.toCardView(),
|
||||||
|
sshKey = type.toSshKeyView(),
|
||||||
|
|
||||||
// Fields we always grab from the UI
|
// Fields we always grab from the UI
|
||||||
name = common.name,
|
name = common.name,
|
||||||
|
@ -66,6 +68,16 @@ private fun VaultAddEditState.ViewState.Content.ItemType.toCipherType(): CipherT
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.Identity -> CipherType.IDENTITY
|
is VaultAddEditState.ViewState.Content.ItemType.Identity -> CipherType.IDENTITY
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.Login -> CipherType.LOGIN
|
is VaultAddEditState.ViewState.Content.ItemType.Login -> CipherType.LOGIN
|
||||||
is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> CipherType.SECURE_NOTE
|
is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> CipherType.SECURE_NOTE
|
||||||
|
is VaultAddEditState.ViewState.Content.ItemType.SshKey -> CipherType.SSH_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun VaultAddEditState.ViewState.Content.ItemType.toSshKeyView(): SshKeyView? =
|
||||||
|
(this as? VaultAddEditState.ViewState.Content.ItemType.SshKey)?.let {
|
||||||
|
SshKeyView(
|
||||||
|
publicKey = it.publicKey.orNullIfBlank(),
|
||||||
|
privateKey = it.privateKey.orNullIfBlank(),
|
||||||
|
fingerprint = it.fingerprint.orNullIfBlank(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VaultAddEditState.ViewState.Content.ItemType.toCardView(): CardView? =
|
private fun VaultAddEditState.ViewState.Content.ItemType.toCardView(): CardView? =
|
||||||
|
|
|
@ -259,6 +259,20 @@ private fun CipherView.toVaultItemOrNull(
|
||||||
extraIconList = toLabelIcons(),
|
extraIconList = toLabelIcons(),
|
||||||
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CipherType.SSH_KEY -> VaultState.ViewState.VaultItem.SshKey(
|
||||||
|
id = id,
|
||||||
|
name = name.asText(),
|
||||||
|
publicKey = sshKey?.publicKey?.asText(),
|
||||||
|
privateKey = sshKey?.privateKey?.asText(),
|
||||||
|
fingerprint = sshKey?.fingerprint?.asText(),
|
||||||
|
overflowOptions = toOverflowActions(
|
||||||
|
hasMasterPassword = hasMasterPassword,
|
||||||
|
isPremiumUser = isPremiumUser,
|
||||||
|
),
|
||||||
|
extraIconList = toLabelIcons(),
|
||||||
|
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
app/src/main/res/drawable/ic_ssh_key.xml
Normal file
14
app/src/main/res/drawable/ic_ssh_key.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M14.838,11.797L15.439,11.916C16.826,12.191 18.316,11.79 19.387,10.719C21.096,9.01 21.096,6.24 19.387,4.531C17.679,2.823 14.909,2.823 13.2,4.531C12.128,5.603 11.728,7.092 12.003,8.48L12.136,9.153L4.375,16.574L3.601,19.67H6.024L6.649,17.17H8.524L9.149,14.67H11.533L14.838,11.797ZM12,15.92H10.125L9.5,18.42H7.625L7,20.92H2L3.25,15.92L10.776,8.723C10.424,6.943 10.937,5.027 12.316,3.648C14.513,1.451 18.075,1.451 20.271,3.648C22.468,5.844 22.468,9.406 20.271,11.602C18.892,12.981 16.975,13.495 15.196,13.142L12,15.92Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M16.736,6.299C16.492,6.543 16.492,6.939 16.736,7.183C16.98,7.427 17.376,7.427 17.62,7.183C17.864,6.939 17.864,6.543 17.62,6.299C17.376,6.055 16.98,6.055 16.736,6.299ZM18.504,5.415C19.236,6.148 19.236,7.335 18.504,8.067C17.771,8.799 16.584,8.799 15.852,8.067C15.12,7.335 15.12,6.148 15.852,5.415C16.584,4.683 17.771,4.683 18.504,5.415Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
|
@ -1061,4 +1061,8 @@ Do you want to switch to this account?</string>
|
||||||
<string name="save_the_exported_file_highlight">Save the exported file</string>
|
<string name="save_the_exported_file_highlight">Save the exported file</string>
|
||||||
<string name="this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server">This is not a recognized Bitwarden server. You may need to check with your provider or update your server.</string>
|
<string name="this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server">This is not a recognized Bitwarden server. You may need to check with your provider or update your server.</string>
|
||||||
<string name="syncing_logins_loading_message">Syncing logins...</string>
|
<string name="syncing_logins_loading_message">Syncing logins...</string>
|
||||||
|
<string name="type_ssh_key">SSH Key</string>
|
||||||
|
<string name="public_key">Public key</string>
|
||||||
|
<string name="private_key">Private key</string>
|
||||||
|
<string name="ssh_key_cipher_item_types">SSH Key Cipher Item Types</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -24,7 +24,7 @@ androidxSplash = "1.1.0-rc01"
|
||||||
androidXAppCompat = "1.7.0"
|
androidXAppCompat = "1.7.0"
|
||||||
androdixAutofill = "1.1.0"
|
androdixAutofill = "1.1.0"
|
||||||
androidxWork = "2.9.1"
|
androidxWork = "2.9.1"
|
||||||
bitwardenSdk = "1.0.0-20240924.112512-21"
|
bitwardenSdk = "1.0.0-20241022.103047-2"
|
||||||
crashlytics = "3.0.2"
|
crashlytics = "3.0.2"
|
||||||
detekt = "1.23.7"
|
detekt = "1.23.7"
|
||||||
firebaseBom = "33.4.0"
|
firebaseBom = "33.4.0"
|
||||||
|
@ -83,7 +83,7 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "
|
||||||
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "androidXSecurityCrypto" }
|
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "androidXSecurityCrypto" }
|
||||||
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplash" }
|
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplash" }
|
||||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" }
|
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" }
|
||||||
bitwarden-sdk = { module = "com.bitwarden:sdk-android", version.ref = "bitwardenSdk" }
|
bitwarden-sdk = { module = "com.bitwarden:sdk-android-temp", version.ref = "bitwardenSdk" }
|
||||||
bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" }
|
bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" }
|
||||||
detekt-detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
|
detekt-detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
|
||||||
detekt-detekt-rules = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" }
|
detekt-detekt-rules = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" }
|
||||||
|
|
Loading…
Reference in a new issue