From 99399695ff8952095fc7bd30aa2464dea02fd71b Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 31 Oct 2024 17:34:46 +0100 Subject: [PATCH 1/6] Adds grid layout for bigger screens --- .../ui/dashboard/DashboardContainer.kt | 149 ++++++++++++------ .../android/ui/dashboard/DashboardFragment.kt | 5 +- 2 files changed, 106 insertions(+), 48 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt index b2834fbd1c4..10096e1c5d2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt @@ -12,6 +12,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -20,6 +23,8 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -28,11 +33,17 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.woocommerce.android.R +import com.woocommerce.android.extensions.WindowSizeClass import com.woocommerce.android.model.DashboardWidget import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.component.WCOutlinedButton import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.OpenRangePicker +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel.ConfigurableWidget +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel.ShareStoreWidget import com.woocommerce.android.ui.dashboard.blaze.DashboardBlazeCard import com.woocommerce.android.ui.dashboard.coupons.DashboardCouponsCard import com.woocommerce.android.ui.dashboard.google.DashboardGoogleAdsCard @@ -49,70 +60,114 @@ import com.woocommerce.android.ui.main.MainActivityViewModel fun DashboardContainer( mainActivityViewModel: MainActivityViewModel, dashboardViewModel: DashboardViewModel, - blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher + blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher, + windowSizeClass: WindowSizeClass ) { dashboardViewModel.dashboardWidgets.observeAsState().value?.let { widgets -> - WidgetList( + DashboardWidgets( widgetUiModels = widgets, mainActivityViewModel = mainActivityViewModel, dashboardViewModel = dashboardViewModel, - blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher + blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colors.surface) + .padding(vertical = dimensionResource(id = R.dimen.major_100)), + numberOfColumns = if (windowSizeClass != WindowSizeClass.Compact) 2 else 1 ) } } @Composable -private fun WidgetList( +private fun DashboardWidgets( widgetUiModels: List, mainActivityViewModel: MainActivityViewModel, dashboardViewModel: DashboardViewModel, - blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher + blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher, + modifier: Modifier = Modifier, + numberOfColumns: Int = 1 ) { - Column( - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.major_100)), - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.surface) - .padding(vertical = dimensionResource(id = R.dimen.major_100)) - ) { - val widgetModifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - widgetUiModels.forEach { - AnimatedVisibility(it.isVisible) { - when (it) { - is DashboardViewModel.DashboardWidgetUiModel.ConfigurableWidget -> { - ConfigurableWidgetCard( - widgetUiModel = it, - mainActivityViewModel = mainActivityViewModel, - dashboardViewModel = dashboardViewModel, - blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher, - modifier = widgetModifier - ) - } + val widgetModifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + if (numberOfColumns == 1) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + widgetUiModels.forEach { + AnimatedVisibility(it.isVisible) { + DashboardWidgetCard( + it, + mainActivityViewModel, + dashboardViewModel, + blazeCampaignCreationDispatcher, + widgetModifier + ) + } + } + } + } else { + val nestedScrollInterop = rememberNestedScrollInteropConnection() + LazyVerticalStaggeredGrid( + modifier = modifier.nestedScroll(nestedScrollInterop), + columns = StaggeredGridCells.Adaptive(400.dp), + verticalItemSpacing = 16.dp, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + items(widgetUiModels) { widget -> + AnimatedVisibility(widget.isVisible) { + DashboardWidgetCard( + widget, + mainActivityViewModel, + dashboardViewModel, + blazeCampaignCreationDispatcher, + widgetModifier + ) + } + } + } + } +} - is DashboardViewModel.DashboardWidgetUiModel.ShareStoreWidget -> { - ShareStoreCard( - onShareClicked = it.onShareClicked, - modifier = widgetModifier - ) - } +@Composable +private fun DashboardWidgetCard( + it: DashboardWidgetUiModel, + mainActivityViewModel: MainActivityViewModel, + dashboardViewModel: DashboardViewModel, + blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher, + widgetModifier: Modifier +) { + when (it) { + is ConfigurableWidget -> { + ConfigurableWidgetCard( + widgetUiModel = it, + mainActivityViewModel = mainActivityViewModel, + dashboardViewModel = dashboardViewModel, + blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher, + modifier = widgetModifier + ) + } - is DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget -> { - FeedbackCard( - widget = it, - modifier = widgetModifier - ) - } + is ShareStoreWidget -> { + ShareStoreCard( + onShareClicked = it.onShareClicked, + modifier = widgetModifier + ) + } - is DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard -> { - NewWidgetsCard( - state = it, - modifier = widgetModifier - ) - } - } - } + is FeedbackWidget -> { + FeedbackCard( + widget = it, + modifier = widgetModifier + ) + } + + is NewWidgetsCard -> { + NewWidgetsCard( + state = it, + modifier = widgetModifier + ) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt index e9eb982ea13..7443c3ec363 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt @@ -26,6 +26,7 @@ import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentDashboardBinding +import com.woocommerce.android.extensions.WindowSizeClass import com.woocommerce.android.extensions.getColorCompat import com.woocommerce.android.extensions.handleNotice import com.woocommerce.android.extensions.handleResult @@ -34,6 +35,7 @@ import com.woocommerce.android.extensions.scrollStartEvents import com.woocommerce.android.extensions.showDateRangePicker import com.woocommerce.android.extensions.startHelpActivity import com.woocommerce.android.extensions.verticalOffsetChanges +import com.woocommerce.android.extensions.windowSizeClass import com.woocommerce.android.model.DashboardWidget import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.tools.SelectedSite @@ -137,7 +139,8 @@ class DashboardFragment : DashboardContainer( mainActivityViewModel = mainActivityViewModel, dashboardViewModel = dashboardViewModel, - blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher + blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher, + windowSizeClass = requireContext().windowSizeClass ) } } From 83227a195107ba0aa47d1b2bd84855df505a6c67 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 5 Dec 2024 18:26:12 +0100 Subject: [PATCH 2/6] Extract dashboard container out of the xml NestedScroll and SwipeToRefresh --- .../ui/dashboard/DashboardContainer.kt | 15 +++--- .../main/res/layout/fragment_dashboard.xml | 48 ++++++++++--------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt index 10096e1c5d2..b0f33562a94 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt @@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items @@ -90,15 +92,17 @@ private fun DashboardWidgets( val widgetModifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) + val nestedScrollInterop = rememberNestedScrollInteropConnection() + if (numberOfColumns == 1) { - Column( - modifier = modifier, + LazyColumn( + modifier = modifier.nestedScroll(nestedScrollInterop), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - widgetUiModels.forEach { - AnimatedVisibility(it.isVisible) { + items(widgetUiModels) { widget -> + AnimatedVisibility(widget.isVisible) { DashboardWidgetCard( - it, + widget, mainActivityViewModel, dashboardViewModel, blazeCampaignCreationDispatcher, @@ -108,7 +112,6 @@ private fun DashboardWidgets( } } } else { - val nestedScrollInterop = rememberNestedScrollInteropConnection() LazyVerticalStaggeredGrid( modifier = modifier.nestedScroll(nestedScrollInterop), columns = StaggeredGridCells.Adaptive(400.dp), diff --git a/WooCommerce/src/main/res/layout/fragment_dashboard.xml b/WooCommerce/src/main/res/layout/fragment_dashboard.xml index e846b7f3bc6..f9f18a8b733 100644 --- a/WooCommerce/src/main/res/layout/fragment_dashboard.xml +++ b/WooCommerce/src/main/res/layout/fragment_dashboard.xml @@ -13,6 +13,31 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + + + - - - - - - - - From 0eca83cc5d3fdb6512493922a60b80a4deb83807 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 5 Dec 2024 19:05:36 +0100 Subject: [PATCH 3/6] Add a composable implementation of swipe to refresh --- .../ui/dashboard/DashboardContainer.kt | 43 +++++++++++++------ .../android/ui/dashboard/DashboardFragment.kt | 8 +--- .../ui/dashboard/DashboardViewModel.kt | 22 +++++++++- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt index b0f33562a94..73219c6e2e5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,8 +19,12 @@ import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.livedata.observeAsState @@ -58,25 +63,37 @@ import com.woocommerce.android.ui.dashboard.stock.DashboardProductStockCard import com.woocommerce.android.ui.dashboard.topperformers.DashboardTopPerformersWidgetCard import com.woocommerce.android.ui.main.MainActivityViewModel +@OptIn(ExperimentalMaterialApi::class) @Composable fun DashboardContainer( mainActivityViewModel: MainActivityViewModel, dashboardViewModel: DashboardViewModel, blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher, - windowSizeClass: WindowSizeClass + windowSizeClass: WindowSizeClass, ) { - dashboardViewModel.dashboardWidgets.observeAsState().value?.let { widgets -> - DashboardWidgets( - widgetUiModels = widgets, - mainActivityViewModel = mainActivityViewModel, - dashboardViewModel = dashboardViewModel, - blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher, - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.surface) - .padding(vertical = dimensionResource(id = R.dimen.major_100)), - numberOfColumns = if (windowSizeClass != WindowSizeClass.Compact) 2 else 1 - ) + dashboardViewModel.dashboardCardsState.observeAsState().value?.let { state -> + + val pullRefreshState = rememberPullRefreshState(state.isRefreshing, dashboardViewModel::onPullToRefresh) + Box(Modifier.pullRefresh(pullRefreshState)) { + DashboardWidgets( + widgetUiModels = state.widgets, + mainActivityViewModel = mainActivityViewModel, + dashboardViewModel = dashboardViewModel, + blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colors.surface) + .padding(vertical = dimensionResource(id = R.dimen.major_100)), + numberOfColumns = if (windowSizeClass != WindowSizeClass.Compact) 2 else 1 + ) + + PullRefreshIndicator( + refreshing = state.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + contentColor = MaterialTheme.colors.primary, + ) + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt index 7443c3ec363..a22c3ac0677 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt @@ -26,7 +26,6 @@ import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentDashboardBinding -import com.woocommerce.android.extensions.WindowSizeClass import com.woocommerce.android.extensions.getColorCompat import com.woocommerce.android.extensions.handleNotice import com.woocommerce.android.extensions.handleResult @@ -200,10 +199,10 @@ class DashboardFragment : dashboardViewModel.jetpackBenefitsBannerState.observe(viewLifecycleOwner) { jetpackBenefitsBanner -> onVisitorStatsUnavailable(jetpackBenefitsBanner) } - dashboardViewModel.dashboardWidgets.observe(viewLifecycleOwner) { widgets -> + dashboardViewModel.dashboardCardsState.observe(viewLifecycleOwner) { state -> // Show banners only if onboarding list is NOT displayed if ( - widgets.none { + state.widgets.none { (it as? DashboardWidgetUiModel.ConfigurableWidget)?.widget?.type == DashboardWidget.Type.ONBOARDING } ) { @@ -213,9 +212,6 @@ class DashboardFragment : dashboardViewModel.hasNewWidgets.observe(viewLifecycleOwner) { hasNewWidgets -> editButtonBadge.isVisible = hasNewWidgets } - dashboardViewModel.isRefreshingOnBackground.observe(viewLifecycleOwner) { isRefreshing -> - binding.myStoreRefreshLayout.isRefreshing = isRefreshing - } } private fun setupResultHandlers() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt index 0edbf70b694..8ea0eb66dc3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt @@ -105,19 +105,32 @@ class DashboardViewModel @Inject constructor( feedbackPrefs.userFeedbackIsDueObservable ) { configurableWidgets, hasNewWidgets, userFeedbackIsDue -> mapWidgetsToUiModels(configurableWidgets, hasNewWidgets, userFeedbackIsDue) - }.asLiveData() + } val hasNewWidgets = dashboardRepository.hasNewWidgets.asLiveData() private val refreshingOnBackground = MutableStateFlow(-1) val isRefreshingOnBackground = refreshingOnBackground.map { it > -1 }.asLiveData() - fun displayRefreshingIndicator() { refreshingOnBackground.value += 1 } + fun displayRefreshingIndicator() { + refreshingOnBackground.value += 1 + } + fun hideRefreshingIndicator() { val value = (refreshingOnBackground.value - 1).coerceAtLeast(-1) refreshingOnBackground.value = value } + val dashboardCardsState = combine( + dashboardWidgets, + refreshingOnBackground.map { it > -1 } + ) { widgets, isRefreshing -> + DashboardCardsState( + widgets = widgets, + isRefreshing = isRefreshing + ) + }.asLiveData() + init { ConnectionChangeReceiver.getEventBus().register(this) @@ -364,4 +377,9 @@ class DashboardViewModel @Inject constructor( action = action ) } + + data class DashboardCardsState( + val widgets: List, + val isRefreshing: Boolean + ) } From 2b7fbfc0bec46377c48875cbf5c80af45acd5617 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 5 Dec 2024 19:46:30 +0100 Subject: [PATCH 4/6] Clean xml layout and ensure we keep the same logic in the DashboardFragment --- .../e2e/screens/mystore/StatsComponent.kt | 2 +- .../screens/mystore/TopPerformersComponent.kt | 2 +- .../android/ui/dashboard/DashboardFragment.kt | 24 +++---- .../ui/dashboard/DashboardViewModel.kt | 4 ++ .../main/res/layout/fragment_dashboard.xml | 66 +++++-------------- 5 files changed, 32 insertions(+), 66 deletions(-) diff --git a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/StatsComponent.kt b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/StatsComponent.kt index d7a302085a1..234ab82a149 100644 --- a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/StatsComponent.kt +++ b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/StatsComponent.kt @@ -6,7 +6,7 @@ import com.woocommerce.android.R import com.woocommerce.android.e2e.helpers.util.Screen import com.woocommerce.android.ui.dashboard.stats.DashboardStatsTestTags -class StatsComponent : Screen(R.id.dashboardStats_root) { +class StatsComponent : Screen(R.id.my_store_stats_container) { override fun recover() { super.recover() clickOn(R.id.dashboard) diff --git a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/TopPerformersComponent.kt b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/TopPerformersComponent.kt index c3e5abc2d91..2df6cfc3903 100644 --- a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/TopPerformersComponent.kt +++ b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/screens/mystore/TopPerformersComponent.kt @@ -6,7 +6,7 @@ import com.woocommerce.android.R import com.woocommerce.android.e2e.helpers.util.Screen import com.woocommerce.android.ui.dashboard.stats.DashboardStatsTestTags -class TopPerformersComponent : Screen(R.id.dashboardStats_root) { +class TopPerformersComponent : Screen(R.id.my_store_stats_container) { override fun recover() { super.recover() clickOn(R.id.dashboard) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt index a22c3ac0677..83f7a06dc7b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt @@ -30,7 +30,6 @@ import com.woocommerce.android.extensions.getColorCompat import com.woocommerce.android.extensions.handleNotice import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely -import com.woocommerce.android.extensions.scrollStartEvents import com.woocommerce.android.extensions.showDateRangePicker import com.woocommerce.android.extensions.startHelpActivity import com.woocommerce.android.extensions.verticalOffsetChanges @@ -52,6 +51,7 @@ import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.Fe import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.FeedbackPositiveAction import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.OpenEditWidgets import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.OpenRangePicker +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.RefreshJitm import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ShareStore import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ShowPrivacyBanner import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel @@ -145,18 +145,8 @@ class DashboardFragment : } } - binding.myStoreRefreshLayout.setOnRefreshListener { - binding.myStoreRefreshLayout.isRefreshing = false - dashboardViewModel.onPullToRefresh() - refreshJitm() - } - prepareJetpackBenefitsBanner() - binding.statsScrollView.scrollStartEvents() - .onEach { usageTracksEventEmitter.interacted() } - .launchIn(viewLifecycleOwner.lifecycleScope) - setupStateObservers() setupResultHandlers() } @@ -190,6 +180,8 @@ class DashboardFragment : is ShowSnackbar -> ToastUtils.showToast(requireContext(), event.message) + is RefreshJitm -> refreshJitm() + else -> event.isHandled = false } } @@ -324,10 +316,6 @@ class DashboardFragment : override fun getFragmentSubtitle(): String = dashboardViewModel.storeName.value ?: "" - override fun scrollToTop() { - binding.statsScrollView.smoothScrollTo(0, 0) - } - private fun handleFeedbackRequestPositiveClick() { // Request a ReviewInfo object from the Google Reviews API. If this fails // we just move on as there isn't anything we can do. @@ -361,7 +349,11 @@ class DashboardFragment : ) } - override fun shouldExpandToolbar() = binding.statsScrollView.scrollY == 0 + override fun shouldExpandToolbar() = true + + override fun scrollToTop() { + return + } @OptIn(ExperimentalBadgeUtils::class) override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt index 8ea0eb66dc3..2df480a70b5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt @@ -25,6 +25,7 @@ import com.woocommerce.android.tools.SiteConnectionType import com.woocommerce.android.tools.connectionType import com.woocommerce.android.ui.analytics.ranges.StatsTimeRangeSelection.SelectionType import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.OpenEditWidgets +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.RefreshJitm import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard import com.woocommerce.android.ui.dashboard.data.DashboardRepository import com.woocommerce.android.ui.prefs.privacy.banner.domain.ShouldShowPrivacyBanner @@ -170,6 +171,7 @@ class DashboardViewModel @Inject constructor( usageTracksEventEmitter.interacted() analyticsTrackerWrapper.track(AnalyticsEvent.DASHBOARD_PULLED_TO_REFRESH) _refreshTrigger.tryEmit(RefreshEvent(isForced = true)) + triggerEvent(RefreshJitm) } fun onResume() { @@ -355,6 +357,8 @@ class DashboardViewModel @Inject constructor( data object FeedbackPositiveAction : DashboardEvent() data object FeedbackNegativeAction : DashboardEvent() + + data object RefreshJitm : DashboardEvent() } data class RefreshEvent(val isForced: Boolean = false) diff --git a/WooCommerce/src/main/res/layout/fragment_dashboard.xml b/WooCommerce/src/main/res/layout/fragment_dashboard.xml index f9f18a8b733..922c6a98a93 100644 --- a/WooCommerce/src/main/res/layout/fragment_dashboard.xml +++ b/WooCommerce/src/main/res/layout/fragment_dashboard.xml @@ -1,59 +1,29 @@ - - - - - - - - - - - - + android:layout_marginBottom="@dimen/minor_100" /> - - - - - - - + + - - + + From 4bbe0830d43fa8a0c719f214d5aba0e21ffadcfb Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 6 Feb 2025 18:53:42 +0100 Subject: [PATCH 5/6] Refactor: Adapt Dashboard card columns to Window Size Classes - Replace custom `WindowSizeClass` with `WindowWidthSizeClass` from `androidx.window.core.layout`. - Use `currentWindowAdaptiveInfo` instead custom extension that ignores screen width to calculate SizeClass - Update the number of columns in the Dashboard based on the Window Size Class. - Use a `Column` with vertical scrolling instead of `LazyColumn` when `numberOfColumns` is 1. - When multiple columns in a row instead of LazyVerticalStaggeredGrid that doesn't work well with nested scrolling interactions - Adds dependency to adaptiveAndroid --- WooCommerce/build.gradle | 3 +- .../ui/dashboard/DashboardContainer.kt | 82 ++++++++++++------- .../android/ui/dashboard/DashboardFragment.kt | 4 +- gradle/libs.versions.toml | 2 + 4 files changed, 60 insertions(+), 31 deletions(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index d34350b3e61..7170c28d608 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -22,7 +22,7 @@ fladle { "notPackage com.woocommerce.android.e2e.tests.screenshot" ] devices = [ - [ "model": "Pixel2.arm", "version": "30" ] + ["model": "Pixel2.arm", "version": "30"] ] localResultsDir = "$rootDir/build/instrumented-tests" @@ -411,6 +411,7 @@ dependencies { implementation(libs.androidx.compose.material.icons.extended) implementation(libs.androidx.compose.ui.text.google.fonts) implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.adaptive.android) implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.constraintlayout.compose) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt index 73219c6e2e5..f3299ed417d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardContainer.kt @@ -13,12 +13,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid -import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells -import androidx.compose.foundation.lazy.staggeredgrid.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -39,8 +36,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowSizeClass +import androidx.window.core.layout.WindowWidthSizeClass import com.woocommerce.android.R -import com.woocommerce.android.extensions.WindowSizeClass import com.woocommerce.android.model.DashboardWidget import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.component.WCColoredButton @@ -84,7 +82,12 @@ fun DashboardContainer( .fillMaxSize() .background(MaterialTheme.colors.surface) .padding(vertical = dimensionResource(id = R.dimen.major_100)), - numberOfColumns = if (windowSizeClass != WindowSizeClass.Compact) 2 else 1 + numberOfColumns = when (windowSizeClass.windowWidthSizeClass) { + WindowWidthSizeClass.COMPACT -> 1 + WindowWidthSizeClass.MEDIUM -> 2 + WindowWidthSizeClass.EXPANDED -> 3 + else -> 1 + } ) PullRefreshIndicator( @@ -99,7 +102,7 @@ fun DashboardContainer( @Composable private fun DashboardWidgets( - widgetUiModels: List, + widgetUiModels: List, mainActivityViewModel: MainActivityViewModel, dashboardViewModel: DashboardViewModel, blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher, @@ -112,11 +115,13 @@ private fun DashboardWidgets( val nestedScrollInterop = rememberNestedScrollInteropConnection() if (numberOfColumns == 1) { - LazyColumn( - modifier = modifier.nestedScroll(nestedScrollInterop), + Column( + modifier = modifier + .nestedScroll(nestedScrollInterop) + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - items(widgetUiModels) { widget -> + widgetUiModels.forEach { widget -> AnimatedVisibility(widget.isVisible) { DashboardWidgetCard( widget, @@ -129,27 +134,48 @@ private fun DashboardWidgets( } } } else { - LazyVerticalStaggeredGrid( - modifier = modifier.nestedScroll(nestedScrollInterop), - columns = StaggeredGridCells.Adaptive(400.dp), - verticalItemSpacing = 16.dp, - horizontalArrangement = Arrangement.SpaceEvenly, + val widgetColumns = splitWidgetsIntoColumns( + numberOfColumns = numberOfColumns, + visibleUiWidgets = widgetUiModels.filter { it.isVisible } + ) + Row( + modifier = modifier + .nestedScroll(nestedScrollInterop) + .verticalScroll(rememberScrollState()) ) { - items(widgetUiModels) { widget -> - AnimatedVisibility(widget.isVisible) { - DashboardWidgetCard( - widget, - mainActivityViewModel, - dashboardViewModel, - blazeCampaignCreationDispatcher, - widgetModifier - ) + widgetColumns.forEach { columnWidgets -> + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + columnWidgets.forEach { widget -> + AnimatedVisibility(widget.isVisible) { + DashboardWidgetCard( + widget, + mainActivityViewModel, + dashboardViewModel, + blazeCampaignCreationDispatcher, + widgetModifier + ) + } + } } } } } } +private fun splitWidgetsIntoColumns( + numberOfColumns: Int, + visibleUiWidgets: List +): MutableList> { + val widgetColumns = MutableList>(numberOfColumns) { mutableListOf() } + for ((index, item) in visibleUiWidgets.withIndex()) { + widgetColumns[index % numberOfColumns].add(item) + } + return widgetColumns +} + @Composable private fun DashboardWidgetCard( it: DashboardWidgetUiModel, @@ -194,7 +220,7 @@ private fun DashboardWidgetCard( @Composable private fun ConfigurableWidgetCard( - widgetUiModel: DashboardViewModel.DashboardWidgetUiModel.ConfigurableWidget, + widgetUiModel: DashboardWidgetUiModel.ConfigurableWidget, mainActivityViewModel: MainActivityViewModel, dashboardViewModel: DashboardViewModel, blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher, @@ -302,7 +328,7 @@ private fun ShareStoreCard( @Composable private fun FeedbackCard( - widget: DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget, + widget: DashboardWidgetUiModel.FeedbackWidget, modifier: Modifier ) { LaunchedEffect(Unit) { @@ -346,7 +372,7 @@ private fun FeedbackCard( @Composable private fun NewWidgetsCard( - state: DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard, + state: DashboardWidgetUiModel.NewWidgetsCard, modifier: Modifier ) { Column( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt index 83f7a06dc7b..7dd641c6b4b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt @@ -6,6 +6,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.annotation.OptIn +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.view.MenuProvider import androidx.core.view.isVisible @@ -33,7 +34,6 @@ import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.extensions.showDateRangePicker import com.woocommerce.android.extensions.startHelpActivity import com.woocommerce.android.extensions.verticalOffsetChanges -import com.woocommerce.android.extensions.windowSizeClass import com.woocommerce.android.model.DashboardWidget import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.tools.SelectedSite @@ -139,7 +139,7 @@ class DashboardFragment : mainActivityViewModel = mainActivityViewModel, dashboardViewModel = dashboardViewModel, blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher, - windowSizeClass = requireContext().windowSizeClass + windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass ) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b8e62008773..5550f60ecd5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -105,6 +105,7 @@ wordpress-lint = "2.1.0" wordpress-mediapicker = 'trunk-a6366298f9c24516ec5da0c9948d45d07bdb7f06' wordpress-utils = '3.15.0' zendesk = '5.0.8' +adaptiveAndroid = "1.0.0" [libraries] android-billingclient-ktx = { group = "com.android.billingclient", name = "billing-ktx", version.ref = "android-billingclient" } @@ -269,6 +270,7 @@ wordpress-aztec-main = { group = "org.wordpress", name = "aztec", version.ref = wordpress-aztec-glide-loader = { group = "org.wordpress.aztec", name = "glide-loader", version.ref = "wordpress-aztec" } wordpress-utils = { group = "org.wordpress", name = "utils", version.ref = "wordpress-utils" } zendesk-support = { group = "com.zendesk", name = "support", version.ref = "zendesk" } +androidx-adaptive-android = { group = "androidx.compose.material3.adaptive", name = "adaptive-android", version.ref = "adaptiveAndroid" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From c72ece013f4f28ae31d056698073441eac102a5a Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 7 Feb 2025 08:12:53 +0100 Subject: [PATCH 6/6] Fix unit tests after adding composable pull to refresh implementation --- .../ui/dashboard/DashboardViewModel.kt | 3 +- .../ui/dashboard/DashboardViewModelTest.kt | 62 ++++++++++--------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt index 2df480a70b5..a689282eb68 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt @@ -100,7 +100,7 @@ class DashboardViewModel @Inject constructor( jetpackBenefitsBannerState(site.connectionType) }.asLiveData() - val dashboardWidgets = combine( + private val dashboardWidgets = combine( dashboardRepository.widgets, dashboardRepository.hasNewWidgets, feedbackPrefs.userFeedbackIsDueObservable @@ -112,7 +112,6 @@ class DashboardViewModel @Inject constructor( private val refreshingOnBackground = MutableStateFlow(-1) - val isRefreshingOnBackground = refreshingOnBackground.map { it > -1 }.asLiveData() fun displayRefreshingIndicator() { refreshingOnBackground.value += 1 } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModelTest.kt index 7969b54ad8f..717f2c09ffb 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModelTest.kt @@ -165,7 +165,8 @@ class DashboardViewModelTest : BaseUnitTest() { } ) ) - whenever(appPrefsWrapper.getJetpackBenefitsDismissalDate()).thenReturn(System.currentTimeMillis() - 1000) + whenever(appPrefsWrapper.getJetpackBenefitsDismissalDate()) + .thenReturn(System.currentTimeMillis() - 1000) } val jetpackBenefitsBanner = viewModel.jetpackBenefitsBannerState.getOrAwaitValue() @@ -189,9 +190,10 @@ class DashboardViewModelTest : BaseUnitTest() { ) } - val widgets = viewModel.dashboardWidgets.captureValues().last() + val viewState = viewModel.dashboardCardsState.captureValues().last() - val shareStoreCard = widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.ShareStoreWidget } + val shareStoreCard = + viewState.widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.ShareStoreWidget } assertThat(shareStoreCard.isVisible).isTrue() } @@ -211,9 +213,10 @@ class DashboardViewModelTest : BaseUnitTest() { ) } - val widgets = viewModel.dashboardWidgets.captureValues().last() + val viewState = viewModel.dashboardCardsState.captureValues().last() - val shareStoreCard = widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.ShareStoreWidget } + val shareStoreCard = + viewState.widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.ShareStoreWidget } assertThat(shareStoreCard.isVisible).isFalse() } @@ -223,9 +226,9 @@ class DashboardViewModelTest : BaseUnitTest() { whenever(feedbackPrefs.userFeedbackIsDueObservable).thenReturn(flowOf(true)) } - val widgets = viewModel.dashboardWidgets.captureValues().last() + val viewState = viewModel.dashboardCardsState.captureValues().last() - val feedbackCard = widgets.filter { it.isVisible }[1] + val feedbackCard = viewState.widgets.filter { it.isVisible }[1] assertThat(feedbackCard).isInstanceOf(DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget::class.java) assertThat(feedbackCard.isVisible).isTrue() } @@ -236,9 +239,9 @@ class DashboardViewModelTest : BaseUnitTest() { whenever(feedbackPrefs.userFeedbackIsDueObservable).thenReturn(flowOf(false)) } - val widgets = viewModel.dashboardWidgets.captureValues().last() + val viewState = viewModel.dashboardCardsState.captureValues().last() - val feedbackCard = widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget } + val feedbackCard = viewState.widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget } assertThat(feedbackCard.isVisible).isFalse() } @@ -249,9 +252,10 @@ class DashboardViewModelTest : BaseUnitTest() { } val event = viewModel.event.runAndCaptureValues { - val widgets = viewModel.dashboardWidgets.captureValues().last() - val feedbackCard = widgets.filterIsInstance() - .first() + val viewState = viewModel.dashboardCardsState.captureValues().last() + val feedbackCard = + viewState.widgets.filterIsInstance() + .first() feedbackCard.onPositiveClick.invoke() }.last() @@ -270,9 +274,10 @@ class DashboardViewModelTest : BaseUnitTest() { } val event = viewModel.event.runAndCaptureValues { - val widgets = viewModel.dashboardWidgets.captureValues().last() - val feedbackCard = widgets.filterIsInstance() - .first() + val viewState = viewModel.dashboardCardsState.captureValues().last() + val feedbackCard = + viewState.widgets.filterIsInstance() + .first() feedbackCard.onNegativeClick.invoke() }.last() @@ -290,9 +295,9 @@ class DashboardViewModelTest : BaseUnitTest() { whenever(dashboardRepository.hasNewWidgets).thenReturn(flowOf(true)) } - val widgets = viewModel.dashboardWidgets.getOrAwaitValue() + val viewState = viewModel.dashboardCardsState.getOrAwaitValue() - val newWidgetsCard = widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard } + val newWidgetsCard = viewState.widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard } assertThat(newWidgetsCard.isVisible).isTrue() } @@ -302,22 +307,23 @@ class DashboardViewModelTest : BaseUnitTest() { whenever(dashboardRepository.hasNewWidgets).thenReturn(flowOf(false)) } - val widgets = viewModel.dashboardWidgets.getOrAwaitValue() + val viewState = viewModel.dashboardCardsState.getOrAwaitValue() - val newWidgetsCard = widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard } + val newWidgetsCard = viewState.widgets.first { it is DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard } assertThat(newWidgetsCard.isVisible).isFalse() } @Test - fun `given site is WPCom suspended, when visitor stats placeholder, then hide Jetpack benefits banner`() = testBlocking { - setup { - whenever(selectedSite.observe()) - .thenReturn(flowOf(SiteModel().apply { origin = SiteModel.ORIGIN_WPAPI })) - whenever(appPrefsWrapper.isSiteWPComSuspended).thenReturn(true) - } + fun `given site is WPCom suspended, when visitor stats placeholder, then hide Jetpack benefits banner`() = + testBlocking { + setup { + whenever(selectedSite.observe()) + .thenReturn(flowOf(SiteModel().apply { origin = SiteModel.ORIGIN_WPAPI })) + whenever(appPrefsWrapper.isSiteWPComSuspended).thenReturn(true) + } - val jetpackBenefitsBannerState = viewModel.jetpackBenefitsBannerState.getOrAwaitValue() + val jetpackBenefitsBannerState = viewModel.jetpackBenefitsBannerState.getOrAwaitValue() - assertThat(jetpackBenefitsBannerState).isNull() - } + assertThat(jetpackBenefitsBannerState).isNull() + } }