mirror of
https://github.com/bitwarden/android.git
synced 2026-06-08 08:06:32 -05:00
BIT-1054, BIT-1055: Adding modal generator UI and navigation from Add/Edit item (#643)
This commit is contained in:
committed by
Álison Fernandes
parent
61e914f8ac
commit
8d5bcc4433
@@ -13,8 +13,10 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassph
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
@@ -27,6 +29,16 @@ interface GeneratorRepository {
|
||||
*/
|
||||
val passwordHistoryStateFlow: StateFlow<LocalDataState<List<PasswordHistoryView>>>
|
||||
|
||||
/**
|
||||
* Flow that represents the modal generated text.
|
||||
*/
|
||||
val generatorResultFlow: Flow<GeneratorResult>
|
||||
|
||||
/**
|
||||
* Emits the modal generator result flow to listeners.
|
||||
*/
|
||||
fun emitGeneratorResult(generatorResult: GeneratorResult)
|
||||
|
||||
/**
|
||||
* Attempt to generate a password based on specifications in [passwordGeneratorRequest].
|
||||
* The [shouldSave] flag determines if the password is saved for future reference
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.bitwarden.core.UsernameGeneratorRequest
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.LocalDataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource
|
||||
@@ -21,6 +22,7 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassph
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
@@ -28,6 +30,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -51,12 +54,18 @@ class GeneratorRepositoryImpl(
|
||||
) : GeneratorRepository {
|
||||
|
||||
private val scope = CoroutineScope(dispatcherManager.io)
|
||||
|
||||
private val mutablePasswordHistoryStateFlow =
|
||||
MutableStateFlow<LocalDataState<List<PasswordHistoryView>>>(LocalDataState.Loading)
|
||||
|
||||
private val mutableGeneratorResultFlow = bufferedMutableSharedFlow<GeneratorResult>()
|
||||
|
||||
override val passwordHistoryStateFlow: StateFlow<LocalDataState<List<PasswordHistoryView>>>
|
||||
get() = mutablePasswordHistoryStateFlow.asStateFlow()
|
||||
|
||||
override val generatorResultFlow: Flow<GeneratorResult>
|
||||
get() = mutableGeneratorResultFlow.asSharedFlow()
|
||||
|
||||
init {
|
||||
mutablePasswordHistoryStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
@@ -90,6 +99,10 @@ class GeneratorRepositoryImpl(
|
||||
)
|
||||
}
|
||||
|
||||
override fun emitGeneratorResult(generatorResult: GeneratorResult) {
|
||||
mutableGeneratorResultFlow.tryEmit(generatorResult)
|
||||
}
|
||||
|
||||
override suspend fun generatePassword(
|
||||
passwordGeneratorRequest: PasswordGeneratorRequest,
|
||||
shouldSave: Boolean,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.x8bit.bitwarden.data.tools.generator.repository.model
|
||||
|
||||
/**
|
||||
* A result from the Generator.
|
||||
*/
|
||||
sealed class GeneratorResult {
|
||||
/**
|
||||
* A generated username.
|
||||
*/
|
||||
data class Username(val username: String) : GeneratorResult()
|
||||
|
||||
/**
|
||||
* A generated password.
|
||||
*/
|
||||
data class Password(val password: String) : GeneratorResult()
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -55,8 +56,10 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenStepper
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
@@ -72,6 +75,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Pa
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_LENGTH_SLIDER_MIN
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceTypeOption
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -101,6 +105,8 @@ fun GeneratorScreen(
|
||||
duration = SnackbarDuration.Short,
|
||||
)
|
||||
}
|
||||
|
||||
GeneratorEvent.NavigateBack -> onNavigateBack.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,31 +168,36 @@ fun GeneratorScreen(
|
||||
RandomWordHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
||||
val scrollBehavior =
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
|
||||
val scrollBehavior = when (state.generatorMode) {
|
||||
GeneratorMode.Default -> {
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
|
||||
}
|
||||
|
||||
else -> TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
}
|
||||
|
||||
BitwardenScaffold(
|
||||
topBar = {
|
||||
BitwardenMediumTopAppBar(
|
||||
title = stringResource(id = R.string.generator),
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
BitwardenOverflowActionItem(
|
||||
menuItemDataList = persistentListOf(
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.password_history),
|
||||
onClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction.PasswordHistoryClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
when (state.generatorMode) {
|
||||
GeneratorMode.Modal.Username, GeneratorMode.Modal.Password ->
|
||||
ModalAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
onCloseClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(GeneratorAction.CloseClick) }
|
||||
},
|
||||
onSelectClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(GeneratorAction.SelectClick) }
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
GeneratorMode.Default ->
|
||||
DefaultAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
onPasswordHistoryClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(GeneratorAction.PasswordHistoryClick) }
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = snackbarHostState)
|
||||
@@ -211,6 +222,54 @@ fun GeneratorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
//region Top App Bar Composables
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun DefaultAppBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
onPasswordHistoryClick: () -> Unit,
|
||||
) {
|
||||
BitwardenMediumTopAppBar(
|
||||
title = stringResource(id = R.string.generator),
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
BitwardenOverflowActionItem(
|
||||
menuItemDataList = persistentListOf(
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.password_history),
|
||||
onClick = onPasswordHistoryClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ModalAppBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
onCloseClick: () -> Unit,
|
||||
onSelectClick: () -> Unit,
|
||||
) {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = R.string.generator),
|
||||
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||
onNavigationIconClick = onCloseClick,
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.select),
|
||||
onClick = onSelectClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
//endregion Top App Bar Composables
|
||||
|
||||
//region ScrollContent and Static Items
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@@ -242,13 +301,14 @@ private fun ScrollContent(
|
||||
onRegenerateClick = onRegenerateClick,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
MainStateOptionsItem(
|
||||
selectedType = state.selectedType,
|
||||
possibleMainStates = state.typeOptions,
|
||||
onMainStateOptionClicked = onMainStateOptionClicked,
|
||||
)
|
||||
if (state.generatorMode == GeneratorMode.Default) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
MainStateOptionsItem(
|
||||
selectedType = state.selectedType,
|
||||
possibleMainStates = state.typeOptions,
|
||||
onMainStateOptionClicked = onMainStateOptionClicked,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassph
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
@@ -71,10 +72,12 @@ class GeneratorViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
) : BaseViewModel<GeneratorState, GeneratorEvent, GeneratorAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: GeneratorState(
|
||||
generatedText = PLACEHOLDER_GENERATED_TEXT,
|
||||
selectedType = Passcode(
|
||||
selectedType = Password(),
|
||||
),
|
||||
generatedText = "",
|
||||
selectedType = when (GeneratorArgs(savedStateHandle).type) {
|
||||
GeneratorMode.Modal.Username -> Username()
|
||||
GeneratorMode.Modal.Password -> Passcode()
|
||||
GeneratorMode.Default -> Passcode(selectedType = Password())
|
||||
},
|
||||
generatorMode = GeneratorArgs(savedStateHandle).type,
|
||||
currentEmailAddress =
|
||||
requireNotNull(authRepository.userStateFlow.value?.activeAccount?.email),
|
||||
@@ -100,6 +103,14 @@ class GeneratorViewModel @Inject constructor(
|
||||
handlePasswordHistoryClick()
|
||||
}
|
||||
|
||||
is GeneratorAction.CloseClick -> {
|
||||
handleCloseClick()
|
||||
}
|
||||
|
||||
is GeneratorAction.SelectClick -> {
|
||||
handleSelectClick()
|
||||
}
|
||||
|
||||
is GeneratorAction.RegenerateClick -> {
|
||||
handleRegenerationClick()
|
||||
}
|
||||
@@ -198,6 +209,27 @@ class GeneratorViewModel @Inject constructor(
|
||||
sendEvent(GeneratorEvent.NavigateToPasswordHistory)
|
||||
}
|
||||
|
||||
private fun handleCloseClick() {
|
||||
sendEvent(GeneratorEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleSelectClick() {
|
||||
when (state.selectedType) {
|
||||
is Passcode -> {
|
||||
generatorRepository.emitGeneratorResult(
|
||||
GeneratorResult.Password(state.generatedText),
|
||||
)
|
||||
}
|
||||
|
||||
is Username -> {
|
||||
generatorRepository.emitGeneratorResult(
|
||||
GeneratorResult.Username(state.generatedText),
|
||||
)
|
||||
}
|
||||
}
|
||||
sendEvent(GeneratorEvent.NavigateBack)
|
||||
}
|
||||
|
||||
//endregion Top Level Handlers
|
||||
|
||||
//region Generation Handlers
|
||||
@@ -1394,10 +1426,6 @@ class GeneratorViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
//endregion Utility Functions
|
||||
|
||||
companion object {
|
||||
private const val PLACEHOLDER_GENERATED_TEXT = "Placeholder"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1791,6 +1819,16 @@ sealed class GeneratorAction {
|
||||
*/
|
||||
data object PasswordHistoryClick : GeneratorAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has selected a generated string from the modal generator
|
||||
*/
|
||||
data object SelectClick : GeneratorAction()
|
||||
|
||||
/**
|
||||
* Indicates the user has clicked the close button.
|
||||
*/
|
||||
data object CloseClick : GeneratorAction()
|
||||
|
||||
/**
|
||||
* Represents the action to regenerate a new passcode or username.
|
||||
*/
|
||||
@@ -2187,6 +2225,11 @@ sealed class GeneratorEvent {
|
||||
*/
|
||||
data object NavigateToPasswordHistory : GeneratorEvent()
|
||||
|
||||
/**
|
||||
* Navigate back to previous screen.
|
||||
*/
|
||||
data object NavigateBack : GeneratorEvent()
|
||||
|
||||
/**
|
||||
* Displays the message in a snackbar.
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
||||
@@ -53,6 +55,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val generatorRepository: GeneratorRepository,
|
||||
) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
@@ -94,6 +97,14 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
.map { VaultAddEditAction.Internal.TotpCodeReceive(totpResult = it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
generatorRepository
|
||||
.generatorResultFlow
|
||||
.map {
|
||||
VaultAddEditAction.Internal.GeneratorResultReceive(generatorResult = it)
|
||||
}
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: VaultAddEditAction) {
|
||||
@@ -415,13 +426,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleLoginOpenUsernameGeneratorClick() {
|
||||
viewModelScope.launch {
|
||||
sendEvent(
|
||||
event = VaultAddEditEvent.ShowToast(
|
||||
message = "Open Username Generator".asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
sendEvent(event = VaultAddEditEvent.NavigateToGeneratorModal(GeneratorMode.Modal.Username))
|
||||
}
|
||||
|
||||
private fun handleLoginPasswordCheckerClick() {
|
||||
@@ -435,13 +440,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleLoginOpenPasswordGeneratorClick() {
|
||||
viewModelScope.launch {
|
||||
sendEvent(
|
||||
event = VaultAddEditEvent.ShowToast(
|
||||
message = "Open Password Generator".asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
sendEvent(event = VaultAddEditEvent.NavigateToGeneratorModal(GeneratorMode.Modal.Password))
|
||||
}
|
||||
|
||||
private fun handleLoginSetupTotpClick(
|
||||
@@ -756,6 +755,9 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
|
||||
is VaultAddEditAction.Internal.TotpCodeReceive -> handleVaultTotpCodeReceive(action)
|
||||
is VaultAddEditAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||
is VaultAddEditAction.Internal.GeneratorResultReceive -> {
|
||||
handleGeneratorResultReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -892,6 +894,27 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGeneratorResultReceive(
|
||||
action: VaultAddEditAction.Internal.GeneratorResultReceive,
|
||||
) {
|
||||
when (action.generatorResult) {
|
||||
is GeneratorResult.Password -> {
|
||||
updateLoginContent { loginType ->
|
||||
loginType.copy(
|
||||
password = action.generatorResult.password,
|
||||
)
|
||||
}
|
||||
}
|
||||
is GeneratorResult.Username -> {
|
||||
updateLoginContent { loginType ->
|
||||
loginType.copy(
|
||||
username = action.generatorResult.username,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Internal Type Handlers
|
||||
|
||||
//region Utility Functions
|
||||
@@ -1697,6 +1720,13 @@ sealed class VaultAddEditAction {
|
||||
*/
|
||||
data class TotpCodeReceive(val totpResult: TotpCodeResult) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the vault totp code result has been received.
|
||||
*/
|
||||
data class GeneratorResultReceive(
|
||||
val generatorResult: GeneratorResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the vault item data has been received.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user