mirror of
https://github.com/bitwarden/android.git
synced 2026-06-07 14:57:41 -05:00
BIT-1268: Pull-to-refresh for sends screen (#618)
This commit is contained in:
committed by
Álison Fernandes
parent
80084ff0fb
commit
0975ab9c7b
@@ -12,8 +12,10 @@ 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.rememberPullToRefreshState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -55,8 +57,18 @@ fun SendScreen(
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
.takeIf { state.isPullToRefreshEnabled }
|
||||
LaunchedEffect(key1 = pullToRefreshState?.isRefreshing) {
|
||||
if (pullToRefreshState?.isRefreshing == true) {
|
||||
viewModel.trySendAction(SendAction.RefreshPull)
|
||||
}
|
||||
}
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is SendEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh()
|
||||
|
||||
is SendEvent.NavigateNewSend -> onNavigateToAddSend()
|
||||
|
||||
is SendEvent.NavigateToEditSend -> onNavigateToEditSend(event.sendId)
|
||||
@@ -146,6 +158,7 @@ fun SendScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
) { padding ->
|
||||
val modifier = Modifier
|
||||
.imePadding()
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
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.util.baseWebSendUrl
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
@@ -40,6 +41,7 @@ class SendViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
private val environmentRepo: EnvironmentRepository,
|
||||
private val settingsRepo: SettingsRepository,
|
||||
private val vaultRepo: VaultRepository,
|
||||
) : BaseViewModel<SendState, SendEvent, SendAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
@@ -47,10 +49,16 @@ class SendViewModel @Inject constructor(
|
||||
?: SendState(
|
||||
viewState = SendState.ViewState.Loading,
|
||||
dialogState = null,
|
||||
isPullToRefreshSettingEnabled = settingsRepo.getPullToRefreshEnabledFlow().value,
|
||||
),
|
||||
) {
|
||||
|
||||
init {
|
||||
settingsRepo
|
||||
.getPullToRefreshEnabledFlow()
|
||||
.map { SendAction.Internal.PullToRefreshEnableReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
vaultRepo
|
||||
.sendDataStateFlow
|
||||
.map { SendAction.Internal.SendDataReceive(it) }
|
||||
@@ -73,10 +81,15 @@ class SendViewModel @Inject constructor(
|
||||
is SendAction.DeleteSendClick -> handleDeleteSendClick(action)
|
||||
is SendAction.RemovePasswordClick -> handleRemovePasswordClick(action)
|
||||
SendAction.DismissDialog -> handleDismissDialog()
|
||||
SendAction.RefreshPull -> handleRefreshPull()
|
||||
is SendAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
|
||||
private fun handleInternalAction(action: SendAction.Internal): Unit = when (action) {
|
||||
is SendAction.Internal.PullToRefreshEnableReceive -> {
|
||||
handlePullToRefreshEnableReceive(action)
|
||||
}
|
||||
|
||||
is SendAction.Internal.DeleteSendResultReceive -> handleDeleteSendResultReceive(action)
|
||||
is SendAction.Internal.RemovePasswordSendResultReceive -> {
|
||||
handleRemovePasswordSendResultReceive(action)
|
||||
@@ -85,6 +98,14 @@ class SendViewModel @Inject constructor(
|
||||
is SendAction.Internal.SendDataReceive -> handleSendDataReceive(action)
|
||||
}
|
||||
|
||||
private fun handlePullToRefreshEnableReceive(
|
||||
action: SendAction.Internal.PullToRefreshEnableReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isPullToRefreshSettingEnabled = action.isPullToRefreshEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteSendResultReceive(action: SendAction.Internal.DeleteSendResultReceive) {
|
||||
when (action.result) {
|
||||
DeleteSendResult.Error -> {
|
||||
@@ -141,6 +162,7 @@ class SendViewModel @Inject constructor(
|
||||
dialogState = null,
|
||||
)
|
||||
}
|
||||
sendEvent(SendEvent.DismissPullToRefresh)
|
||||
}
|
||||
|
||||
is DataState.Loaded -> {
|
||||
@@ -155,6 +177,7 @@ class SendViewModel @Inject constructor(
|
||||
dialogState = null,
|
||||
)
|
||||
}
|
||||
sendEvent(SendEvent.DismissPullToRefresh)
|
||||
}
|
||||
|
||||
DataState.Loading -> {
|
||||
@@ -174,6 +197,7 @@ class SendViewModel @Inject constructor(
|
||||
dialogState = null,
|
||||
)
|
||||
}
|
||||
sendEvent(SendEvent.DismissPullToRefresh)
|
||||
}
|
||||
|
||||
is DataState.Pending -> {
|
||||
@@ -269,6 +293,12 @@ class SendViewModel @Inject constructor(
|
||||
private fun handleDismissDialog() {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
}
|
||||
|
||||
private fun handleRefreshPull() {
|
||||
// The Pull-To-Refresh composable is already in the refreshing state.
|
||||
// We will reset that state when sendDataStateFlow emits later on.
|
||||
vaultRepo.sync()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,12 +308,24 @@ class SendViewModel @Inject constructor(
|
||||
data class SendState(
|
||||
val viewState: ViewState,
|
||||
val dialogState: DialogState?,
|
||||
private val isPullToRefreshSettingEnabled: Boolean,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* Indicates that the pull-to-refresh should be enabled in the UI.
|
||||
*/
|
||||
val isPullToRefreshEnabled: Boolean
|
||||
get() = isPullToRefreshSettingEnabled && viewState.isPullToRefreshEnabled
|
||||
|
||||
/**
|
||||
* Represents the specific view states for the send screen.
|
||||
*/
|
||||
sealed class ViewState : Parcelable {
|
||||
/**
|
||||
* Indicates the pull-to-refresh feature should be available during the current state.
|
||||
*/
|
||||
abstract val isPullToRefreshEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Indicates if the FAB should be displayed.
|
||||
*/
|
||||
@@ -298,6 +340,7 @@ data class SendState(
|
||||
val fileTypeCount: Int,
|
||||
val sendItems: List<SendItem>,
|
||||
) : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = true
|
||||
override val shouldDisplayFab: Boolean get() = true
|
||||
|
||||
/**
|
||||
@@ -328,6 +371,7 @@ data class SendState(
|
||||
*/
|
||||
@Parcelize
|
||||
data object Empty : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = true
|
||||
override val shouldDisplayFab: Boolean get() = true
|
||||
}
|
||||
|
||||
@@ -338,6 +382,7 @@ data class SendState(
|
||||
data class Error(
|
||||
val message: Text,
|
||||
) : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = true
|
||||
override val shouldDisplayFab: Boolean get() = false
|
||||
}
|
||||
|
||||
@@ -346,6 +391,7 @@ data class SendState(
|
||||
*/
|
||||
@Parcelize
|
||||
data object Loading : ViewState() {
|
||||
override val isPullToRefreshEnabled: Boolean get() = false
|
||||
override val shouldDisplayFab: Boolean get() = false
|
||||
}
|
||||
}
|
||||
@@ -458,10 +504,20 @@ sealed class SendAction {
|
||||
*/
|
||||
data object DismissDialog : SendAction()
|
||||
|
||||
/**
|
||||
* User has triggered a pull to refresh.
|
||||
*/
|
||||
data object RefreshPull : SendAction()
|
||||
|
||||
/**
|
||||
* Models actions that the [SendViewModel] itself will send.
|
||||
*/
|
||||
sealed class Internal : SendAction() {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -487,6 +543,11 @@ sealed class SendAction {
|
||||
* Models events for the send screen.
|
||||
*/
|
||||
sealed class SendEvent {
|
||||
/**
|
||||
* Dismisses the pull-to-refresh indicator.
|
||||
*/
|
||||
data object DismissPullToRefresh : SendEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the new send screen.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user