diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreen.kt index 259170484..906637b2e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreen.kt @@ -20,13 +20,13 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -308,7 +308,7 @@ private fun TermsAndPrivacySwitch( } .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onCheckedChange.invoke(!isChecked) }, ) .padding(start = 16.dp) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/startregistration/StartRegistrationScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/startregistration/StartRegistrationScreen.kt index 8dff71862..c75dcb907 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/startregistration/StartRegistrationScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/startregistration/StartRegistrationScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -28,6 +27,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -448,7 +448,7 @@ private fun ReceiveMarketingEmailsSwitch( } .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onCheckedChange.invoke(!isChecked) }, ) .fillMaxWidth(), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt index daab4a550..420cdafa0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt @@ -298,7 +298,6 @@ private fun TwoFactorLoginScreenContent( } } -@OptIn(ExperimentalMaterial3Api::class) @Composable @Preview(showBackground = true) private fun TwoFactorLoginScreenContentPreview() { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt index 39fa9a9d5..dcda99487 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt @@ -1,7 +1,6 @@ package com.x8bit.bitwarden.ui.auth.feature.welcome import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -23,7 +22,6 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -57,7 +55,6 @@ private val LANDSCAPE_HORIZONTAL_MARGIN: Dp = 128.dp /** * Top level composable for the welcome screen. */ -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun WelcomeScreen( onNavigateToCreateAccount: () -> Unit, @@ -103,7 +100,6 @@ fun WelcomeScreen( } } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun WelcomeScreenContent( state: WelcomeState, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt index 146deb3fe..0dff2b40f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/EventsEffect.kt @@ -2,8 +2,8 @@ package com.x8bit.bitwarden.ui.platform.base.util import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt index 965e37fb8..7c8094097 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt @@ -7,6 +7,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.ui.CombinedModifier import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithCache @@ -42,14 +43,17 @@ fun Modifier.scrolledContainerBackground( ): Modifier { val expandedColor = MaterialTheme.colorScheme.surface val collapsedColor = MaterialTheme.colorScheme.surfaceContainer - return this then drawBehind { - drawRect( - color = topAppBarScrollBehavior.toScrolledContainerColor( - expandedColor = expandedColor, - collapsedColor = collapsedColor, - ), - ) - } + return CombinedModifier( + outer = this, + inner = drawBehind { + drawRect( + color = topAppBarScrollBehavior.toScrolledContainerColor( + expandedColor = expandedColor, + collapsedColor = collapsedColor, + ), + ) + }, + ) } /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt index 2dd3e2c52..01221d184 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -30,6 +29,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -276,7 +276,7 @@ private fun AccountSummaryItem( .testTag("AccountCell") .combinedClickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onSwitchAccountClick(accountSummary) }, onLongClick = { onSwitchAccountLongClick(accountSummary) }, ) @@ -398,7 +398,7 @@ private fun AddAccountItem( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .padding(vertical = 8.dp) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt index 72219422e..02abce0a9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt @@ -4,9 +4,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -33,7 +33,7 @@ fun BitwardenBasicDialogRow( modifier = modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .padding( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dropdown/EnvironmentSelector.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dropdown/EnvironmentSelector.kt index 104eb91b6..fe8af767c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dropdown/EnvironmentSelector.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dropdown/EnvironmentSelector.kt @@ -6,10 +6,10 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -57,7 +57,7 @@ fun EnvironmentSelector( modifier = Modifier .clip(RoundedCornerShape(28.dp)) .clickable( - indication = rememberRipple( + indication = ripple( bounded = true, color = MaterialTheme.colorScheme.primary, ), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt index 40e7f5f9d..4be9d850c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt @@ -6,10 +6,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -51,7 +51,7 @@ fun BitwardenGroupItem( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .bottomDivider( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt index 6432cb702..f40b80ac8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt @@ -10,11 +10,11 @@ import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -83,7 +83,7 @@ fun BitwardenListItem( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .defaultMinSize(minHeight = 72.dp) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/row/BitwardenTextRow.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/row/BitwardenTextRow.kt index 376f95199..324143551 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/row/BitwardenTextRow.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/row/BitwardenTextRow.kt @@ -9,10 +9,10 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -50,7 +50,7 @@ fun BitwardenTextRow( .clickable( enabled = isEnabled, interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .semantics(mergeDescendants = true) { }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/scaffold/BitwardenScaffold.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/scaffold/BitwardenScaffold.kt index 77e2c1b04..c11fd0fe3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/scaffold/BitwardenScaffold.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/scaffold/BitwardenScaffold.kt @@ -13,14 +13,15 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.contentColorFor -import androidx.compose.material3.pulltorefresh.PullToRefreshContainer -import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults +import androidx.compose.material3.pulltorefresh.pullToRefresh +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @@ -37,7 +38,7 @@ fun BitwardenScaffold( snackbarHost: @Composable () -> Unit = { }, floatingActionButton: @Composable () -> Unit = { }, floatingActionButtonPosition: FabPosition = FabPosition.End, - pullToRefreshState: PullToRefreshState? = null, + pullToRefreshState: BitwardenPullToRefreshState = rememberBitwardenPullToRefreshState(), containerColor: Color = MaterialTheme.colorScheme.surface, contentColor: Color = contentColorFor(containerColor), contentWindowInsets: WindowInsets = ScaffoldDefaults @@ -48,7 +49,6 @@ fun BitwardenScaffold( Scaffold( modifier = Modifier .semantics { testTagsAsResourceId = true } - .run { pullToRefreshState?.let { nestedScroll(it.nestedScrollConnection) } ?: this } .then(modifier), topBar = topBar, bottomBar = bottomBar, @@ -63,18 +63,50 @@ fun BitwardenScaffold( contentColor = contentColor, contentWindowInsets = contentWindowInsets, content = { paddingValues -> - Box { + val internalPullToRefreshState = rememberPullToRefreshState() + Box( + modifier = Modifier.pullToRefresh( + state = internalPullToRefreshState, + isRefreshing = pullToRefreshState.isRefreshing, + onRefresh = pullToRefreshState.onRefresh, + enabled = pullToRefreshState.isEnabled, + ), + ) { content(paddingValues) - pullToRefreshState?.let { - PullToRefreshContainer( - state = it, - modifier = Modifier - .padding(paddingValues) - .align(Alignment.TopCenter), - ) - } + PullToRefreshDefaults.Indicator( + modifier = Modifier + .padding(paddingValues) + .align(Alignment.TopCenter), + isRefreshing = pullToRefreshState.isRefreshing, + state = internalPullToRefreshState, + ) } }, ) } + +/** + * The state of the pull-to-refresh. + */ +data class BitwardenPullToRefreshState( + val isEnabled: Boolean, + val isRefreshing: Boolean, + val onRefresh: () -> Unit, +) + +/** + * Create and remember the default [BitwardenPullToRefreshState]. + */ +@Composable +fun rememberBitwardenPullToRefreshState( + isEnabled: Boolean = false, + isRefreshing: Boolean = false, + onRefresh: () -> Unit = { }, +): BitwardenPullToRefreshState = remember(isEnabled, isRefreshing, onRefresh) { + BitwardenPullToRefreshState( + isEnabled = isEnabled, + isRefreshing = isRefreshing, + onRefresh = onRefresh, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/segment/BitwardenSegmentedButton.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/segment/BitwardenSegmentedButton.kt index cbbb570b4..fae60e646 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/segment/BitwardenSegmentedButton.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/segment/BitwardenSegmentedButton.kt @@ -1,6 +1,5 @@ package com.x8bit.bitwarden.ui.platform.components.segment -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MultiChoiceSegmentedButtonRow import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButtonDefaults @@ -16,7 +15,6 @@ import kotlinx.collections.immutable.ImmutableList * @param options List of options to display. * @param modifier Modifier. */ -@OptIn(ExperimentalMaterial3Api::class) @Composable fun BitwardenSegmentedButton( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/text/BitwardenClickableText.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/text/BitwardenClickableText.kt index a7b9f0ccf..5b786b0bd 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/text/BitwardenClickableText.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/text/BitwardenClickableText.kt @@ -5,9 +5,9 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -41,7 +41,7 @@ fun BitwardenClickableText( modifier = modifier .clip(RoundedCornerShape(cornerSize)) .clickable( - indication = rememberRipple( + indication = ripple( bounded = true, color = MaterialTheme.colorScheme.primary, ), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt index 831db3074..0eb82605a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt @@ -10,10 +10,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -49,7 +49,7 @@ fun BitwardenSwitch( if (onCheckedChange != null) { this.clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onCheckedChange.invoke(!isChecked) }, ) } else { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitchWithActions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitchWithActions.kt index 6e86c4c69..0f9a235e6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitchWithActions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenSwitchWithActions.kt @@ -5,11 +5,11 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -49,7 +49,7 @@ fun BitwardenSwitchWithActions( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onCheckedChange?.invoke(!isChecked) }, ) .semantics(mergeDescendants = true) { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenWideSwitch.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenWideSwitch.kt index a1eb4eee6..f18fdd425 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenWideSwitch.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenWideSwitch.kt @@ -10,10 +10,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -57,7 +57,7 @@ fun BitwardenWideSwitch( .wrapContentHeight() .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onCheckedChange?.invoke(!isChecked) }, enabled = !readOnly && enabled, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsScreen.kt index 15bc8bf82..3a128db88 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsScreen.kt @@ -12,13 +12,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -108,7 +108,7 @@ private fun SettingsRow( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .bottomDivider(paddingStart = 16.dp) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt index 0caa99571..9b84c9349 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt @@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -22,6 +21,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -249,7 +249,7 @@ private fun CopyRow( modifier = modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .semantics(mergeDescendants = true) { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreen.kt index fa690ba9c..f432bd203 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreen.kt @@ -17,19 +17,16 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -52,6 +49,7 @@ import com.x8bit.bitwarden.ui.platform.components.content.BitwardenErrorContent import com.x8bit.bitwarden.ui.platform.components.content.BitwardenLoadingContent import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography @@ -70,17 +68,15 @@ fun PendingRequestsScreen( val state by viewModel.stateFlow.collectAsStateWithLifecycle() val context = LocalContext.current val resources = context.resources - val pullToRefreshState by rememberUpdatedState( - newValue = rememberPullToRefreshState().takeIf { state.isPullToRefreshEnabled }, + val pullToRefreshState = rememberBitwardenPullToRefreshState( + isEnabled = state.isPullToRefreshEnabled, + isRefreshing = state.isRefreshing, + onRefresh = remember(viewModel) { + { viewModel.trySendAction(PendingRequestsAction.RefreshPull) } + }, ) - LaunchedEffect(key1 = pullToRefreshState?.isRefreshing) { - if (pullToRefreshState?.isRefreshing == true) { - viewModel.trySendAction(PendingRequestsAction.RefreshPull) - } - } EventsEffect(viewModel = viewModel) { event -> when (event) { - PendingRequestsEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh() PendingRequestsEvent.NavigateBack -> onNavigateBack() is PendingRequestsEvent.NavigateToLoginApproval -> { onNavigateToLoginApproval(event.fingerprint) @@ -244,7 +240,7 @@ private fun PendingRequestItem( modifier = modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onNavigateToLoginApproval(fingerprintPhrase) }, ), horizontalAlignment = Alignment.Start, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt index 87dd49f62..f4ac92d24 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt @@ -38,6 +38,7 @@ class PendingRequestsViewModel @Inject constructor( authRequests = emptyList(), viewState = PendingRequestsState.ViewState.Loading, isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value, + isRefreshing = false, ), ) { private var authJob: Job = Job().apply { complete() } @@ -93,6 +94,7 @@ class PendingRequestsViewModel @Inject constructor( } private fun handleRefreshPull() { + mutableStateFlow.update { it.copy(isRefreshing = true) } updateAuthRequestList() } @@ -169,7 +171,7 @@ class PendingRequestsViewModel @Inject constructor( } } } - sendEvent(PendingRequestsEvent.DismissPullToRefresh) + mutableStateFlow.update { it.copy(isRefreshing = false) } } private fun updateAuthRequestList() { @@ -190,6 +192,7 @@ data class PendingRequestsState( val authRequests: List, val viewState: ViewState, private val isPullToRefreshSettingEnabled: Boolean, + val isRefreshing: Boolean, ) : Parcelable { /** * Indicates that the pull-to-refresh should be enabled in the UI. @@ -259,11 +262,6 @@ data class PendingRequestsState( * Models events for the delete account screen. */ sealed class PendingRequestsEvent { - /** - * Dismisses the pull-to-refresh indicator. - */ - data object DismissPullToRefresh : PendingRequestsEvent() - /** * Navigates back. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/blockautofill/BlockAutoFillScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/blockautofill/BlockAutoFillScreen.kt index 2f5d4b2ef..9475a8ee8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/blockautofill/BlockAutoFillScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/blockautofill/BlockAutoFillScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -28,6 +27,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -280,7 +280,7 @@ private fun BlockAutoFillListItem( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onClick, ) .bottomDivider(paddingStart = 16.dp) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/folders/FoldersScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/folders/FoldersScreen.kt index cd24e7c3e..01616c85a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/folders/FoldersScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/folders/FoldersScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon @@ -20,6 +19,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -168,7 +168,7 @@ private fun FoldersContent( .testTag("FolderCell") .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = { onItemClick(it.id) }, ) .bottomDivider(paddingStart = 16.dp) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt index f3f3d8754..1895387f7 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBarItem @@ -142,7 +141,6 @@ fun VaultUnlockedNavBarScreen( * Scaffold that contains the bottom nav bar for the [VaultUnlockedNavBarScreen] */ @Composable -@OptIn(ExperimentalMaterial3Api::class) @Suppress("LongMethod") private fun VaultUnlockedNavBarScaffold( state: VaultUnlockedNavBarState, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt index 9fba06944..15511d5f2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt @@ -12,10 +12,8 @@ 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 @@ -39,6 +37,7 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType @@ -63,18 +62,16 @@ 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) - } - } + val pullToRefreshState = rememberBitwardenPullToRefreshState( + isEnabled = state.isPullToRefreshEnabled, + isRefreshing = state.isRefreshing, + onRefresh = remember(viewModel) { + { viewModel.trySendAction(SendAction.RefreshPull) } + }, + ) EventsEffect(viewModel = viewModel) { event -> when (event) { - is SendEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh() - is SendEvent.NavigateToSearch -> onNavigateToSearchSend(SearchType.Sends.All) is SendEvent.NavigateNewSend -> onNavigateToAddSend() diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt index cae801d9f..ca1e63505 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt @@ -56,6 +56,7 @@ class SendViewModel @Inject constructor( policyDisablesSend = policyManager .getActivePolicies(type = PolicyTypeJson.DISABLE_SEND) .any(), + isRefreshing = false, ), ) { @@ -174,9 +175,9 @@ class SendViewModel @Inject constructor( message = R.string.generic_error_message.asText(), ), dialogState = null, + isRefreshing = false, ) } - sendEvent(SendEvent.DismissPullToRefresh) } is DataState.Loaded -> { @@ -189,9 +190,9 @@ class SendViewModel @Inject constructor( .baseWebSendUrl, ), dialogState = null, + isRefreshing = false, ) } - sendEvent(SendEvent.DismissPullToRefresh) } DataState.Loading -> { @@ -212,9 +213,9 @@ class SendViewModel @Inject constructor( ), ), dialogState = null, + isRefreshing = false, ) } - sendEvent(SendEvent.DismissPullToRefresh) } is DataState.Pending -> { @@ -317,6 +318,7 @@ class SendViewModel @Inject constructor( } 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. vaultRepo.sync() @@ -332,6 +334,7 @@ data class SendState( val dialogState: DialogState?, private val isPullToRefreshSettingEnabled: Boolean, val policyDisablesSend: Boolean, + val isRefreshing: Boolean, ) : Parcelable { /** @@ -573,11 +576,6 @@ 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. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt index 112395651..a3a52a7d9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt @@ -8,11 +8,8 @@ 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.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -45,7 +42,9 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPassword import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenOverwritePasskeyConfirmationDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenPinDialog import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState +import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenPullToRefreshState import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager import com.x8bit.bitwarden.ui.platform.composition.LocalFido2CompletionManager @@ -65,7 +64,6 @@ import kotlinx.collections.immutable.toImmutableList /** * Displays the vault item listing screen. */ -@OptIn(ExperimentalMaterial3Api::class) @Composable @Suppress("LongMethod", "CyclomaticComplexMethod") fun VaultItemListingScreen( @@ -89,18 +87,17 @@ fun VaultItemListingScreen( VaultItemListingUserVerificationHandlers.create(viewModel = viewModel) } - val pullToRefreshState = rememberPullToRefreshState().takeIf { state.isPullToRefreshEnabled } - LaunchedEffect(key1 = pullToRefreshState?.isRefreshing) { - if (pullToRefreshState?.isRefreshing == true) { - viewModel.trySendAction(VaultItemListingsAction.RefreshPull) - } - } + val pullToRefreshState = rememberBitwardenPullToRefreshState( + isEnabled = state.isPullToRefreshEnabled, + isRefreshing = state.isRefreshing, + onRefresh = remember(viewModel) { + { 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) } @@ -388,7 +385,7 @@ private fun VaultItemListingDialogs( @Composable private fun VaultItemListingScaffold( state: VaultItemListingState, - pullToRefreshState: PullToRefreshState?, + pullToRefreshState: BitwardenPullToRefreshState, vaultItemListingHandlers: VaultItemListingHandlers, ) { var isAccountMenuVisible by rememberSaveable { mutableStateOf(false) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index baa291f2f..6ddab13af 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -133,6 +133,7 @@ class VaultItemListingViewModel @Inject constructor( fido2CredentialAssertionRequest = fido2AssertionData?.fido2AssertionRequest, fido2GetCredentialsRequest = fido2GetCredentialsData?.fido2GetCredentialsRequest, isPremium = userState.activeAccount.isPremium, + isRefreshing = false, ) }, ) { @@ -298,6 +299,7 @@ class VaultItemListingViewModel @Inject constructor( } 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() @@ -1232,7 +1234,7 @@ class VaultItemListingViewModel @Inject constructor( ) } } - sendEvent(VaultItemListingEvent.DismissPullToRefresh) + mutableStateFlow.update { it.copy(isRefreshing = false) } } private fun vaultLoadedReceive(vaultData: DataState.Loaded) { @@ -1261,7 +1263,7 @@ class VaultItemListingViewModel @Inject constructor( ), ) } - ?: sendEvent(VaultItemListingEvent.DismissPullToRefresh) + ?: mutableStateFlow.update { it.copy(isRefreshing = false) } } private fun vaultLoadingReceive() { @@ -1286,7 +1288,7 @@ class VaultItemListingViewModel @Inject constructor( ) } } - sendEvent(VaultItemListingEvent.DismissPullToRefresh) + mutableStateFlow.update { it.copy(isRefreshing = false) } } private fun vaultPendingReceive(vaultData: DataState.Pending) { @@ -1640,6 +1642,7 @@ data class VaultItemListingState( val fido2GetCredentialsRequest: Fido2GetCredentialsRequest? = null, val hasMasterPassword: Boolean, val isPremium: Boolean, + val isRefreshing: Boolean, ) { /** * Whether or not this represents a listing screen for autofill. @@ -2027,11 +2030,6 @@ 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. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt index 566eef70a..539281891 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt @@ -16,8 +16,6 @@ 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 @@ -52,7 +50,9 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState +import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenPullToRefreshState import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager @@ -70,7 +70,6 @@ import kotlinx.collections.immutable.toImmutableList /** * The vault screen for the application. */ -@OptIn(ExperimentalMaterial3Api::class) @Suppress("LongMethod") @Composable fun VaultScreen( @@ -88,16 +87,15 @@ fun VaultScreen( ) { 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(VaultAction.RefreshPull) - } - } + val pullToRefreshState = rememberBitwardenPullToRefreshState( + isEnabled = state.isPullToRefreshEnabled, + isRefreshing = state.isRefreshing, + onRefresh = remember(viewModel) { + { viewModel.trySendAction(VaultAction.RefreshPull) } + }, + ) EventsEffect(viewModel = viewModel) { event -> when (event) { - VaultEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh() - VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen() VaultEvent.NavigateToVaultSearchScreen -> onNavigateToSearchVault(SearchType.Vault.All) @@ -167,7 +165,7 @@ private fun VaultScreenPushNotifications( @Composable private fun VaultScreenScaffold( state: VaultState, - pullToRefreshState: PullToRefreshState?, + pullToRefreshState: BitwardenPullToRefreshState, vaultHandlers: VaultHandlers, onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit, ) { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt index dcf284bb9..a34f948d6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt @@ -91,6 +91,7 @@ class VaultViewModel @Inject constructor( baseIconUrl = userState.activeAccount.environment.environmentUrlData.baseIconUrl, hasMasterPassword = userState.activeAccount.hasMasterPassword, hideNotificationsDialog = isBuildVersionBelow(Build.VERSION_CODES.TIRAMISU) || isFdroid, + isRefreshing = false, ) }, ) { @@ -298,6 +299,7 @@ class VaultViewModel @Inject constructor( } 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() @@ -510,8 +512,8 @@ class VaultViewModel @Inject constructor( hasMasterPassword = state.hasMasterPassword, errorTitle = R.string.an_error_has_occurred.asText(), errorMessage = R.string.generic_error_message.asText(), + isRefreshing = false, ) - sendEvent(VaultEvent.DismissPullToRefresh) } private fun vaultLoadedReceive(vaultData: DataState.Loaded) { @@ -532,9 +534,9 @@ class VaultViewModel @Inject constructor( vaultFilterType = vaultFilterTypeOrDefault, ), dialog = null, + isRefreshing = false, ) } - sendEvent(VaultEvent.DismissPullToRefresh) } private fun vaultLoadingReceive() { @@ -551,8 +553,8 @@ class VaultViewModel @Inject constructor( isIconLoadingDisabled = state.isIconLoadingDisabled, hasMasterPassword = state.hasMasterPassword, errorMessage = R.string.internet_connection_required_message.asText(), + isRefreshing = false, ) - sendEvent(VaultEvent.DismissPullToRefresh) } private fun vaultPendingReceive(vaultData: DataState.Pending) { @@ -633,6 +635,7 @@ data class VaultState( val baseIconUrl: String, val isIconLoadingDisabled: Boolean, val hideNotificationsDialog: Boolean, + val isRefreshing: Boolean, ) : Parcelable { /** @@ -921,11 +924,6 @@ data class VaultState( * Models effects for the [VaultScreen]. */ sealed class VaultEvent { - /** - * Dismisses the pull-to-refresh indicator. - */ - data object DismissPullToRefresh : VaultEvent() - /** * Navigate to the Vault Search screen. */ @@ -1186,6 +1184,7 @@ private fun MutableStateFlow.updateToErrorStateOrDialog( hasMasterPassword: Boolean, errorTitle: Text, errorMessage: Text, + isRefreshing: Boolean, ) { this.update { if (vaultData != null) { @@ -1201,6 +1200,7 @@ private fun MutableStateFlow.updateToErrorStateOrDialog( title = errorTitle, message = errorMessage, ), + isRefreshing = isRefreshing, ) } else { it.copy( @@ -1208,6 +1208,7 @@ private fun MutableStateFlow.updateToErrorStateOrDialog( message = errorMessage, ), dialog = null, + isRefreshing = isRefreshing, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt index 2167734f6..5ec79df0f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt @@ -8,11 +8,11 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -60,7 +60,7 @@ fun VaultVerificationCodeItem( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = MaterialTheme.colorScheme.primary), + indication = ripple(color = MaterialTheme.colorScheme.primary), onClick = onItemClick, ) .defaultMinSize(minHeight = 72.dp) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt index 6a56f0a27..9ea88e259 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt @@ -7,10 +7,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api 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 @@ -31,6 +29,7 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderTextWithSupportLabel import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.vault.feature.verificationcode.handlers.VerificationCodeHandlers import kotlinx.collections.immutable.ImmutableList @@ -54,16 +53,16 @@ fun VerificationCodeScreen( VerificationCodeHandlers.create(viewModel) } - val pullToRefreshState = rememberPullToRefreshState().takeIf { state.isPullToRefreshEnabled } - LaunchedEffect(key1 = pullToRefreshState?.isRefreshing) { - if (pullToRefreshState?.isRefreshing == true) { - viewModel.trySendAction(VerificationCodeAction.RefreshPull) - } - } + val pullToRefreshState = rememberBitwardenPullToRefreshState( + isEnabled = state.isPullToRefreshEnabled, + isRefreshing = state.isRefreshing, + onRefresh = remember(viewModel) { + { viewModel.trySendAction(VerificationCodeAction.RefreshPull) } + }, + ) EventsEffect(viewModel = viewModel) { event -> when (event) { - is VerificationCodeEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh() is VerificationCodeEvent.NavigateBack -> onNavigateBack() is VerificationCodeEvent.NavigateToVaultItem -> onNavigateToVaultItemScreen(event.id) is VerificationCodeEvent.NavigateToVaultSearchScreen -> { @@ -74,7 +73,6 @@ fun VerificationCodeScreen( VerificationCodeDialogs(dialogState = state.dialogState) - @OptIn(ExperimentalMaterial3Api::class) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) BitwardenScaffold( modifier = Modifier diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt index 529191a7a..61cc10318 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt @@ -49,6 +49,7 @@ class VerificationCodeViewModel @Inject constructor( vaultFilterType = vaultRepository.vaultFilterType, viewState = VerificationCodeState.ViewState.Loading, dialogState = null, + isRefreshing = false, ) }, ) { @@ -123,6 +124,7 @@ class VerificationCodeViewModel @Inject constructor( } 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() @@ -228,7 +230,7 @@ class VerificationCodeViewModel @Inject constructor( ) } } - sendEvent(VerificationCodeEvent.DismissPullToRefresh) + mutableStateFlow.update { it.copy(isRefreshing = false) } } private fun vaultPendingReceive( @@ -248,7 +250,7 @@ class VerificationCodeViewModel @Inject constructor( verificationCodeData = verificationCodeData.data, clearDialogState = true, ) - sendEvent(VerificationCodeEvent.DismissPullToRefresh) + mutableStateFlow.update { it.copy(isRefreshing = false) } } private fun vaultLoadingReceive() { @@ -271,7 +273,7 @@ class VerificationCodeViewModel @Inject constructor( ) } } - sendEvent(VerificationCodeEvent.DismissPullToRefresh) + mutableStateFlow.update { it.copy(isRefreshing = false) } } private fun updateStateWithVerificationCodeData( @@ -339,6 +341,7 @@ data class VerificationCodeState( val baseIconUrl: String, val dialogState: DialogState?, val isPullToRefreshSettingEnabled: Boolean, + val isRefreshing: Boolean, ) : Parcelable { /** @@ -421,12 +424,6 @@ data class VerificationCodeDisplayItem( * Models events for the [VerificationCodeScreen]. */ sealed class VerificationCodeEvent { - - /** - * Dismisses the pull-to-refresh indicator. - */ - data object DismissPullToRefresh : VerificationCodeEvent() - /** * Navigate back. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceScreenTest.kt index 2ed287171..3ce7043be 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceScreenTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice import android.net.Uri import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.isDialog @@ -124,12 +125,14 @@ class LoginWithDeviceScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy(viewState = LoginWithDeviceState.ViewState.Loading) } - composeTestRule.onNode(isProgressBar).assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy(viewState = DEFAULT_STATE.viewState) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt index 51cb379c7..ab96dc131 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.platform.feature.search import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.filterToOne @@ -125,13 +126,15 @@ class SearchScreenTest : BaseComposeTest() { @Test fun `progressbar should be displayed according to state`() { mutableStateFlow.update { DEFAULT_STATE } - composeTestRule.onNode(isProgressBar).assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy(viewState = SearchState.ViewState.Empty(message = null)) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreenTest.kt index 858db2b93..6e373b534 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsScreenTest.kt @@ -119,6 +119,7 @@ class PendingRequestsScreenTest : BaseComposeTest() { authRequests = emptyList(), viewState = PendingRequestsState.ViewState.Loading, isPullToRefreshSettingEnabled = false, + isRefreshing = false, ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModelTest.kt index e9bc21f3f..bdea2ce7c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModelTest.kt @@ -376,6 +376,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() { authRequests = emptyList(), viewState = PendingRequestsState.ViewState.Empty, isPullToRefreshSettingEnabled = false, + isRefreshing = false, ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt index 1ab4f92fb..480a8c723 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.tools.feature.send import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.filterToOne @@ -263,22 +264,26 @@ class SendScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy(viewState = SendState.ViewState.Loading) } - composeTestRule.onNode(isProgressBar).assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy(viewState = SendState.ViewState.Empty) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) mutableStateFlow.update { it.copy(viewState = SendState.ViewState.Error("Fail".asText())) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) mutableStateFlow.update { it.copy(viewState = DEFAULT_CONTENT_VIEW_STATE) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test @@ -709,6 +714,7 @@ private val DEFAULT_STATE: SendState = SendState( dialogState = null, isPullToRefreshSettingEnabled = false, policyDisablesSend = false, + isRefreshing = false, ) private val DEFAULT_SEND_ITEM: SendState.ViewState.Content.SendItem = diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModelTest.kt index 25a46f3d4..1eb8972ba 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModelTest.kt @@ -313,52 +313,52 @@ class SendViewModelTest : BaseViewModelTest() { assertEquals(initialState.copy(dialogState = null), viewModel.stateFlow.value) } - @Suppress("MaxLineLength") @Test - fun `VaultRepository SendData Error should update view state to Error and emit DismissPullToRefresh`() = - runTest { - val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) - val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState)) + fun `VaultRepository SendData Error should update view state to Error`() = runTest { + val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) + val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState)) - viewModel.eventFlow.test { - mutableSendDataFlow.value = DataState.Error(Throwable("Fail")) - assertEquals(SendEvent.DismissPullToRefresh, awaitItem()) - } + viewModel.eventFlow.test { + mutableSendDataFlow.value = DataState.Error(Throwable("Fail")) + } - assertEquals( - DEFAULT_STATE.copy( - viewState = SendState.ViewState.Error( - message = R.string.generic_error_message.asText(), - ), - dialogState = null, + assertEquals( + DEFAULT_STATE.copy( + viewState = SendState.ViewState.Error( + message = R.string.generic_error_message.asText(), ), - viewModel.stateFlow.value, - ) - } + dialogState = null, + isRefreshing = false, + ), + viewModel.stateFlow.value, + ) + } @Test - fun `VaultRepository SendData Loaded should update view state and emit DismissPullToRefresh`() = - runTest { - val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) - val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState)) - val viewState = mockk() - val sendData = mockk { - every { - toViewState(Environment.Us.environmentUrlData.baseWebSendUrl) - } returns viewState - } - - viewModel.eventFlow.test { - mutableSendDataFlow.value = DataState.Loaded(sendData) - assertEquals(SendEvent.DismissPullToRefresh, awaitItem()) - } - - assertEquals( - DEFAULT_STATE.copy(viewState = viewState, dialogState = null), - viewModel.stateFlow.value, - ) + fun `VaultRepository SendData Loaded should update view state`() = runTest { + val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) + val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState)) + val viewState = mockk() + val sendData = mockk { + every { + toViewState(Environment.Us.environmentUrlData.baseWebSendUrl) + } returns viewState } + viewModel.eventFlow.test { + mutableSendDataFlow.value = DataState.Loaded(sendData) + } + + assertEquals( + DEFAULT_STATE.copy( + viewState = viewState, + dialogState = null, + isRefreshing = false, + ), + viewModel.stateFlow.value, + ) + } + @Test fun `VaultRepository SendData Loading should update view state to Loading`() { val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) @@ -372,34 +372,32 @@ class SendViewModelTest : BaseViewModelTest() { ) } - @Suppress("MaxLineLength") @Test - fun `VaultRepository SendData NoNetwork should update view state to Error and emit DismissPullToRefresh`() = - runTest { - val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) - val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState)) + fun `VaultRepository SendData NoNetwork should update view state to Error`() = runTest { + val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) + val viewModel = createViewModel(state = DEFAULT_STATE.copy(dialogState = dialogState)) - viewModel.eventFlow.test { - mutableSendDataFlow.value = DataState.NoNetwork() - assertEquals(SendEvent.DismissPullToRefresh, awaitItem()) - } - - assertEquals( - DEFAULT_STATE.copy( - viewState = SendState.ViewState.Error( - message = R.string.internet_connection_required_title - .asText() - .concat( - " ".asText(), - R.string.internet_connection_required_message.asText(), - ), - ), - dialogState = null, - ), - viewModel.stateFlow.value, - ) + viewModel.eventFlow.test { + mutableSendDataFlow.value = DataState.NoNetwork() } + assertEquals( + DEFAULT_STATE.copy( + viewState = SendState.ViewState.Error( + message = R.string.internet_connection_required_title + .asText() + .concat( + " ".asText(), + R.string.internet_connection_required_message.asText(), + ), + ), + dialogState = null, + isRefreshing = false, + ), + viewModel.stateFlow.value, + ) + } + @Test fun `VaultRepository SendData Pending should update view state`() { val dialogState = SendState.DialogState.Loading(R.string.syncing.asText()) @@ -470,4 +468,5 @@ private val DEFAULT_STATE: SendState = SendState( dialogState = null, isPullToRefreshSettingEnabled = false, policyDisablesSend = false, + isRefreshing = false, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt index 46d371ba6..ad22465bf 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt @@ -1,6 +1,8 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend +import androidx.compose.ui.semantics.SemanticsActions import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotDisplayed @@ -19,6 +21,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performSemanticsAction import androidx.compose.ui.test.performTextInput import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest @@ -409,7 +412,9 @@ class AddSendScreenTest : BaseComposeTest() { fun `File segmented button click should send FileTypeClick`() { composeTestRule .onNodeWithText("File") - .performClick() + // A bug prevents performClick from working here so we + // have to perform the semantic action instead. + .performSemanticsAction(SemanticsActions.OnClick) verify { viewModel.trySendAction(AddSendAction.FileTypeClick) } } @@ -417,7 +422,9 @@ class AddSendScreenTest : BaseComposeTest() { fun `Text segmented button click should send TextTypeClick`() { composeTestRule .onAllNodesWithText("Text")[0] - .performClick() + // A bug prevents performClick from working here so we + // have to perform the semantic action instead. + .performSemanticsAction(SemanticsActions.OnClick) verify { viewModel.trySendAction(AddSendAction.TextTypeClick) } } @@ -912,17 +919,20 @@ class AddSendScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy(viewState = AddSendState.ViewState.Loading) } - composeTestRule.onNode(isProgressBar).assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy(viewState = AddSendState.ViewState.Error("Fail".asText())) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) mutableStateFlow.update { it.copy(viewState = DEFAULT_VIEW_STATE) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index 5b2aa727e..70fcc79a6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit import androidx.compose.ui.geometry.Offset import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotDisplayed @@ -580,12 +581,14 @@ class VaultAddEditScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy(viewState = VaultAddEditState.ViewState.Loading) } - composeTestRule.onNode(isProgressBar).assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy(viewState = VaultAddEditState.ViewState.Error("Fail".asText())) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) mutableStateFlow.update { it.copy( @@ -596,7 +599,8 @@ class VaultAddEditScreenTest : BaseComposeTest() { ), ) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsScreenTest.kt index d04577fc4..4ef370de4 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsScreenTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.attachments import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.filterToOne import androidx.compose.ui.test.hasAnyAncestor @@ -89,17 +90,20 @@ class AttachmentsScreenTest : BaseComposeTest() { @Test fun `progressbar should be displayed according to state`() { mutableStateFlow.update { it.copy(viewState = AttachmentsState.ViewState.Loading) } - composeTestRule.onNode(isProgressBar).assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy(viewState = AttachmentsState.ViewState.Error("Fail".asText())) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) mutableStateFlow.update { it.copy(viewState = DEFAULT_CONTENT_WITHOUT_ATTACHMENTS) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt index db62de1ca..52126a054 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onSiblings import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performScrollTo import androidx.compose.ui.test.performTextInput import androidx.core.net.toUri import com.x8bit.bitwarden.R @@ -1364,9 +1363,8 @@ class VaultItemScreenTest : BaseComposeTest() { ) } - composeTestRule - .onNode(isProgressBar) - .assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) composeTestRule .onNodeWithText("Passkey") @@ -1411,9 +1409,8 @@ class VaultItemScreenTest : BaseComposeTest() { ) } - composeTestRule - .onNode(isProgressBar) - .assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) composeTestRule .onNodeWithContentDescription("Copy TOTP") @@ -1434,10 +1431,8 @@ class VaultItemScreenTest : BaseComposeTest() { ) } - composeTestRule - .onNode(isProgressBar) - .performScrollTo() - .assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) composeTestRule .onNodeWithContentDescription("Copy TOTP") @@ -1452,10 +1447,8 @@ class VaultItemScreenTest : BaseComposeTest() { ) } - composeTestRule - .onNode(isProgressBar) - .performScrollTo() - .assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) composeTestRule .onNodeWithContentDescription("Copy TOTP") @@ -1471,9 +1464,8 @@ class VaultItemScreenTest : BaseComposeTest() { ) } - composeTestRule - .onNode(isProgressBar) - .assertIsNotDisplayed() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) composeTestRule .onNodeWithContentDescription("Copy TOTP") @@ -1680,19 +1672,22 @@ class VaultItemScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy(viewState = VaultItemState.ViewState.Loading) } - composeTestRule.onNode(isProgressBar).assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy(viewState = VaultItemState.ViewState.Error("Fail".asText())) } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) mutableStateFlow.update { currentState -> updateLoginType(currentState) { copy(totpCodeItemData = null) } } - composeTestRule.onNode(isProgressBar).assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt index 5347ed080..fb89f2ad2 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertTextEquals @@ -490,9 +491,8 @@ class VaultItemListingScreenTest : BaseComposeTest() { fun `progressbar should be displayed according to state`() { mutableStateFlow.update { DEFAULT_STATE } - composeTestRule - .onNode(isProgressBar) - .assertIsDisplayed() + // There are 2 because of the pull-to-refresh + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(2) mutableStateFlow.update { it.copy( @@ -504,9 +504,8 @@ class VaultItemListingScreenTest : BaseComposeTest() { ) } - composeTestRule - .onNode(isProgressBar) - .assertDoesNotExist() + // Only pull-to-refresh remains + composeTestRule.onAllNodes(isProgressBar).assertCountEquals(1) } @Test @@ -2059,6 +2058,7 @@ private val DEFAULT_STATE = VaultItemListingState( policyDisablesSend = false, hasMasterPassword = true, isPremium = false, + isRefreshing = false, ) private val STATE_FOR_AUTOFILL = DEFAULT_STATE.copy( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index a3e8bfba7..62ef30a7a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -498,10 +498,6 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { VaultItemListingState.DialogState.Loading(R.string.saving.asText()), viewModel.stateFlow.value.dialogState, ) - assertEquals( - VaultItemListingEvent.DismissPullToRefresh, - awaitItem(), - ) assertEquals( VaultItemListingEvent.Fido2UserVerification( isRequired = true, @@ -1195,40 +1191,36 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { } @Test - fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() = - runTest { - setupMockUri() + fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() = runTest { + setupMockUri() - 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)), - ), - ) + 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)), + ), + ) - val viewModel = createVaultItemListingViewModel() + val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals( - createVaultItemListingState( - viewState = VaultItemListingState.ViewState.Content( - displayCollectionList = emptyList(), - displayItemList = listOf( - createMockDisplayItemForCipher(number = 1) - .copy(secondSubtitleTestTag = "PasskeySite"), - ), - displayFolderList = emptyList(), + assertEquals( + createVaultItemListingState( + viewState = VaultItemListingState.ViewState.Content( + displayCollectionList = emptyList(), + displayItemList = listOf( + createMockDisplayItemForCipher(number = 1) + .copy(secondSubtitleTestTag = "PasskeySite"), ), + displayFolderList = emptyList(), ), - viewModel.stateFlow.value, - ) - } + ), + viewModel.stateFlow.value, + ) + } @Suppress("MaxLineLength") @Test @@ -1475,10 +1467,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ), ) val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( @@ -1504,10 +1495,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( @@ -1626,10 +1615,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.Error( @@ -1656,10 +1643,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.Content( @@ -1691,10 +1676,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( @@ -1721,10 +1704,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( @@ -1743,10 +1724,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.Error( @@ -1777,10 +1756,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.Content( @@ -1811,10 +1788,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( @@ -1840,10 +1815,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { val viewModel = createVaultItemListingViewModel() - viewModel.eventFlow.test { - mutableVaultDataStateFlow.tryEmit(value = dataState) - assertEquals(VaultItemListingEvent.DismissPullToRefresh, awaitItem()) - } + mutableVaultDataStateFlow.tryEmit(value = dataState) + assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( @@ -3763,6 +3736,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { hasMasterPassword = true, fido2CredentialRequest = null, isPremium = true, + isRefreshing = false, ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt index 14a31390e..f71db9143 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt @@ -1190,6 +1190,7 @@ private val DEFAULT_STATE: VaultState = VaultState( isIconLoadingDisabled = false, hasMasterPassword = true, hideNotificationsDialog = true, + isRefreshing = false, ) private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultState.ViewState.Content( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt index 9a817b860..10dd52dc4 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt @@ -612,7 +612,6 @@ class VaultViewModelTest : BaseViewModelTest() { VaultEvent.ShowToast(R.string.syncing_complete.asText()), awaitItem(), ) - assertEquals(VaultEvent.DismissPullToRefresh, awaitItem()) } } @@ -660,7 +659,6 @@ class VaultViewModelTest : BaseViewModelTest() { VaultEvent.ShowToast(R.string.syncing_complete.asText()), awaitItem(), ) - assertEquals(VaultEvent.DismissPullToRefresh, awaitItem()) } } @@ -1591,4 +1589,5 @@ private fun createMockVaultState( isIconLoadingDisabled = false, hasMasterPassword = true, hideNotificationsDialog = true, + isRefreshing = false, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt index 68c1be3b9..920659eaa 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt @@ -407,4 +407,5 @@ private val DEFAULT_STATE = VerificationCodeState( baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl, isPullToRefreshSettingEnabled = false, dialogState = null, + isRefreshing = false, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt index de0be3083..a6912d6b1 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt @@ -171,7 +171,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { } } - @Suppress("MaxLineLength") @Test fun `AuthCodeFlow Pending with data should update state to Content`() { setupMockUri() @@ -197,7 +196,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { ) } - @Suppress("MaxLineLength") @Test fun `AuthCodeFlow Pending with no data should call NavigateBack to go to the vault screen`() = runTest { @@ -216,7 +214,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { } } - @Suppress("MaxLineLength") @Test fun `AuthCodeFlow Error with data should update state to Content`() = runTest { setupMockUri() @@ -233,10 +230,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { ), ) - viewModel.eventFlow.test { - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) - } - assertEquals( createVerificationCodeState( viewState = VerificationCodeState.ViewState.Content( @@ -247,7 +240,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { ) } - @Suppress("MaxLineLength") @Test fun `AuthCodeFlow Error with no data should call NavigateBack to go to the vault screen`() = runTest { @@ -264,11 +256,9 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { assertEquals(VerificationCodeEvent.NavigateBack, awaitItem()) - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) } } - @Suppress("MaxLineLength") @Test fun `AuthCodeFlow Error with null data should show error screen`() = runTest { setupMockUri() @@ -282,10 +272,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { ), ) - viewModel.eventFlow.test { - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) - } - assertEquals( createVerificationCodeState( viewState = VerificationCodeState.ViewState.Error( @@ -308,7 +294,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { assertEquals(VerificationCodeEvent.NavigateBack, awaitItem()) - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) } } @@ -350,10 +335,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { ), ) - viewModel.eventFlow.test { - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) - } - assertEquals( createVerificationCodeState( viewState = VerificationCodeState.ViewState.Content( @@ -375,7 +356,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { assertEquals(VerificationCodeEvent.NavigateBack, awaitItem()) - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) } } @@ -394,10 +374,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { ), ) - viewModel.eventFlow.test { - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) - } - assertEquals( createVerificationCodeState( viewState = VerificationCodeState.ViewState.Content( @@ -426,7 +402,6 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { assertEquals(VerificationCodeEvent.NavigateBack, awaitItem()) - assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem()) } } @@ -527,6 +502,7 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl, dialogState = null, isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value, + isRefreshing = false, ) private fun createDisplayItemList() = listOf( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 19afb55a7..d2e3a1340 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ androidxActivity = "1.9.1" androidXBiometrics = "1.2.0-alpha05" androidxBrowser = "1.8.0" androidxCamera = "1.3.4" -androidxComposeBom = "2024.08.00" +androidxComposeBom = "2024.09.00" androidxCore = "1.13.1" androidxCredentials = "1.2.2" androidxHiltNavigationCompose = "1.2.0"