mirror of
https://github.com/bitwarden/android.git
synced 2026-03-22 20:41:29 -05:00
[PM-15873] Add delay to PTR to remove the spinning wheel (#4750)
This commit is contained in:
@@ -18,6 +18,7 @@ import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
|
||||
@@ -55,6 +56,7 @@ import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import com.x8bit.bitwarden.ui.vault.util.shortName
|
||||
import com.x8bit.bitwarden.ui.vault.util.toVaultItemCipherType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -84,6 +86,7 @@ class VaultViewModel @Inject constructor(
|
||||
private val reviewPromptManager: ReviewPromptManager,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
||||
initialState = run {
|
||||
val userState = requireNotNull(authRepository.userStateFlow.value)
|
||||
@@ -362,10 +365,21 @@ class VaultViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleSyncClick() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultState.DialogState.Syncing)
|
||||
if (networkConnectionManager.isNetworkConnected) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultState.DialogState.Syncing)
|
||||
}
|
||||
vaultRepository.sync(forced = true)
|
||||
} else {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultState.DialogState.Error(
|
||||
R.string.internet_connection_required_title.asText(),
|
||||
R.string.internet_connection_required_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
vaultRepository.sync(forced = true)
|
||||
}
|
||||
|
||||
private fun handleLockClick() {
|
||||
@@ -426,11 +440,17 @@ class VaultViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun handleRefreshPull() {
|
||||
mutableStateFlow.update { it.copy(isRefreshing = true) }
|
||||
// The Pull-To-Refresh composable is already in the refreshing state.
|
||||
// We will reset that state when sendDataStateFlow emits later on.
|
||||
vaultRepository.sync(forced = false)
|
||||
viewModelScope.launch {
|
||||
delay(250)
|
||||
if (networkConnectionManager.isNetworkConnected) {
|
||||
vaultRepository.sync(forced = false)
|
||||
} else {
|
||||
sendAction(VaultAction.Internal.InternetConnectionErrorReceived)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOverflowOptionClick(action: VaultAction.OverflowOptionClick) {
|
||||
@@ -588,6 +608,22 @@ class VaultViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
is VaultAction.Internal.SnackbarDataReceive -> handleSnackbarDataReceive(action)
|
||||
|
||||
VaultAction.Internal.InternetConnectionErrorReceived -> {
|
||||
handleInternetConnectionErrorReceived()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInternetConnectionErrorReceived() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
dialog = VaultState.DialogState.Error(
|
||||
R.string.internet_connection_required_title.asText(),
|
||||
R.string.internet_connection_required_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1420,6 +1456,11 @@ sealed class VaultAction {
|
||||
val isIconLoadingDisabled: Boolean,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the there is not internet connection.
|
||||
*/
|
||||
data object InternetConnectionErrorReceived : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a result for generating a verification code has been received.
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
|
||||
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.Environment
|
||||
@@ -54,9 +55,11 @@ import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
@@ -155,6 +158,10 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
every { specialCircumstance } returns null
|
||||
}
|
||||
|
||||
private val networkConnectionManager: NetworkConnectionManager = mockk {
|
||||
every { isNetworkConnected } returns true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct and should trigger a syncIfNecessary call`() {
|
||||
val viewModel = createViewModel()
|
||||
@@ -493,6 +500,27 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on SyncClick should show the no network dialog if not connection is available`() {
|
||||
val viewModel = createViewModel()
|
||||
every {
|
||||
networkConnectionManager.isNetworkConnected
|
||||
} returns false
|
||||
viewModel.trySendAction(VaultAction.SyncClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialog = VaultState.DialogState.Error(
|
||||
R.string.internet_connection_required_title.asText(),
|
||||
R.string.internet_connection_required_message.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 0) {
|
||||
vaultRepository.sync(forced = true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on LockClick should call lockVaultForCurrentUser on the VaultRepository`() {
|
||||
val viewModel = createViewModel()
|
||||
@@ -1343,13 +1371,38 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `RefreshPull should call vault repository sync`() {
|
||||
fun `RefreshPull should call vault repository sync`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.RefreshPull)
|
||||
advanceTimeBy(300)
|
||||
verify(exactly = 1) {
|
||||
vaultRepository.sync(forced = false)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `RefreshPull should show network error if no internet connection`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
every {
|
||||
networkConnectionManager.isNetworkConnected
|
||||
} returns false
|
||||
|
||||
viewModel.trySendAction(VaultAction.RefreshPull)
|
||||
|
||||
verify(exactly = 1) {
|
||||
advanceTimeBy(300)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
isRefreshing = false,
|
||||
dialog = VaultState.DialogState.Error(
|
||||
R.string.internet_connection_required_title.asText(),
|
||||
R.string.internet_connection_required_message.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 0) {
|
||||
vaultRepository.sync(forced = false)
|
||||
}
|
||||
}
|
||||
@@ -1940,6 +1993,23 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `InternetConnectionErrorReceived should show network error if no internet connection`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.Internal.InternetConnectionErrorReceived)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
isRefreshing = false,
|
||||
dialog = VaultState.DialogState.Error(
|
||||
R.string.internet_connection_required_title.asText(),
|
||||
R.string.internet_connection_required_message.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createViewModel(): VaultViewModel =
|
||||
VaultViewModel(
|
||||
authRepository = authRepository,
|
||||
@@ -1954,6 +2024,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
reviewPromptManager = reviewPromptManager,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user