mirror of
https://github.com/bitwarden/android.git
synced 2026-06-07 23:58:03 -05:00
BIT-1158: Add No Network states to Vault Screen (#391)
This commit is contained in:
committed by
Álison Fernandes
parent
36e913b680
commit
0655f74479
@@ -0,0 +1,49 @@
|
||||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
|
||||
/**
|
||||
* A Bitwarden-themed, re-usable error state.
|
||||
*
|
||||
* Note that when [onTryAgainClick] is absent, there will be no "Try again" button displayed.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenErrorContent(
|
||||
message: String,
|
||||
modifier: Modifier = Modifier,
|
||||
onTryAgainClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = message,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
onTryAgainClick?.let {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.try_again),
|
||||
onClick = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,15 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
@@ -125,6 +129,12 @@ fun VaultScreen(
|
||||
trashClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAction.TrashClick) }
|
||||
},
|
||||
tryAgainClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAction.TryAgainClick) }
|
||||
},
|
||||
dialogDismiss = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAction.DialogDismiss) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,6 +161,8 @@ private fun VaultScreenScaffold(
|
||||
identityGroupClick: () -> Unit,
|
||||
secureNoteGroupClick: () -> Unit,
|
||||
trashClick: () -> Unit,
|
||||
tryAgainClick: () -> Unit,
|
||||
dialogDismiss: () -> Unit,
|
||||
) {
|
||||
var accountMenuVisible by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
@@ -165,6 +177,20 @@ private fun VaultScreenScaffold(
|
||||
canScroll = { !accountMenuVisible },
|
||||
)
|
||||
|
||||
when (val dialog = state.dialog) {
|
||||
is VaultState.DialogState.Error -> {
|
||||
BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
title = dialog.title,
|
||||
message = dialog.message,
|
||||
),
|
||||
onDismissRequest = dialogDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
BitwardenScaffold(
|
||||
topBar = {
|
||||
BitwardenMediumTopAppBar(
|
||||
@@ -229,9 +255,15 @@ private fun VaultScreenScaffold(
|
||||
modifier = modifier,
|
||||
addItemClickAction = addItemClickAction,
|
||||
)
|
||||
|
||||
is VaultState.ViewState.Error -> BitwardenErrorContent(
|
||||
message = viewState.message(),
|
||||
onTryAgainClick = tryAgainClick,
|
||||
modifier = modifier
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
BitwardenAccountSwitcher(
|
||||
isVisible = accountMenuVisible,
|
||||
accountSummaries = state.accountSummaries.toImmutableList(),
|
||||
|
||||
@@ -95,6 +95,8 @@ class VaultViewModel @Inject constructor(
|
||||
is VaultAction.SecureNoteGroupClick -> handleSecureNoteClick()
|
||||
is VaultAction.TrashClick -> handleTrashClick()
|
||||
is VaultAction.VaultItemClick -> handleVaultItemClick(action)
|
||||
is VaultAction.TryAgainClick -> handleTryAgainClick()
|
||||
is VaultAction.DialogDismiss -> handleDialogDismiss()
|
||||
is VaultAction.Internal.UserStateUpdateReceive -> handleUserStateUpdateReceive(action)
|
||||
is VaultAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||
}
|
||||
@@ -176,6 +178,16 @@ class VaultViewModel @Inject constructor(
|
||||
sendEvent(VaultEvent.NavigateToVaultItem(action.vaultItem.id))
|
||||
}
|
||||
|
||||
private fun handleTryAgainClick() {
|
||||
vaultRepository.sync()
|
||||
}
|
||||
|
||||
private fun handleDialogDismiss() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUserStateUpdateReceive(action: VaultAction.Internal.UserStateUpdateReceive) {
|
||||
// Leave the current data alone if there is no UserState; we are in the process of logging
|
||||
// out.
|
||||
@@ -225,9 +237,27 @@ class VaultViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun vaultNoNetworkReceive(vaultData: DataState.NoNetwork<VaultData>) {
|
||||
// TODO update state to no network state BIT-1158
|
||||
mutableStateFlow.update { it.copy(viewState = VaultState.ViewState.NoItems) }
|
||||
sendEvent(VaultEvent.ShowToast(message = "Vault no network state not yet implemented"))
|
||||
val title = R.string.internet_connection_required_title.asText()
|
||||
val message = R.string.internet_connection_required_message.asText()
|
||||
if (vaultData.data != null) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = vaultData.data.toViewState(),
|
||||
dialog = VaultState.DialogState.Error(
|
||||
title = title,
|
||||
message = message,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultState.ViewState.Error(
|
||||
message = message,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun vaultPendingReceive(vaultData: DataState.Pending<VaultData>) {
|
||||
@@ -245,6 +275,7 @@ class VaultViewModel @Inject constructor(
|
||||
* @property initials The initials to be displayed on the avatar.
|
||||
* @property accountSummaries List of all the current accounts.
|
||||
* @property viewState The specific view state representing loading, no items, or content state.
|
||||
* @property dialog Information about any dialogs that may need to be displayed.
|
||||
* @property isSwitchingAccounts Whether or not we are actively switching accounts.
|
||||
*/
|
||||
@Parcelize
|
||||
@@ -253,6 +284,7 @@ data class VaultState(
|
||||
val initials: String,
|
||||
val accountSummaries: List<AccountSummary>,
|
||||
val viewState: ViewState,
|
||||
val dialog: DialogState? = null,
|
||||
// Internal-use properties
|
||||
val isSwitchingAccounts: Boolean = false,
|
||||
) : Parcelable {
|
||||
@@ -280,6 +312,15 @@ data class VaultState(
|
||||
@Parcelize
|
||||
data object NoItems : ViewState()
|
||||
|
||||
/**
|
||||
* Represents a state where the [VaultScreen] is unable to display data due to an error
|
||||
* retrieving it. The given [message] should be displayed.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Error(
|
||||
val message: Text,
|
||||
) : ViewState()
|
||||
|
||||
/**
|
||||
* Content state for the [VaultScreen] showing the actual content or items.
|
||||
*
|
||||
@@ -433,6 +474,21 @@ data class VaultState(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a dialog to display.
|
||||
*/
|
||||
sealed class DialogState : Parcelable {
|
||||
|
||||
/**
|
||||
* Represents an error dialog with the given [title] and [message].
|
||||
*/
|
||||
@Parcelize
|
||||
data class Error(
|
||||
val title: Text,
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The maximum number of no folder items that can be displayed before the UI creates a
|
||||
@@ -572,6 +628,16 @@ sealed class VaultAction {
|
||||
*/
|
||||
data object TrashClick : VaultAction()
|
||||
|
||||
/**
|
||||
* The user has requested that any visible dialogs are dismissed.
|
||||
*/
|
||||
data object DialogDismiss : VaultAction()
|
||||
|
||||
/**
|
||||
* User clicked the Try Again button when there is an error displayed.
|
||||
*/
|
||||
data object TryAgainClick : VaultAction()
|
||||
|
||||
/**
|
||||
* Models actions that the [VaultViewModel] itself might send.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user