Skip to content

Commit

Permalink
Merge pull request #13488 from woocommerce/issue/11750-dashboard-big-…
Browse files Browse the repository at this point in the history
…screen-support

Issue/11750 dashboard big screen support
  • Loading branch information
JorgeMucientes authored Feb 11, 2025
2 parents 5f2395f + 88ccc00 commit 08a9a97
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 158 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*** For entries which are touching the Android Wear app's, start entry with `[WEAR]` too.
21.8
-----
- [**] Dashboard tab now supports tablet/bug screen layout [https://github.com/woocommerce/woocommerce-android/pull/13488]
- [**] Improved accessibility support (handling of increased text size) in in-person payment flows [https://github.com/woocommerce/woocommerce-android/pull/13414]
- [***] Fixed currency mismatch in order editing by ensuring products, custom amounts, and shipping lines respect the order’s currency. [https://github.com/woocommerce/woocommerce-android/pull/13483]
- [*] Updated the notification icon for better readability [https://github.com/woocommerce/woocommerce-android/pull/13516]
Expand Down
2 changes: 1 addition & 1 deletion WooCommerce/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.woocommerce.android.ui.dashboard.stats.DashboardStatsTestTags
import org.hamcrest.Matchers
import org.json.JSONArray

class DashboardScreen : Screen(R.id.my_store_refresh_layout) {
class DashboardScreen : Screen(R.id.dashboard_container) {
val stats = StatsComponent()
val topPerformers = TopPerformersComponent()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,49 @@ 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.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.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
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
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
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.compose.ui.unit.dp
import com.woocommerce.android.R
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.DashboardCardsState
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
Expand All @@ -45,70 +60,92 @@ 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
blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher,
) {
dashboardViewModel.dashboardWidgets.observeAsState().value?.let { widgets ->
WidgetList(
widgetUiModels = widgets,
mainActivityViewModel = mainActivityViewModel,
dashboardViewModel = dashboardViewModel,
blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher
)
dashboardViewModel.dashboardCardsState.observeAsState().value?.let { state ->

val pullRefreshState = rememberPullRefreshState(state.isRefreshing, dashboardViewModel::onPullToRefresh)
BoxWithConstraints(Modifier.pullRefresh(pullRefreshState)) {
val boxWithConstraintsScope = this
DashboardWidgets(
widgetUiModels = state.widgets,
mainActivityViewModel = mainActivityViewModel,
dashboardViewModel = dashboardViewModel,
blazeCampaignCreationDispatcher = blazeCampaignCreationDispatcher,
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.surface)
.padding(16.dp),
numberOfColumns = calculateColumnNumber(boxWithConstraintsScope.maxWidth, state)
)
PullRefreshIndicator(
refreshing = state.isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
contentColor = MaterialTheme.colors.primary,
)
}
}
}

@Composable
private fun WidgetList(
widgetUiModels: List<DashboardViewModel.DashboardWidgetUiModel>,
private fun DashboardWidgets(
widgetUiModels: List<DashboardWidgetUiModel>,
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 nestedScrollInterop = rememberNestedScrollInteropConnection()

is DashboardViewModel.DashboardWidgetUiModel.ShareStoreWidget -> {
ShareStoreCard(
onShareClicked = it.onShareClicked,
modifier = widgetModifier
)
}

is DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget -> {
FeedbackCard(
widget = it,
modifier = widgetModifier
)
}

is DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard -> {
NewWidgetsCard(
state = it,
modifier = widgetModifier
if (numberOfColumns == 1) {
Column(
modifier = modifier
.nestedScroll(nestedScrollInterop)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
widgetUiModels.forEach { widget ->
AnimatedVisibility(widget.isVisible) {
DashboardWidgetCard(
widget,
mainActivityViewModel,
dashboardViewModel,
blazeCampaignCreationDispatcher,
Modifier.fillMaxWidth()
)
}
}
}
} else {
val widgetColumns = splitWidgetsIntoColumns(
numberOfColumns = numberOfColumns,
visibleUiWidgets = widgetUiModels.filter { it.isVisible }
)
Row(
modifier = modifier
.nestedScroll(nestedScrollInterop)
.verticalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
widgetColumns.forEach { columnWidgets ->
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
columnWidgets.forEach { widget ->
DashboardWidgetCard(
widget,
mainActivityViewModel,
dashboardViewModel,
blazeCampaignCreationDispatcher,
Modifier.fillMaxWidth()
)
}
}
Expand All @@ -117,9 +154,74 @@ private fun WidgetList(
}
}

@Suppress("MagicNumber")
private fun calculateColumnNumber(
availableWidthInDp: Dp,
state: DashboardCardsState
) = when {
availableWidthInDp < 600.dp -> 1 // 600dp covers 99.96% of phones in portrait
availableWidthInDp < 1000.dp -> 2 // 1000dp should be enough to avoid 3 columns on big phones in landscape
else -> 3 // 3 columns should only display on tablets in landscape
}.coerceAtMost(
maximumValue = state.widgets.filter { it.isVisible }.size
)

private fun splitWidgetsIntoColumns(
numberOfColumns: Int,
visibleUiWidgets: List<DashboardWidgetUiModel>
): MutableList<MutableList<DashboardWidgetUiModel>> {
val widgetColumns = MutableList<MutableList<DashboardWidgetUiModel>>(numberOfColumns) { mutableListOf() }
for ((index, item) in visibleUiWidgets.withIndex()) {
widgetColumns[index % numberOfColumns].add(item)
}
return widgetColumns
}

@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 ShareStoreWidget -> {
ShareStoreCard(
onShareClicked = it.onShareClicked,
modifier = widgetModifier
)
}

is FeedbackWidget -> {
FeedbackCard(
widget = it,
modifier = widgetModifier
)
}

is NewWidgetsCard -> {
NewWidgetsCard(
state = it,
modifier = widgetModifier
)
}
}
}

@Composable
private fun ConfigurableWidgetCard(
widgetUiModel: DashboardViewModel.DashboardWidgetUiModel.ConfigurableWidget,
widgetUiModel: DashboardWidgetUiModel.ConfigurableWidget,
mainActivityViewModel: MainActivityViewModel,
dashboardViewModel: DashboardViewModel,
blazeCampaignCreationDispatcher: BlazeCampaignCreationDispatcher,
Expand Down Expand Up @@ -227,7 +329,7 @@ private fun ShareStoreCard(

@Composable
private fun FeedbackCard(
widget: DashboardViewModel.DashboardWidgetUiModel.FeedbackWidget,
widget: DashboardWidgetUiModel.FeedbackWidget,
modifier: Modifier
) {
LaunchedEffect(Unit) {
Expand Down Expand Up @@ -271,7 +373,7 @@ private fun FeedbackCard(

@Composable
private fun NewWidgetsCard(
state: DashboardViewModel.DashboardWidgetUiModel.NewWidgetsCard,
state: DashboardWidgetUiModel.NewWidgetsCard,
modifier: Modifier
) {
Column(
Expand Down
Loading

0 comments on commit 08a9a97

Please sign in to comment.