mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1499 listing screen pull-to-refresh (#690)
This commit is contained in:
parent
c487074de6
commit
88e4b45f7d
4 changed files with 222 additions and 90 deletions
|
@ -8,8 +8,11 @@ import androidx.compose.material3.FloatingActionButton
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshState
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
|
@ -40,6 +43,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
/**
|
||||
* Displays the vault item listing screen.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun VaultItemListingScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
|
@ -53,10 +57,19 @@ fun VaultItemListingScreen(
|
|||
val state by viewModel.stateFlow.collectAsState()
|
||||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
|
||||
val pullToRefreshState = rememberPullToRefreshState().takeIf { state.isPullToRefreshEnabled }
|
||||
LaunchedEffect(key1 = pullToRefreshState?.isRefreshing) {
|
||||
if (pullToRefreshState?.isRefreshing == true) {
|
||||
viewModel.trySendAction(VaultItemListingsAction.RefreshPull)
|
||||
}
|
||||
}
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is VaultItemListingEvent.NavigateBack -> onNavigateBack()
|
||||
|
||||
is VaultItemListingEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh()
|
||||
|
||||
is VaultItemListingEvent.NavigateToVaultItem -> {
|
||||
onNavigateToVaultItem(event.id)
|
||||
}
|
||||
|
@ -99,6 +112,7 @@ fun VaultItemListingScreen(
|
|||
|
||||
VaultItemListingScaffold(
|
||||
state = state,
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
vaultItemListingHandlers = remember(viewModel) {
|
||||
VaultItemListingHandlers.create(viewModel)
|
||||
},
|
||||
|
@ -132,6 +146,7 @@ private fun VaultItemListingDialogs(
|
|||
@Composable
|
||||
private fun VaultItemListingScaffold(
|
||||
state: VaultItemListingState,
|
||||
pullToRefreshState: PullToRefreshState?,
|
||||
vaultItemListingHandlers: VaultItemListingHandlers,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
@ -179,6 +194,7 @@ private fun VaultItemListingScaffold(
|
|||
}
|
||||
}
|
||||
},
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
) { paddingValues ->
|
||||
val modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
|||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -59,11 +60,18 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
baseWebSendUrl = environmentRepository.environment.environmentUrlData.baseWebSendUrl,
|
||||
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
|
||||
dialogState = null,
|
||||
),
|
||||
) {
|
||||
|
||||
init {
|
||||
settingsRepository
|
||||
.getPullToRefreshEnabledFlow()
|
||||
.map { VaultItemListingsAction.Internal.PullToRefreshEnableReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.isIconLoadingDisabledFlow
|
||||
.onEach { sendAction(VaultItemListingsAction.Internal.IconLoadingSettingReceive(it)) }
|
||||
|
@ -86,6 +94,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
is VaultItemListingsAction.ItemClick -> handleItemClick(action)
|
||||
is VaultItemListingsAction.AddVaultItemClick -> handleAddVaultItemClick()
|
||||
is VaultItemListingsAction.RefreshClick -> handleRefreshClick()
|
||||
is VaultItemListingsAction.RefreshPull -> handleRefreshPull()
|
||||
is VaultItemListingsAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +104,12 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
vaultRepository.sync()
|
||||
}
|
||||
|
||||
private fun handleRefreshPull() {
|
||||
// The Pull-To-Refresh composable is already in the refreshing state.
|
||||
// We will reset that state when sendDataStateFlow emits later on.
|
||||
vaultRepository.sync()
|
||||
}
|
||||
|
||||
private fun handleCopySendUrlClick(action: ListingItemOverflowAction.SendAction.CopyUrlClick) {
|
||||
clipboardManager.setText(text = action.sendUrl)
|
||||
}
|
||||
|
@ -222,6 +237,10 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
|
||||
private fun handleInternalAction(action: VaultItemListingsAction.Internal) {
|
||||
when (action) {
|
||||
is VaultItemListingsAction.Internal.PullToRefreshEnableReceive -> {
|
||||
handlePullToRefreshEnableReceive(action)
|
||||
}
|
||||
|
||||
is VaultItemListingsAction.Internal.DeleteSendResultReceive -> {
|
||||
handleDeleteSendResultReceive(action)
|
||||
}
|
||||
|
@ -237,6 +256,14 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePullToRefreshEnableReceive(
|
||||
action: VaultItemListingsAction.Internal.PullToRefreshEnableReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isPullToRefreshSettingEnabled = action.isPullToRefreshEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteSendResultReceive(
|
||||
action: VaultItemListingsAction.Internal.DeleteSendResultReceive,
|
||||
) {
|
||||
|
@ -326,10 +353,12 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
sendEvent(VaultItemListingEvent.DismissPullToRefresh)
|
||||
}
|
||||
|
||||
private fun vaultLoadedReceive(vaultData: DataState.Loaded<VaultData>) {
|
||||
updateStateWithVaultData(vaultData = vaultData.data, clearDialogState = true)
|
||||
sendEvent(VaultItemListingEvent.DismissPullToRefresh)
|
||||
}
|
||||
|
||||
private fun vaultLoadingReceive() {
|
||||
|
@ -351,6 +380,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
sendEvent(VaultItemListingEvent.DismissPullToRefresh)
|
||||
}
|
||||
|
||||
private fun vaultPendingReceive(vaultData: DataState.Pending<VaultData>) {
|
||||
|
@ -413,8 +443,15 @@ data class VaultItemListingState(
|
|||
val baseIconUrl: String,
|
||||
val isIconLoadingDisabled: Boolean,
|
||||
val dialogState: DialogState?,
|
||||
private val isPullToRefreshSettingEnabled: Boolean,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Indicates that the pull-to-refresh should be enabled in the UI.
|
||||
*/
|
||||
val isPullToRefreshEnabled: Boolean
|
||||
get() = isPullToRefreshSettingEnabled && viewState.isPullToRefreshEnabled
|
||||
|
||||
/**
|
||||
* Represents the current state of any dialogs on the screen.
|
||||
*/
|
||||
|
@ -442,17 +479,25 @@ data class VaultItemListingState(
|
|||
* Represents the specific view states for the [VaultItemListingScreen].
|
||||
*/
|
||||
sealed class ViewState {
|
||||
/**
|
||||
* Indicates the pull-to-refresh feature should be available during the current state.
|
||||
*/
|
||||
abstract val isPullToRefreshEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Loading state for the [VaultItemListingScreen],
|
||||
* signifying that the content is being processed.
|
||||
*/
|
||||
data object Loading : ViewState()
|
||||
data object Loading : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a state where the [VaultItemListingScreen] has no items to display.
|
||||
*/
|
||||
data object NoItems : ViewState()
|
||||
data object NoItems : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Content state for the [VaultItemListingScreen] showing the actual content or items.
|
||||
|
@ -461,7 +506,9 @@ data class VaultItemListingState(
|
|||
*/
|
||||
data class Content(
|
||||
val displayItemList: List<DisplayItem>,
|
||||
) : ViewState()
|
||||
) : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an error state for the [VaultItemListingScreen].
|
||||
|
@ -470,7 +517,9 @@ data class VaultItemListingState(
|
|||
*/
|
||||
data class Error(
|
||||
val message: Text,
|
||||
) : ViewState()
|
||||
) : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -609,6 +658,10 @@ data class VaultItemListingState(
|
|||
* Models events for the [VaultItemListingScreen].
|
||||
*/
|
||||
sealed class VaultItemListingEvent {
|
||||
/**
|
||||
* Dismisses the pull-to-refresh indicator.
|
||||
*/
|
||||
data object DismissPullToRefresh : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the Create Account screen.
|
||||
|
@ -711,10 +764,19 @@ sealed class VaultItemListingsAction {
|
|||
*/
|
||||
data class ItemClick(val id: String) : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* User has triggered a pull to refresh.
|
||||
*/
|
||||
data object RefreshPull : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Models actions that the [VaultItemListingViewModel] itself might send.
|
||||
*/
|
||||
sealed class Internal : VaultItemListingsAction() {
|
||||
/**
|
||||
* Indicates that the pull to refresh feature toggle has changed.
|
||||
*/
|
||||
data class PullToRefreshEnableReceive(val isPullToRefreshEnabled: Boolean) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a result for deleting the send has been received.
|
||||
|
|
|
@ -699,6 +699,7 @@ private val DEFAULT_STATE = VaultItemListingState(
|
|||
baseWebSendUrl = Environment.Us.environmentUrlData.baseWebSendUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isPullToRefreshSettingEnabled = false,
|
||||
dialogState = null,
|
||||
)
|
||||
|
||||
|
|
|
@ -66,10 +66,12 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
every { environmentStateFlow } returns mockk()
|
||||
}
|
||||
|
||||
private val mutablePullToRefreshEnabledFlow = MutableStateFlow(false)
|
||||
private val mutableIsIconLoadingDisabledFlow = MutableStateFlow(false)
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { isIconLoadingDisabled } returns false
|
||||
every { isIconLoadingDisabledFlow } returns mutableIsIconLoadingDisabledFlow
|
||||
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshEnabledFlow
|
||||
}
|
||||
private val initialState = createVaultItemListingState()
|
||||
private val initialSavedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
||||
|
@ -350,25 +352,27 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
isDeleted = false,
|
||||
),
|
||||
val dataState = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
isDeleted = false,
|
||||
),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
|
@ -384,17 +388,19 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `vaultDataStateFlow Loaded with empty items should update ViewState to NoItems`() =
|
||||
runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
sendViewList = emptyList(),
|
||||
),
|
||||
val dataState = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
sendViewList = emptyList(),
|
||||
),
|
||||
)
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(viewState = VaultItemListingState.ViewState.NoItems),
|
||||
viewModel.stateFlow.value,
|
||||
|
@ -404,17 +410,20 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `vaultDataStateFlow Loaded with trash items should update ViewState to NoItems`() =
|
||||
runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
val dataState = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
)
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.NoItems,
|
||||
|
@ -508,14 +517,16 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Error without data should update state to Error`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Error(
|
||||
error = IllegalStateException(),
|
||||
),
|
||||
val dataState = DataState.Error<VaultData>(
|
||||
error = IllegalStateException(),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.Error(
|
||||
|
@ -530,20 +541,22 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
fun `vaultDataStateFlow Error with data should update state to Content`() = runTest {
|
||||
setupMockUri()
|
||||
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Error(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = false)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
error = IllegalStateException(),
|
||||
val dataState = DataState.Error(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = false)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
error = IllegalStateException(),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
|
@ -560,20 +573,22 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Error with empty data should update state to NoItems`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Error(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
sendViewList = emptyList(),
|
||||
),
|
||||
error = IllegalStateException(),
|
||||
val dataState = DataState.Error(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
sendViewList = emptyList(),
|
||||
),
|
||||
error = IllegalStateException(),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.NoItems,
|
||||
|
@ -584,20 +599,22 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Error with trash data should update state to NoItems`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Error(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = true)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
error = IllegalStateException(),
|
||||
val dataState = DataState.Error(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = true)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
error = IllegalStateException(),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.NoItems,
|
||||
|
@ -608,12 +625,14 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `vaultDataStateFlow NoNetwork without data should update state to Error`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.NoNetwork(),
|
||||
)
|
||||
val dataState = DataState.NoNetwork<VaultData>()
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.Error(
|
||||
|
@ -630,19 +649,21 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
fun `vaultDataStateFlow NoNetwork with data should update state to Content`() = runTest {
|
||||
setupMockUri()
|
||||
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.NoNetwork(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = false)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
val dataState = DataState.NoNetwork(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = false)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
|
@ -659,19 +680,21 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `vaultDataStateFlow NoNetwork with empty data should update state to NoItems`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.NoNetwork(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
sendViewList = emptyList(),
|
||||
),
|
||||
val dataState = DataState.NoNetwork(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
sendViewList = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.NoItems,
|
||||
|
@ -682,19 +705,21 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `vaultDataStateFlow NoNetwork with trash data should update state to NoItems`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.NoNetwork(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = true)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
val dataState = DataState.NoNetwork(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = true)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(value = dataState)
|
||||
assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.NoItems,
|
||||
|
@ -713,6 +738,33 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
assertTrue(viewModel.stateFlow.value.isIconLoadingDisabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `RefreshPull should call vault repository sync`() {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.trySendAction(VaultItemListingsAction.RefreshPull)
|
||||
|
||||
verify(exactly = 1) {
|
||||
vaultRepository.sync()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PullToRefreshEnableReceive should update isPullToRefreshEnabled`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.Internal.PullToRefreshEnableReceive(
|
||||
isPullToRefreshEnabled = true,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
initialState.copy(isPullToRefreshSettingEnabled = true),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType: VaultItemListingType,
|
||||
|
@ -779,6 +831,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
baseWebSendUrl = Environment.Us.environmentUrlData.baseWebSendUrl,
|
||||
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||
isPullToRefreshSettingEnabled = false,
|
||||
dialogState = null,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue