mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-541, 1370 Adding icon loading for login items (#654)
This commit is contained in:
parent
ff7a015472
commit
a87bcd28ff
22 changed files with 599 additions and 67 deletions
|
@ -6,6 +6,7 @@ import java.net.URI
|
||||||
|
|
||||||
private const val DEFAULT_WEB_VAULT_URL: String = "https://vault.bitwarden.com"
|
private const val DEFAULT_WEB_VAULT_URL: String = "https://vault.bitwarden.com"
|
||||||
private const val DEFAULT_WEB_SEND_URL: String = "https://send.bitwarden.com/#"
|
private const val DEFAULT_WEB_SEND_URL: String = "https://send.bitwarden.com/#"
|
||||||
|
private const val DEFAULT_ICON_URL: String = "https://icons.bitwarden.net/"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the base web vault URL. This will check for a custom [EnvironmentUrlDataJson.webVault]
|
* Returns the base web vault URL. This will check for a custom [EnvironmentUrlDataJson.webVault]
|
||||||
|
@ -37,6 +38,17 @@ val EnvironmentUrlDataJson.baseWebSendUrl: String
|
||||||
?.let { "$it/#/send/" }
|
?.let { "$it/#/send/" }
|
||||||
?: DEFAULT_WEB_SEND_URL
|
?: DEFAULT_WEB_SEND_URL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a base icon url based on the environment or the default value if values are missing.
|
||||||
|
*/
|
||||||
|
val EnvironmentUrlDataJson.baseIconUrl: String
|
||||||
|
get() =
|
||||||
|
this
|
||||||
|
.icon
|
||||||
|
.takeIf { !it.isNullOrBlank() }
|
||||||
|
?: base.takeIf { it.isNotBlank() }?.let { "$it/icons" }
|
||||||
|
?: DEFAULT_ICON_URL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the appropriate pre-defined labels for environments matching the known US/EU values.
|
* Returns the appropriate pre-defined labels for environments matching the known US/EU values.
|
||||||
* Otherwise returns the host of the custom base URL.
|
* Otherwise returns the host of the custom base URL.
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
||||||
|
import com.bumptech.glide.integration.compose.GlideImage
|
||||||
|
import com.bumptech.glide.integration.compose.placeholder
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Bitwarden icon that is either locally loaded or loaded using glide.
|
||||||
|
*
|
||||||
|
* @param iconData Label for the text field.
|
||||||
|
* @param tint the color to be applied as the tint for the icon.
|
||||||
|
* @param modifier A [Modifier] for the composable.
|
||||||
|
* @param contentDescription A description of the switch's UI for accessibility purposes.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalGlideComposeApi::class)
|
||||||
|
@Composable
|
||||||
|
fun BitwardenIcon(
|
||||||
|
iconData: IconData,
|
||||||
|
tint: Color,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
contentDescription: String? = null,
|
||||||
|
) {
|
||||||
|
when (iconData) {
|
||||||
|
is IconData.Network -> {
|
||||||
|
GlideImage(
|
||||||
|
model = iconData.uri,
|
||||||
|
failure = placeholder(iconData.fallbackIconRes),
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
it.placeholder(iconData.fallbackIconRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is IconData.Local -> {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = iconData.iconRes),
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
tint = tint,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import kotlinx.collections.immutable.PersistentList
|
import kotlinx.collections.immutable.PersistentList
|
||||||
|
@ -52,7 +53,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
@Composable
|
@Composable
|
||||||
fun BitwardenListItem(
|
fun BitwardenListItem(
|
||||||
label: String,
|
label: String,
|
||||||
startIcon: Painter,
|
startIcon: IconData,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
selectionDataList: PersistentList<SelectionItemData>,
|
selectionDataList: PersistentList<SelectionItemData>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -73,8 +74,8 @@ fun BitwardenListItem(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
Icon(
|
BitwardenIcon(
|
||||||
painter = startIcon,
|
iconData = startIcon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
|
@ -159,7 +160,7 @@ private fun BitwardenListItem_preview() {
|
||||||
BitwardenListItem(
|
BitwardenListItem(
|
||||||
label = "Sample Label",
|
label = "Sample Label",
|
||||||
supportingLabel = "Jan 3, 2024, 10:35 AM",
|
supportingLabel = "Jan 3, 2024, 10:35 AM",
|
||||||
startIcon = painterResource(id = R.drawable.ic_send_text),
|
startIcon = IconData.Local(R.drawable.ic_send_text),
|
||||||
onClick = {},
|
onClick = {},
|
||||||
selectionDataList = persistentListOf(),
|
selectionDataList = persistentListOf(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.components.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to denote the type of icon being passed.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
sealed class IconData : Parcelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class representing the resources required for an icon.
|
||||||
|
*
|
||||||
|
* @property iconRes the resource for the local icon.
|
||||||
|
*/
|
||||||
|
data class Local(
|
||||||
|
val iconRes: Int,
|
||||||
|
) : IconData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class representing the resources required for a network-based icon.
|
||||||
|
*
|
||||||
|
* @property uri the link for the icon.
|
||||||
|
* @property fallbackIconRes fallback resource if the image cannot be loaded.
|
||||||
|
*/
|
||||||
|
data class Network(
|
||||||
|
val uri: String,
|
||||||
|
val fallbackIconRes: Int,
|
||||||
|
) : IconData()
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenGroupItem
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenGroupItem
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderTextWithSupportLabel
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderTextWithSupportLabel
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.send.handlers.SendHandlers
|
import com.x8bit.bitwarden.ui.tools.feature.send.handlers.SendHandlers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +76,7 @@ fun SendContent(
|
||||||
|
|
||||||
items(state.sendItems) {
|
items(state.sendItems) {
|
||||||
SendListItem(
|
SendListItem(
|
||||||
startIcon = painterResource(id = it.type.iconRes),
|
startIcon = IconData.Local(it.type.iconRes),
|
||||||
label = it.name,
|
label = it.name,
|
||||||
supportingLabel = it.deletionDate,
|
supportingLabel = it.deletionDate,
|
||||||
trailingLabelIcons = it.iconList,
|
trailingLabelIcons = it.iconList,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.SelectionItemData
|
import com.x8bit.bitwarden.ui.platform.components.SelectionItemData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
||||||
|
@ -40,7 +41,7 @@ import com.x8bit.bitwarden.ui.tools.feature.send.model.SendStatusIcon
|
||||||
fun SendListItem(
|
fun SendListItem(
|
||||||
label: String,
|
label: String,
|
||||||
supportingLabel: String,
|
supportingLabel: String,
|
||||||
startIcon: Painter,
|
startIcon: IconData,
|
||||||
trailingLabelIcons: List<SendStatusIcon>,
|
trailingLabelIcons: List<SendStatusIcon>,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onEditClick: () -> Unit,
|
onEditClick: () -> Unit,
|
||||||
|
@ -111,7 +112,7 @@ private fun SendListItem_preview() {
|
||||||
SendListItem(
|
SendListItem(
|
||||||
label = "Sample Label",
|
label = "Sample Label",
|
||||||
supportingLabel = "Jan 3, 2024, 10:35 AM",
|
supportingLabel = "Jan 3, 2024, 10:35 AM",
|
||||||
startIcon = painterResource(id = R.drawable.ic_send_text),
|
startIcon = IconData.Local(R.drawable.ic_send_text),
|
||||||
trailingLabelIcons = emptyList(),
|
trailingLabelIcons = emptyList(),
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onCopyClick = {},
|
onCopyClick = {},
|
||||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
@ -36,7 +35,7 @@ fun VaultItemListingContent(
|
||||||
}
|
}
|
||||||
items(state.displayItemList) {
|
items(state.displayItemList) {
|
||||||
VaultEntryListItem(
|
VaultEntryListItem(
|
||||||
startIcon = painterResource(id = it.iconRes),
|
startIcon = it.iconData,
|
||||||
label = it.title,
|
label = it.title,
|
||||||
supportingLabel = it.subtitle,
|
supportingLabel = it.subtitle,
|
||||||
onClick = { vaultItemClick(it.id) },
|
onClick = { vaultItemClick(it.id) },
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.determineListingPredicate
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.determineListingPredicate
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toViewState
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toViewState
|
||||||
|
@ -30,16 +33,25 @@ import javax.inject.Inject
|
||||||
class VaultItemListingViewModel @Inject constructor(
|
class VaultItemListingViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val vaultRepository: VaultRepository,
|
private val vaultRepository: VaultRepository,
|
||||||
|
private val environmentRepository: EnvironmentRepository,
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
|
) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
|
||||||
initialState = VaultItemListingState(
|
initialState = VaultItemListingState(
|
||||||
itemListingType = VaultItemListingArgs(savedStateHandle = savedStateHandle)
|
itemListingType = VaultItemListingArgs(savedStateHandle = savedStateHandle)
|
||||||
.vaultItemListingType
|
.vaultItemListingType
|
||||||
.toItemListingType(),
|
.toItemListingType(),
|
||||||
viewState = VaultItemListingState.ViewState.Loading,
|
viewState = VaultItemListingState.ViewState.Loading,
|
||||||
|
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
||||||
|
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
settingsRepository
|
||||||
|
.isIconLoadingDisabledFlow
|
||||||
|
.onEach { sendAction(VaultItemListingsAction.Internal.IconLoadingSettingReceive(it)) }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
vaultRepository
|
vaultRepository
|
||||||
.vaultDataStateFlow
|
.vaultDataStateFlow
|
||||||
.onEach { sendAction(VaultItemListingsAction.Internal.VaultDataReceive(it)) }
|
.onEach { sendAction(VaultItemListingsAction.Internal.VaultDataReceive(it)) }
|
||||||
|
@ -54,6 +66,8 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
is VaultItemListingsAction.AddVaultItemClick -> handleAddVaultItemClick()
|
is VaultItemListingsAction.AddVaultItemClick -> handleAddVaultItemClick()
|
||||||
is VaultItemListingsAction.RefreshClick -> handleRefreshClick()
|
is VaultItemListingsAction.RefreshClick -> handleRefreshClick()
|
||||||
is VaultItemListingsAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
is VaultItemListingsAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||||
|
is VaultItemListingsAction.Internal.IconLoadingSettingReceive ->
|
||||||
|
handleIconsSettingReceived(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +113,18 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
is DataState.Pending -> vaultPendingReceive(vaultData = vaultData)
|
is DataState.Pending -> vaultPendingReceive(vaultData = vaultData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleIconsSettingReceived(
|
||||||
|
action: VaultItemListingsAction.Internal.IconLoadingSettingReceive,
|
||||||
|
) {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(isIconLoadingDisabled = action.isIconLoadingDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
vaultRepository.vaultDataStateFlow.value.data?.let { vaultData ->
|
||||||
|
updateStateWithVaultData(vaultData)
|
||||||
|
}
|
||||||
|
}
|
||||||
//endregion VaultItemListing Handlers
|
//endregion VaultItemListing Handlers
|
||||||
|
|
||||||
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
||||||
|
@ -159,7 +185,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
.filter { cipherView ->
|
.filter { cipherView ->
|
||||||
cipherView.determineListingPredicate(currentState.itemListingType)
|
cipherView.determineListingPredicate(currentState.itemListingType)
|
||||||
}
|
}
|
||||||
.toViewState(),
|
.toViewState(
|
||||||
|
baseIconUrl = state.baseIconUrl,
|
||||||
|
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,6 +200,8 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
data class VaultItemListingState(
|
data class VaultItemListingState(
|
||||||
val itemListingType: ItemListingType,
|
val itemListingType: ItemListingType,
|
||||||
val viewState: ViewState,
|
val viewState: ViewState,
|
||||||
|
val baseIconUrl: String,
|
||||||
|
val isIconLoadingDisabled: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,16 +245,13 @@ data class VaultItemListingState(
|
||||||
* @property id the id of the item.
|
* @property id the id of the item.
|
||||||
* @property title title of the item.
|
* @property title title of the item.
|
||||||
* @property subtitle subtitle of the item (nullable).
|
* @property subtitle subtitle of the item (nullable).
|
||||||
* @property uri uri for the icon to be displayed (nullable).
|
* @property iconData data for the icon to be displayed (nullable).
|
||||||
* @property iconRes the icon to be displayed.
|
|
||||||
*/
|
*/
|
||||||
data class DisplayItem(
|
data class DisplayItem(
|
||||||
val id: String,
|
val id: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val subtitle: String?,
|
val subtitle: String?,
|
||||||
val uri: String?,
|
val iconData: IconData,
|
||||||
@DrawableRes
|
|
||||||
val iconRes: Int,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -399,6 +427,13 @@ sealed class VaultItemListingsAction {
|
||||||
*/
|
*/
|
||||||
sealed class Internal : VaultItemListingsAction() {
|
sealed class Internal : VaultItemListingsAction() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the icon setting was received.
|
||||||
|
*/
|
||||||
|
data class IconLoadingSettingReceive(
|
||||||
|
val isIconLoadingDisabled: Boolean,
|
||||||
|
) : Internal()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates vault data was received.
|
* Indicates vault data was received.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,7 +6,9 @@ import com.bitwarden.core.CipherView
|
||||||
import com.bitwarden.core.CollectionView
|
import com.bitwarden.core.CollectionView
|
||||||
import com.bitwarden.core.FolderView
|
import com.bitwarden.core.FolderView
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines a predicate to filter a list of [CipherView] based on the
|
* Determines a predicate to filter a list of [CipherView] based on the
|
||||||
|
@ -48,9 +50,17 @@ fun CipherView.determineListingPredicate(
|
||||||
/**
|
/**
|
||||||
* Transforms a list of [CipherView] into [VaultItemListingState.ViewState].
|
* Transforms a list of [CipherView] into [VaultItemListingState.ViewState].
|
||||||
*/
|
*/
|
||||||
fun List<CipherView>.toViewState(): VaultItemListingState.ViewState =
|
fun List<CipherView>.toViewState(
|
||||||
|
baseIconUrl: String,
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
|
): VaultItemListingState.ViewState =
|
||||||
if (isNotEmpty()) {
|
if (isNotEmpty()) {
|
||||||
VaultItemListingState.ViewState.Content(displayItemList = toDisplayItemList())
|
VaultItemListingState.ViewState.Content(
|
||||||
|
displayItemList = toDisplayItemList(
|
||||||
|
baseIconUrl = baseIconUrl,
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
VaultItemListingState.ViewState.NoItems
|
VaultItemListingState.ViewState.NoItems
|
||||||
}
|
}
|
||||||
|
@ -82,18 +92,49 @@ fun VaultItemListingState.ItemListingType.updateWithAdditionalDataIfNecessary(
|
||||||
is VaultItemListingState.ItemListingType.Trash -> this
|
is VaultItemListingState.ItemListingType.Trash -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<CipherView>.toDisplayItemList(): List<VaultItemListingState.DisplayItem> =
|
private fun List<CipherView>.toDisplayItemList(
|
||||||
this.map { it.toDisplayItem() }
|
baseIconUrl: String,
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
|
): List<VaultItemListingState.DisplayItem> =
|
||||||
|
this.map {
|
||||||
|
it.toDisplayItem(
|
||||||
|
baseIconUrl = baseIconUrl,
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun CipherView.toDisplayItem(): VaultItemListingState.DisplayItem =
|
private fun CipherView.toDisplayItem(
|
||||||
|
baseIconUrl: String,
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
|
): VaultItemListingState.DisplayItem =
|
||||||
VaultItemListingState.DisplayItem(
|
VaultItemListingState.DisplayItem(
|
||||||
id = id.orEmpty(),
|
id = id.orEmpty(),
|
||||||
title = name,
|
title = name,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
iconRes = type.iconRes,
|
iconData = this.toIconData(
|
||||||
uri = uri,
|
baseIconUrl = baseIconUrl,
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun CipherView.toIconData(
|
||||||
|
baseIconUrl: String,
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
|
): IconData {
|
||||||
|
return when (this.type) {
|
||||||
|
CipherType.LOGIN -> {
|
||||||
|
login?.uris.toLoginIconData(
|
||||||
|
baseIconUrl = baseIconUrl,
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
IconData.Local(iconRes = this.type.iconRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
private val CipherView.subtitle: String?
|
private val CipherView.subtitle: String?
|
||||||
get() = when (type) {
|
get() = when (type) {
|
||||||
|
@ -122,18 +163,3 @@ private val CipherType.iconRes: Int
|
||||||
CipherType.CARD -> R.drawable.ic_card_item
|
CipherType.CARD -> R.drawable.ic_card_item
|
||||||
CipherType.IDENTITY -> R.drawable.ic_identity_item
|
CipherType.IDENTITY -> R.drawable.ic_identity_item
|
||||||
}
|
}
|
||||||
|
|
||||||
private val CipherView.uri: String?
|
|
||||||
get() = when (type) {
|
|
||||||
CipherType.LOGIN -> {
|
|
||||||
login
|
|
||||||
?.uris
|
|
||||||
?.firstOrNull()
|
|
||||||
?.uri
|
|
||||||
.orEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
CipherType.SECURE_NOTE -> null
|
|
||||||
CipherType.CARD -> null
|
|
||||||
CipherType.IDENTITY -> null
|
|
||||||
}
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ fun VaultContent(
|
||||||
|
|
||||||
items(state.favoriteItems) { favoriteItem ->
|
items(state.favoriteItems) { favoriteItem ->
|
||||||
VaultEntryListItem(
|
VaultEntryListItem(
|
||||||
startIcon = painterResource(id = favoriteItem.startIcon),
|
startIcon = favoriteItem.startIcon,
|
||||||
label = favoriteItem.name(),
|
label = favoriteItem.name(),
|
||||||
supportingLabel = favoriteItem.supportingLabel?.invoke(),
|
supportingLabel = favoriteItem.supportingLabel?.invoke(),
|
||||||
onClick = { vaultItemClick(favoriteItem) },
|
onClick = { vaultItemClick(favoriteItem) },
|
||||||
|
@ -234,8 +234,9 @@ fun VaultContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(state.noFolderItems) { noFolderItem ->
|
items(state.noFolderItems) { noFolderItem ->
|
||||||
|
|
||||||
VaultEntryListItem(
|
VaultEntryListItem(
|
||||||
startIcon = painterResource(id = noFolderItem.startIcon),
|
startIcon = noFolderItem.startIcon,
|
||||||
label = noFolderItem.name(),
|
label = noFolderItem.name(),
|
||||||
supportingLabel = noFolderItem.supportingLabel?.invoke(),
|
supportingLabel = noFolderItem.supportingLabel?.invoke(),
|
||||||
onClick = { vaultItemClick(noFolderItem) },
|
onClick = { vaultItemClick(noFolderItem) },
|
||||||
|
|
|
@ -3,13 +3,12 @@ package com.x8bit.bitwarden.ui.vault.feature.vault
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem
|
||||||
import com.x8bit.bitwarden.ui.platform.components.SelectionItemData
|
import com.x8bit.bitwarden.ui.platform.components.SelectionItemData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun VaultEntryListItem(
|
fun VaultEntryListItem(
|
||||||
startIcon: Painter,
|
startIcon: IconData,
|
||||||
label: String,
|
label: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -54,7 +53,7 @@ fun VaultEntryListItem(
|
||||||
private fun VaultEntryListItem_preview() {
|
private fun VaultEntryListItem_preview() {
|
||||||
BitwardenTheme {
|
BitwardenTheme {
|
||||||
VaultEntryListItem(
|
VaultEntryListItem(
|
||||||
startIcon = painterResource(id = R.drawable.ic_login_item),
|
startIcon = IconData.Local(R.drawable.ic_login_item),
|
||||||
label = "Example Login",
|
label = "Example Login",
|
||||||
supportingLabel = "Username",
|
supportingLabel = "Username",
|
||||||
onClick = {},
|
onClick = {},
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.vault
|
package com.x8bit.bitwarden.ui.vault.feature.vault
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
@ -10,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
@ -18,6 +18,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.hexToColor
|
import com.x8bit.bitwarden.ui.platform.base.util.hexToColor
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials
|
||||||
|
@ -43,8 +44,8 @@ import javax.inject.Inject
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class VaultViewModel @Inject constructor(
|
class VaultViewModel @Inject constructor(
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository,
|
||||||
private val settingsRepository: SettingsRepository,
|
|
||||||
private val vaultRepository: VaultRepository,
|
private val vaultRepository: VaultRepository,
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
||||||
initialState = run {
|
initialState = run {
|
||||||
val userState = requireNotNull(authRepository.userStateFlow.value)
|
val userState = requireNotNull(authRepository.userStateFlow.value)
|
||||||
|
@ -59,8 +60,10 @@ class VaultViewModel @Inject constructor(
|
||||||
accountSummaries = accountSummaries,
|
accountSummaries = accountSummaries,
|
||||||
vaultFilterData = vaultFilterData,
|
vaultFilterData = vaultFilterData,
|
||||||
viewState = VaultState.ViewState.Loading,
|
viewState = VaultState.ViewState.Loading,
|
||||||
|
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||||
isPremium = userState.activeAccount.isPremium,
|
isPremium = userState.activeAccount.isPremium,
|
||||||
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
|
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
|
||||||
|
baseIconUrl = userState.activeAccount.environment.environmentUrlData.baseIconUrl,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -77,6 +80,12 @@ class VaultViewModel @Inject constructor(
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
settingsRepository
|
||||||
|
.isIconLoadingDisabledFlow
|
||||||
|
.map { VaultAction.Internal.IconLoadingSettingReceive(it) }
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
vaultRepository
|
vaultRepository
|
||||||
.vaultDataStateFlow
|
.vaultDataStateFlow
|
||||||
.onEach { sendAction(VaultAction.Internal.VaultDataReceive(vaultData = it)) }
|
.onEach { sendAction(VaultAction.Internal.VaultDataReceive(vaultData = it)) }
|
||||||
|
@ -118,6 +127,16 @@ class VaultViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleIconLoadingSettingReceive(
|
||||||
|
action: VaultAction.Internal.IconLoadingSettingReceive,
|
||||||
|
) {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(isIconLoadingDisabled = action.isIconLoadingDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateViewState(vaultRepository.vaultDataStateFlow.value)
|
||||||
|
}
|
||||||
|
|
||||||
//region VaultAction Handlers
|
//region VaultAction Handlers
|
||||||
private fun handleAddItemClick() {
|
private fun handleAddItemClick() {
|
||||||
sendEvent(VaultEvent.NavigateToAddItemScreen)
|
sendEvent(VaultEvent.NavigateToAddItemScreen)
|
||||||
|
@ -252,6 +271,9 @@ class VaultViewModel @Inject constructor(
|
||||||
|
|
||||||
is VaultAction.Internal.UserStateUpdateReceive -> handleUserStateUpdateReceive(action)
|
is VaultAction.Internal.UserStateUpdateReceive -> handleUserStateUpdateReceive(action)
|
||||||
is VaultAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
is VaultAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||||
|
is VaultAction.Internal.IconLoadingSettingReceive -> handleIconLoadingSettingReceive(
|
||||||
|
action,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,8 +330,10 @@ class VaultViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
||||||
mutableStateFlow.updateToErrorStateOrDialog(
|
mutableStateFlow.updateToErrorStateOrDialog(
|
||||||
|
baseIconUrl = state.baseIconUrl,
|
||||||
vaultData = vaultData.data,
|
vaultData = vaultData.data,
|
||||||
vaultFilterType = vaultFilterTypeOrDefault,
|
vaultFilterType = vaultFilterTypeOrDefault,
|
||||||
|
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||||
isPremium = state.isPremium,
|
isPremium = state.isPremium,
|
||||||
errorTitle = R.string.an_error_has_occurred.asText(),
|
errorTitle = R.string.an_error_has_occurred.asText(),
|
||||||
errorMessage = R.string.generic_error_message.asText(),
|
errorMessage = R.string.generic_error_message.asText(),
|
||||||
|
@ -328,6 +352,8 @@ class VaultViewModel @Inject constructor(
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = vaultData.data.toViewState(
|
viewState = vaultData.data.toViewState(
|
||||||
|
baseIconUrl = state.baseIconUrl,
|
||||||
|
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||||
isPremium = state.isPremium,
|
isPremium = state.isPremium,
|
||||||
vaultFilterType = vaultFilterTypeOrDefault,
|
vaultFilterType = vaultFilterTypeOrDefault,
|
||||||
),
|
),
|
||||||
|
@ -343,10 +369,12 @@ class VaultViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun vaultNoNetworkReceive(vaultData: DataState.NoNetwork<VaultData>) {
|
private fun vaultNoNetworkReceive(vaultData: DataState.NoNetwork<VaultData>) {
|
||||||
mutableStateFlow.updateToErrorStateOrDialog(
|
mutableStateFlow.updateToErrorStateOrDialog(
|
||||||
|
baseIconUrl = state.baseIconUrl,
|
||||||
vaultData = vaultData.data,
|
vaultData = vaultData.data,
|
||||||
vaultFilterType = vaultFilterTypeOrDefault,
|
vaultFilterType = vaultFilterTypeOrDefault,
|
||||||
isPremium = state.isPremium,
|
isPremium = state.isPremium,
|
||||||
errorTitle = R.string.internet_connection_required_title.asText(),
|
errorTitle = R.string.internet_connection_required_title.asText(),
|
||||||
|
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||||
errorMessage = R.string.internet_connection_required_message.asText(),
|
errorMessage = R.string.internet_connection_required_message.asText(),
|
||||||
)
|
)
|
||||||
sendEvent(VaultEvent.DismissPullToRefresh)
|
sendEvent(VaultEvent.DismissPullToRefresh)
|
||||||
|
@ -357,6 +385,8 @@ class VaultViewModel @Inject constructor(
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = vaultData.data.toViewState(
|
viewState = vaultData.data.toViewState(
|
||||||
|
baseIconUrl = state.baseIconUrl,
|
||||||
|
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||||
isPremium = state.isPremium,
|
isPremium = state.isPremium,
|
||||||
vaultFilterType = vaultFilterTypeOrDefault,
|
vaultFilterType = vaultFilterTypeOrDefault,
|
||||||
),
|
),
|
||||||
|
@ -391,6 +421,8 @@ data class VaultState(
|
||||||
val isSwitchingAccounts: Boolean = false,
|
val isSwitchingAccounts: Boolean = false,
|
||||||
val isPremium: Boolean,
|
val isPremium: Boolean,
|
||||||
private val isPullToRefreshSettingEnabled: Boolean,
|
private val isPullToRefreshSettingEnabled: Boolean,
|
||||||
|
val baseIconUrl: String,
|
||||||
|
val isIconLoadingDisabled: Boolean,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -536,8 +568,7 @@ data class VaultState(
|
||||||
*/
|
*/
|
||||||
abstract val name: Text
|
abstract val name: Text
|
||||||
|
|
||||||
@get:DrawableRes
|
abstract val startIcon: IconData
|
||||||
abstract val startIcon: Int
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An optional supporting label for the vault item that provides additional information.
|
* An optional supporting label for the vault item that provides additional information.
|
||||||
|
@ -555,9 +586,9 @@ data class VaultState(
|
||||||
data class Login(
|
data class Login(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
override val name: Text,
|
override val name: Text,
|
||||||
|
override val startIcon: IconData = IconData.Local(R.drawable.ic_login_item),
|
||||||
val username: Text?,
|
val username: Text?,
|
||||||
) : VaultItem() {
|
) : VaultItem() {
|
||||||
override val startIcon: Int get() = R.drawable.ic_login_item
|
|
||||||
override val supportingLabel: Text? get() = username
|
override val supportingLabel: Text? get() = username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,10 +602,10 @@ data class VaultState(
|
||||||
data class Card(
|
data class Card(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
override val name: Text,
|
override val name: Text,
|
||||||
|
override val startIcon: IconData = IconData.Local(R.drawable.ic_card_item),
|
||||||
val brand: Text? = null,
|
val brand: Text? = null,
|
||||||
val lastFourDigits: Text? = null,
|
val lastFourDigits: Text? = null,
|
||||||
) : VaultItem() {
|
) : VaultItem() {
|
||||||
override val startIcon: Int get() = R.drawable.ic_card_item
|
|
||||||
override val supportingLabel: Text?
|
override val supportingLabel: Text?
|
||||||
get() = when {
|
get() = when {
|
||||||
brand != null && lastFourDigits != null -> brand
|
brand != null && lastFourDigits != null -> brand
|
||||||
|
@ -597,9 +628,9 @@ data class VaultState(
|
||||||
data class Identity(
|
data class Identity(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
override val name: Text,
|
override val name: Text,
|
||||||
|
override val startIcon: IconData = IconData.Local(R.drawable.ic_identity_item),
|
||||||
val firstName: Text?,
|
val firstName: Text?,
|
||||||
) : VaultItem() {
|
) : VaultItem() {
|
||||||
override val startIcon: Int get() = R.drawable.ic_identity_item
|
|
||||||
override val supportingLabel: Text? get() = firstName
|
override val supportingLabel: Text? get() = firstName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,8 +642,8 @@ data class VaultState(
|
||||||
data class SecureNote(
|
data class SecureNote(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
override val name: Text,
|
override val name: Text,
|
||||||
|
override val startIcon: IconData = IconData.Local(R.drawable.ic_secure_note_item),
|
||||||
) : VaultItem() {
|
) : VaultItem() {
|
||||||
override val startIcon: Int get() = R.drawable.ic_secure_note_item
|
|
||||||
override val supportingLabel: Text? get() = null
|
override val supportingLabel: Text? get() = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -841,6 +872,13 @@ sealed class VaultAction {
|
||||||
*/
|
*/
|
||||||
sealed class Internal : VaultAction() {
|
sealed class Internal : VaultAction() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the icon loading setting has been changed.
|
||||||
|
*/
|
||||||
|
data class IconLoadingSettingReceive(
|
||||||
|
val isIconLoadingDisabled: Boolean,
|
||||||
|
) : Internal()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the pull to refresh feature toggle has changed.
|
* Indicates that the pull to refresh feature toggle has changed.
|
||||||
*/
|
*/
|
||||||
|
@ -862,9 +900,12 @@ sealed class VaultAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
|
private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
|
||||||
|
baseIconUrl: String,
|
||||||
vaultData: VaultData?,
|
vaultData: VaultData?,
|
||||||
vaultFilterType: VaultFilterType,
|
vaultFilterType: VaultFilterType,
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
isPremium: Boolean,
|
isPremium: Boolean,
|
||||||
errorTitle: Text,
|
errorTitle: Text,
|
||||||
errorMessage: Text,
|
errorMessage: Text,
|
||||||
|
@ -873,8 +914,10 @@ private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
|
||||||
if (vaultData != null) {
|
if (vaultData != null) {
|
||||||
it.copy(
|
it.copy(
|
||||||
viewState = vaultData.toViewState(
|
viewState = vaultData.toViewState(
|
||||||
|
baseIconUrl = baseIconUrl,
|
||||||
isPremium = isPremium,
|
isPremium = isPremium,
|
||||||
vaultFilterType = vaultFilterType,
|
vaultFilterType = vaultFilterType,
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
),
|
),
|
||||||
dialog = VaultState.DialogState.Error(
|
dialog = VaultState.DialogState.Error(
|
||||||
title = errorTitle,
|
title = errorTitle,
|
||||||
|
|
|
@ -1,19 +1,28 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import com.bitwarden.core.CipherType
|
import com.bitwarden.core.CipherType
|
||||||
import com.bitwarden.core.CipherView
|
import com.bitwarden.core.CipherView
|
||||||
import com.bitwarden.core.CollectionView
|
import com.bitwarden.core.CollectionView
|
||||||
import com.bitwarden.core.FolderView
|
import com.bitwarden.core.FolderView
|
||||||
|
import com.bitwarden.core.LoginUriView
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
|
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
|
|
||||||
|
private const val ANDROID_URI = "androidapp://"
|
||||||
|
private const val IOS_URI = "iosapp://"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms [VaultData] into [VaultState.ViewState] using the given [vaultFilterType].
|
* Transforms [VaultData] into [VaultState.ViewState] using the given [vaultFilterType].
|
||||||
*/
|
*/
|
||||||
fun VaultData.toViewState(
|
fun VaultData.toViewState(
|
||||||
isPremium: Boolean,
|
isPremium: Boolean,
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
|
baseIconUrl: String,
|
||||||
vaultFilterType: VaultFilterType,
|
vaultFilterType: VaultFilterType,
|
||||||
): VaultState.ViewState {
|
): VaultState.ViewState {
|
||||||
val filteredCipherViewList = cipherViewList.toFilteredList(vaultFilterType)
|
val filteredCipherViewList = cipherViewList.toFilteredList(vaultFilterType)
|
||||||
|
@ -36,7 +45,12 @@ fun VaultData.toViewState(
|
||||||
.count { it.type == CipherType.SECURE_NOTE },
|
.count { it.type == CipherType.SECURE_NOTE },
|
||||||
favoriteItems = filteredCipherViewList
|
favoriteItems = filteredCipherViewList
|
||||||
.filter { it.favorite }
|
.filter { it.favorite }
|
||||||
.mapNotNull { it.toVaultItemOrNull() },
|
.mapNotNull {
|
||||||
|
it.toVaultItemOrNull(
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
|
baseIconUrl = baseIconUrl,
|
||||||
|
)
|
||||||
|
},
|
||||||
folderItems = filteredFolderViewList.map { folderView ->
|
folderItems = filteredFolderViewList.map { folderView ->
|
||||||
VaultState.ViewState.FolderItem(
|
VaultState.ViewState.FolderItem(
|
||||||
id = folderView.id,
|
id = folderView.id,
|
||||||
|
@ -47,7 +61,12 @@ fun VaultData.toViewState(
|
||||||
},
|
},
|
||||||
noFolderItems = filteredCipherViewList
|
noFolderItems = filteredCipherViewList
|
||||||
.filter { it.folderId.isNullOrBlank() }
|
.filter { it.folderId.isNullOrBlank() }
|
||||||
.mapNotNull { it.toVaultItemOrNull() },
|
.mapNotNull {
|
||||||
|
it.toVaultItemOrNull(
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
|
baseIconUrl = baseIconUrl,
|
||||||
|
)
|
||||||
|
},
|
||||||
collectionItems = filteredCollectionViewList
|
collectionItems = filteredCollectionViewList
|
||||||
.filter { it.id != null }
|
.filter { it.id != null }
|
||||||
.map { collectionView ->
|
.map { collectionView ->
|
||||||
|
@ -67,17 +86,66 @@ fun VaultData.toViewState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to build the icon data for login item icons.
|
||||||
|
*/
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
fun List<LoginUriView>?.toLoginIconData(
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
|
baseIconUrl: String,
|
||||||
|
): IconData {
|
||||||
|
val localIconData = IconData.Local(R.drawable.ic_login_item)
|
||||||
|
|
||||||
|
var uri = this
|
||||||
|
?.map { it.uri }
|
||||||
|
?.firstOrNull { uri -> uri?.contains(".") == true }
|
||||||
|
?: return localIconData
|
||||||
|
|
||||||
|
if (uri.startsWith(ANDROID_URI)) {
|
||||||
|
return IconData.Local(R.drawable.ic_settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.startsWith(IOS_URI)) {
|
||||||
|
return IconData.Local(R.drawable.ic_settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIconLoadingDisabled) {
|
||||||
|
return localIconData
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri.contains("://")) {
|
||||||
|
uri = "http://$uri"
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconUri = Uri.parse(uri)
|
||||||
|
val hostname = iconUri.host
|
||||||
|
|
||||||
|
val url = "$baseIconUrl/$hostname/icon.png"
|
||||||
|
|
||||||
|
return IconData.Network(
|
||||||
|
uri = url,
|
||||||
|
fallbackIconRes = R.drawable.ic_login_item,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a [CipherView] into a [VaultState.ViewState.VaultItem].
|
* Transforms a [CipherView] into a [VaultState.ViewState.VaultItem].
|
||||||
*/
|
*/
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
private fun CipherView.toVaultItemOrNull(): VaultState.ViewState.VaultItem? {
|
private fun CipherView.toVaultItemOrNull(
|
||||||
|
isIconLoadingDisabled: Boolean,
|
||||||
|
baseIconUrl: String,
|
||||||
|
): VaultState.ViewState.VaultItem? {
|
||||||
val id = this.id ?: return null
|
val id = this.id ?: return null
|
||||||
return when (type) {
|
return when (type) {
|
||||||
CipherType.LOGIN -> VaultState.ViewState.VaultItem.Login(
|
CipherType.LOGIN -> VaultState.ViewState.VaultItem.Login(
|
||||||
id = id,
|
id = id,
|
||||||
name = name.asText(),
|
name = name.asText(),
|
||||||
username = login?.username?.asText(),
|
username = login?.username?.asText(),
|
||||||
|
startIcon = login?.uris.toLoginIconData(
|
||||||
|
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||||
|
baseIconUrl = baseIconUrl,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote(
|
CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote(
|
||||||
|
|
|
@ -170,6 +170,39 @@ class EnvironmentUrlsDataJsonExtensionsTest {
|
||||||
(null as EnvironmentUrlDataJson?).toEnvironmentUrlsOrDefault(),
|
(null as EnvironmentUrlDataJson?).toEnvironmentUrlsOrDefault(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toIconBaseurl should return icon if value is present`() {
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.baseIconUrl,
|
||||||
|
"icon",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toIconBaseurl should return base value if icon is null`() {
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
|
||||||
|
.copy(icon = null)
|
||||||
|
.baseIconUrl,
|
||||||
|
"base/icons",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toIconBaseurl should return default url if base is empty and icon is null`() {
|
||||||
|
val expectedUrl = "https://icons.bitwarden.net/"
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
expectedUrl,
|
||||||
|
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
|
||||||
|
.copy(
|
||||||
|
base = "",
|
||||||
|
icon = null,
|
||||||
|
)
|
||||||
|
.baseIconUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA = EnvironmentUrlDataJson(
|
private val DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA = EnvironmentUrlDataJson(
|
||||||
|
|
|
@ -85,7 +85,7 @@ fun createMockLoginView(number: Int): LoginView =
|
||||||
*/
|
*/
|
||||||
fun createMockUriView(number: Int): LoginUriView =
|
fun createMockUriView(number: Int): LoginUriView =
|
||||||
LoginUriView(
|
LoginUriView(
|
||||||
uri = "mockUri-$number",
|
uri = "www.mockuri$number.com",
|
||||||
match = UriMatchType.HOST,
|
match = UriMatchType.HOST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,12 @@ import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performScrollToNode
|
import androidx.compose.ui.test.performScrollToNode
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -393,6 +396,8 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
private val DEFAULT_STATE = VaultItemListingState(
|
private val DEFAULT_STATE = VaultItemListingState(
|
||||||
itemListingType = VaultItemListingState.ItemListingType.Login,
|
itemListingType = VaultItemListingState.ItemListingType.Login,
|
||||||
viewState = VaultItemListingState.ViewState.Loading,
|
viewState = VaultItemListingState.ViewState.Loading,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createDisplayItem(number: Int): VaultItemListingState.DisplayItem =
|
private fun createDisplayItem(number: Int): VaultItemListingState.DisplayItem =
|
||||||
|
@ -400,6 +405,5 @@ private fun createDisplayItem(number: Int): VaultItemListingState.DisplayItem =
|
||||||
id = "mockId-$number",
|
id = "mockId-$number",
|
||||||
title = "mockTitle-$number",
|
title = "mockTitle-$number",
|
||||||
subtitle = "mockSubtitle-$number",
|
subtitle = "mockSubtitle-$number",
|
||||||
uri = "mockUri-$number",
|
iconData = IconData.Local(R.drawable.ic_card_item),
|
||||||
iconRes = R.drawable.ic_card_item,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||||
|
@ -17,10 +22,14 @@ import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockItemListi
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class VaultItemListingViewModelTest : BaseViewModelTest() {
|
class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
|
@ -31,6 +40,16 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
||||||
every { sync() } returns Unit
|
every { sync() } returns Unit
|
||||||
}
|
}
|
||||||
|
private val environmentRepository: EnvironmentRepository = mockk {
|
||||||
|
every { environment } returns Environment.Us
|
||||||
|
every { environmentStateFlow } returns mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mutableIsIconLoadingDisabledFlow = MutableStateFlow(false)
|
||||||
|
private val settingsRepository: SettingsRepository = mockk() {
|
||||||
|
every { isIconLoadingDisabled } returns false
|
||||||
|
every { isIconLoadingDisabledFlow } returns mutableIsIconLoadingDisabledFlow
|
||||||
|
}
|
||||||
private val initialState = createVaultItemListingState()
|
private val initialState = createVaultItemListingState()
|
||||||
private val initialSavedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
private val initialSavedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
||||||
vaultItemListingType = VaultItemListingType.Login,
|
vaultItemListingType = VaultItemListingType.Login,
|
||||||
|
@ -92,6 +111,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() =
|
fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() =
|
||||||
runTest {
|
runTest {
|
||||||
|
setupMockUri()
|
||||||
|
|
||||||
mutableVaultDataStateFlow.tryEmit(
|
mutableVaultDataStateFlow.tryEmit(
|
||||||
value = DataState.Loaded(
|
value = DataState.Loaded(
|
||||||
data = VaultData(
|
data = VaultData(
|
||||||
|
@ -178,6 +199,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `vaultDataStateFlow Pending with data should update state to Content`() = runTest {
|
fun `vaultDataStateFlow Pending with data should update state to Content`() = runTest {
|
||||||
|
setupMockUri()
|
||||||
|
|
||||||
mutableVaultDataStateFlow.tryEmit(
|
mutableVaultDataStateFlow.tryEmit(
|
||||||
value = DataState.Pending(
|
value = DataState.Pending(
|
||||||
data = VaultData(
|
data = VaultData(
|
||||||
|
@ -267,6 +290,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `vaultDataStateFlow Error with data should update state to Content`() = runTest {
|
fun `vaultDataStateFlow Error with data should update state to Content`() = runTest {
|
||||||
|
setupMockUri()
|
||||||
|
|
||||||
mutableVaultDataStateFlow.tryEmit(
|
mutableVaultDataStateFlow.tryEmit(
|
||||||
value = DataState.Error(
|
value = DataState.Error(
|
||||||
data = VaultData(
|
data = VaultData(
|
||||||
|
@ -291,6 +316,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unmockkStatic(Uri::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -363,6 +390,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `vaultDataStateFlow NoNetwork with data should update state to Content`() = runTest {
|
fun `vaultDataStateFlow NoNetwork with data should update state to Content`() = runTest {
|
||||||
|
setupMockUri()
|
||||||
|
|
||||||
mutableVaultDataStateFlow.tryEmit(
|
mutableVaultDataStateFlow.tryEmit(
|
||||||
value = DataState.NoNetwork(
|
value = DataState.NoNetwork(
|
||||||
data = VaultData(
|
data = VaultData(
|
||||||
|
@ -386,6 +415,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unmockkStatic(Uri::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -434,6 +465,16 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `icon loading state updates should update isIconLoadingDisabled`() = runTest {
|
||||||
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
|
assertFalse(viewModel.stateFlow.value.isIconLoadingDisabled)
|
||||||
|
|
||||||
|
mutableIsIconLoadingDisabledFlow.value = true
|
||||||
|
assertTrue(viewModel.stateFlow.value.isIconLoadingDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("CyclomaticComplexMethod")
|
@Suppress("CyclomaticComplexMethod")
|
||||||
private fun createSavedStateHandleWithVaultItemListingType(
|
private fun createSavedStateHandleWithVaultItemListingType(
|
||||||
vaultItemListingType: VaultItemListingType,
|
vaultItemListingType: VaultItemListingType,
|
||||||
|
@ -464,6 +505,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupMockUri() {
|
||||||
|
mockkStatic(Uri::class)
|
||||||
|
val uriMock = mockk<Uri>()
|
||||||
|
every { Uri.parse(any()) } returns uriMock
|
||||||
|
every { uriMock.host } returns "www.mockuri.com"
|
||||||
|
}
|
||||||
|
|
||||||
private fun createVaultItemListingViewModel(
|
private fun createVaultItemListingViewModel(
|
||||||
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
||||||
vaultRepository: VaultRepository = this.vaultRepository,
|
vaultRepository: VaultRepository = this.vaultRepository,
|
||||||
|
@ -471,6 +519,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
VaultItemListingViewModel(
|
VaultItemListingViewModel(
|
||||||
savedStateHandle = savedStateHandle,
|
savedStateHandle = savedStateHandle,
|
||||||
vaultRepository = vaultRepository,
|
vaultRepository = vaultRepository,
|
||||||
|
environmentRepository = environmentRepository,
|
||||||
|
settingsRepository = settingsRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
|
@ -481,5 +531,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
VaultItemListingState(
|
VaultItemListingState(
|
||||||
itemListingType = itemListingType,
|
itemListingType = itemListingType,
|
||||||
viewState = viewState,
|
viewState = viewState,
|
||||||
|
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
||||||
|
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
|
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import com.bitwarden.core.CipherType
|
import com.bitwarden.core.CipherType
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
@ -244,6 +251,11 @@ class VaultItemListingDataExtensionsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `toViewState should transform a list of CipherViews into a ViewState`() {
|
fun `toViewState should transform a list of CipherViews into a ViewState`() {
|
||||||
|
mockkStatic(Uri::class)
|
||||||
|
val uriMock = mockk<Uri>()
|
||||||
|
every { Uri.parse(any()) } returns uriMock
|
||||||
|
every { uriMock.host } returns "www.mockuri.com"
|
||||||
|
|
||||||
val cipherViewList = listOf(
|
val cipherViewList = listOf(
|
||||||
createMockCipherView(
|
createMockCipherView(
|
||||||
number = 1,
|
number = 1,
|
||||||
|
@ -267,7 +279,10 @@ class VaultItemListingDataExtensionsTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = cipherViewList.toViewState()
|
val result = cipherViewList.toViewState(
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingState.ViewState.Content(
|
VaultItemListingState.ViewState.Content(
|
||||||
|
@ -292,6 +307,8 @@ class VaultItemListingDataExtensionsTest {
|
||||||
),
|
),
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unmockkStatic(Uri::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
|
||||||
|
|
||||||
import com.bitwarden.core.CipherType
|
import com.bitwarden.core.CipherType
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,8 +18,10 @@ fun createMockItemListingDisplayItem(
|
||||||
id = "mockId-$number",
|
id = "mockId-$number",
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
subtitle = "mockUsername-$number",
|
subtitle = "mockUsername-$number",
|
||||||
iconRes = R.drawable.ic_login_item,
|
iconData = IconData.Network(
|
||||||
uri = "mockUri-$number",
|
"https://vault.bitwarden.com/icons/www.mockuri.com/icon.png",
|
||||||
|
fallbackIconRes = R.drawable.ic_login_item,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +30,7 @@ fun createMockItemListingDisplayItem(
|
||||||
id = "mockId-$number",
|
id = "mockId-$number",
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
subtitle = null,
|
subtitle = null,
|
||||||
iconRes = R.drawable.ic_secure_note_item,
|
iconData = IconData.Local(R.drawable.ic_secure_note_item),
|
||||||
uri = null,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +39,7 @@ fun createMockItemListingDisplayItem(
|
||||||
id = "mockId-$number",
|
id = "mockId-$number",
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
subtitle = "er-$number",
|
subtitle = "er-$number",
|
||||||
iconRes = R.drawable.ic_card_item,
|
iconData = IconData.Local(R.drawable.ic_card_item),
|
||||||
uri = null,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +48,7 @@ fun createMockItemListingDisplayItem(
|
||||||
id = "mockId-$number",
|
id = "mockId-$number",
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
subtitle = "mockFirstName-${number}mockLastName-$number",
|
subtitle = "mockFirstName-${number}mockLastName-$number",
|
||||||
iconRes = R.drawable.ic_identity_item,
|
iconData = IconData.Local(R.drawable.ic_identity_item),
|
||||||
uri = null,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performScrollToNode
|
import androidx.compose.ui.test.performScrollToNode
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
@ -1071,6 +1073,8 @@ private val DEFAULT_STATE: VaultState = VaultState(
|
||||||
viewState = VaultState.ViewState.Loading,
|
viewState = VaultState.ViewState.Loading,
|
||||||
isPremium = false,
|
isPremium = false,
|
||||||
isPullToRefreshSettingEnabled = false,
|
isPullToRefreshSettingEnabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultState.ViewState.Content(
|
private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultState.ViewState.Content(
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState.SpecialCircumsta
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||||
|
@ -31,12 +32,15 @@ import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class VaultViewModelTest : BaseViewModelTest() {
|
class VaultViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
private val mutablePullToRefreshEnabledFlow = MutableStateFlow(false)
|
private val mutablePullToRefreshEnabledFlow = MutableStateFlow(false)
|
||||||
|
private val mutableIsIconLoadingDisabledFlow = MutableStateFlow(false)
|
||||||
|
|
||||||
private val mutableUserStateFlow =
|
private val mutableUserStateFlow =
|
||||||
MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||||
|
@ -57,6 +61,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
private val settingsRepository: SettingsRepository = mockk {
|
private val settingsRepository: SettingsRepository = mockk {
|
||||||
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshEnabledFlow
|
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshEnabledFlow
|
||||||
|
every { isIconLoadingDisabledFlow } returns mutableIsIconLoadingDisabledFlow
|
||||||
|
every { isIconLoadingDisabled } returns false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val vaultRepository: VaultRepository =
|
private val vaultRepository: VaultRepository =
|
||||||
|
@ -357,6 +363,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||||
viewState = vaultData.toViewState(
|
viewState = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
vaultFilterType = VaultFilterType.AllVaults,
|
vaultFilterType = VaultFilterType.AllVaults,
|
||||||
|
isIconLoadingDisabled = viewModel.stateFlow.value.isIconLoadingDisabled,
|
||||||
|
baseIconUrl = viewModel.stateFlow.value.baseIconUrl,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.copy(
|
.copy(
|
||||||
|
@ -378,6 +386,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||||
viewState = vaultData.toViewState(
|
viewState = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
vaultFilterType = VaultFilterType.MyVault,
|
vaultFilterType = VaultFilterType.MyVault,
|
||||||
|
isIconLoadingDisabled = viewModel.stateFlow.value.isIconLoadingDisabled,
|
||||||
|
baseIconUrl = viewModel.stateFlow.value.baseIconUrl,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
|
@ -1032,6 +1042,16 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `icon loading state updates should update isIconLoadingDisabled`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
|
||||||
|
assertFalse(viewModel.stateFlow.value.isIconLoadingDisabled)
|
||||||
|
|
||||||
|
mutableIsIconLoadingDisabledFlow.value = true
|
||||||
|
assertTrue(viewModel.stateFlow.value.isIconLoadingDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createViewModel(): VaultViewModel =
|
private fun createViewModel(): VaultViewModel =
|
||||||
VaultViewModel(
|
VaultViewModel(
|
||||||
authRepository = authRepository,
|
authRepository = authRepository,
|
||||||
|
@ -1120,4 +1140,6 @@ private fun createMockVaultState(
|
||||||
isSwitchingAccounts = false,
|
isSwitchingAccounts = false,
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
isPullToRefreshSettingEnabled = false,
|
isPullToRefreshSettingEnabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.bitwarden.core.CipherType
|
||||||
|
import com.bitwarden.core.LoginUriView
|
||||||
|
import com.bitwarden.core.UriMatchType
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
|
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
@ -25,6 +37,8 @@ class VaultDataExtensionsTest {
|
||||||
|
|
||||||
val actual = vaultData.toViewState(
|
val actual = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
vaultFilterType = VaultFilterType.AllVaults,
|
vaultFilterType = VaultFilterType.AllVaults,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -72,6 +86,8 @@ class VaultDataExtensionsTest {
|
||||||
|
|
||||||
val actual = vaultData.toViewState(
|
val actual = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
vaultFilterType = VaultFilterType.MyVault,
|
vaultFilterType = VaultFilterType.MyVault,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,6 +132,8 @@ class VaultDataExtensionsTest {
|
||||||
|
|
||||||
val actual = vaultData.toViewState(
|
val actual = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
vaultFilterType = VaultFilterType.OrganizationVault(
|
vaultFilterType = VaultFilterType.OrganizationVault(
|
||||||
organizationId = "mockOrganizationId-1",
|
organizationId = "mockOrganizationId-1",
|
||||||
organizationName = "Mock Organization 1",
|
organizationName = "Mock Organization 1",
|
||||||
|
@ -156,6 +174,8 @@ class VaultDataExtensionsTest {
|
||||||
|
|
||||||
val actual = vaultData.toViewState(
|
val actual = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
vaultFilterType = VaultFilterType.AllVaults,
|
vaultFilterType = VaultFilterType.AllVaults,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -176,6 +196,8 @@ class VaultDataExtensionsTest {
|
||||||
|
|
||||||
val actual = vaultData.toViewState(
|
val actual = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
vaultFilterType = VaultFilterType.AllVaults,
|
vaultFilterType = VaultFilterType.AllVaults,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -197,6 +219,8 @@ class VaultDataExtensionsTest {
|
||||||
|
|
||||||
val actual = vaultData.toViewState(
|
val actual = vaultData.toViewState(
|
||||||
isPremium = true,
|
isPremium = true,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
vaultFilterType = VaultFilterType.AllVaults,
|
vaultFilterType = VaultFilterType.AllVaults,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -230,6 +254,8 @@ class VaultDataExtensionsTest {
|
||||||
val actual = vaultData.toViewState(
|
val actual = vaultData.toViewState(
|
||||||
isPremium = false,
|
isPremium = false,
|
||||||
vaultFilterType = VaultFilterType.AllVaults,
|
vaultFilterType = VaultFilterType.AllVaults,
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -248,4 +274,111 @@ class VaultDataExtensionsTest {
|
||||||
actual,
|
actual,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `toLoginIconData should return a IconData Local type if isIconLoadingDisabled is true`() {
|
||||||
|
val actual =
|
||||||
|
createMockCipherView(
|
||||||
|
number = 1,
|
||||||
|
cipherType = CipherType.LOGIN,
|
||||||
|
)
|
||||||
|
.login
|
||||||
|
?.uris
|
||||||
|
.toLoginIconData(
|
||||||
|
isIconLoadingDisabled = true,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = IconData.Local(iconRes = R.drawable.ic_login_item)
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `toLoginIconData should return a IconData Local type if no valid uris are found`() {
|
||||||
|
val actual = listOf(
|
||||||
|
LoginUriView(
|
||||||
|
uri = "",
|
||||||
|
match = UriMatchType.HOST,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toLoginIconData(
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = IconData.Local(iconRes = R.drawable.ic_login_item)
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `toLoginIconData should return a IconData Local type if an Android uri is detected`() {
|
||||||
|
val actual = listOf(
|
||||||
|
LoginUriView(
|
||||||
|
uri = "androidapp://test.com",
|
||||||
|
match = UriMatchType.HOST,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toLoginIconData(
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = IconData.Local(iconRes = R.drawable.ic_settings)
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `toLoginIconData should return a IconData Local type if an iOS uri is detected`() {
|
||||||
|
val actual = listOf(
|
||||||
|
LoginUriView(
|
||||||
|
uri = "iosapp://test.com",
|
||||||
|
match = UriMatchType.HOST,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toLoginIconData(
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = IconData.Local(iconRes = R.drawable.ic_settings)
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `toLoginIconData should return IconData Network type if isIconLoadingDisabled is false`() {
|
||||||
|
mockkStatic(Uri::class)
|
||||||
|
val uriMock = mockk<Uri>()
|
||||||
|
every { Uri.parse(any()) } returns uriMock
|
||||||
|
every { uriMock.host } returns "www.mockuri1.com"
|
||||||
|
|
||||||
|
val actual =
|
||||||
|
createMockCipherView(
|
||||||
|
number = 1,
|
||||||
|
cipherType = CipherType.LOGIN,
|
||||||
|
)
|
||||||
|
.login
|
||||||
|
?.uris
|
||||||
|
.toLoginIconData(
|
||||||
|
isIconLoadingDisabled = false,
|
||||||
|
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = IconData.Network(
|
||||||
|
uri = "https://vault.bitwarden.com/icons/www.mockuri1.com/icon.png",
|
||||||
|
fallbackIconRes = R.drawable.ic_login_item,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
|
||||||
|
unmockkStatic(Uri::class)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue