From 3c7ee5d15347636bdb074602d89a1dbfbc5f9119 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 13:48:03 +0200 Subject: [PATCH 01/21] Add a dashboard orders card ViewModel --- .../orders/DashboardOrdersViewModel.kt | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt new file mode 100644 index 00000000000..1898904f86b --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt @@ -0,0 +1,159 @@ +package com.woocommerce.android.ui.dashboard.orders + +import androidx.annotation.ColorRes +import androidx.annotation.StringRes +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.woocommerce.android.R +import com.woocommerce.android.analytics.AnalyticsEvent +import com.woocommerce.android.analytics.AnalyticsTracker +import com.woocommerce.android.analytics.AnalyticsTrackerWrapper +import com.woocommerce.android.extensions.capitalize +import com.woocommerce.android.extensions.formatToMMMdd +import com.woocommerce.android.model.DashboardWidget +import com.woocommerce.android.model.DashboardWidget.Type.ORDERS +import com.woocommerce.android.model.Order +import com.woocommerce.android.model.toOrderStatus +import com.woocommerce.android.ui.dashboard.DashboardViewModel +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetAction +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetMenu +import com.woocommerce.android.ui.dashboard.DashboardViewModel.RefreshEvent +import com.woocommerce.android.ui.dashboard.defaultHideMenuEntry +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.Factory +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Content +import com.woocommerce.android.ui.orders.list.OrderListRepository +import com.woocommerce.android.util.CurrencyFormatter +import com.woocommerce.android.viewmodel.ScopedViewModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch +import java.util.Locale + +@HiltViewModel(assistedFactory = Factory::class) +class DashboardOrdersViewModel @AssistedInject constructor( + savedStateHandle: SavedStateHandle, + @Assisted private val parentViewModel: DashboardViewModel, + private val orderListRepository: OrderListRepository, + private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, + private val currencyFormatter: CurrencyFormatter +) : ScopedViewModel(savedStateHandle) { + companion object { + const val MAX_NUMBER_OF_ORDERS_TO_DISPLAY_IN_CARD = 3 + } + + private val orderStatusMap = MutableSharedFlow>(extraBufferCapacity = 1) + private val refreshTrigger = MutableSharedFlow(extraBufferCapacity = 1) + + val menu = DashboardWidgetMenu( + items = listOf( + DashboardWidget.Type.ORDERS.defaultHideMenuEntry { + parentViewModel.onHideWidgetClicked(ORDERS) + } + ) + ) + + val button = DashboardWidgetAction( + titleResource = R.string.dashboard_action_view_all_orders, + action = parentViewModel::onNavigateToOrders + ) + + @OptIn(ExperimentalCoroutinesApi::class) + val viewState = merge(parentViewModel.refreshTrigger, refreshTrigger) + .onStart { emit(RefreshEvent())} + .flatMapLatest { + orderListRepository.observeTopOrders( + MAX_NUMBER_OF_ORDERS_TO_DISPLAY_IN_CARD + ) + } + .combine(orderStatusMap) { result, statusMap -> + result.fold( + onSuccess = { orders -> + if (orders.isEmpty()) { + ViewState.Loading + } else { + Content( + orders.map { + val status = statusMap[it.status.value]?.label + ?: it.status.value.capitalize(Locale.getDefault()) + + ViewState.OrderItem( + number = "#${it.number}", + date = it.dateCreated.formatToMMMdd(), + customerName = it.billingName, + status = status, + statusColor = it.status.color, + totalPrice = currencyFormatter.formatCurrency(it.total, it.currency) + ) + } + ) + } + }, + onFailure = { + ViewState.Error(it.message ?: "") + } + ) + } + .asLiveData() + + init { + viewModelScope.launch { + orderStatusMap.tryEmit( + orderListRepository.getCachedOrderStatusOptions().mapValues { (_, value) -> + value.toOrderStatus() + } + ) + } + } + + private val Order.Status.color: Int + get() { + return when (this) { + is Order.Status.Processing -> R.color.tag_bg_processing + is Order.Status.Completed -> R.color.tag_bg_completed + is Order.Status.Failed -> R.color.tag_bg_failed + is Order.Status.OnHold -> R.color.tag_bg_on_hold + else -> R.color.tag_bg_other + } + } + + fun onRefresh() { + analyticsTrackerWrapper.track( + AnalyticsEvent.DYNAMIC_DASHBOARD_CARD_RETRY_TAPPED, + mapOf( + AnalyticsTracker.KEY_TYPE to DashboardWidget.Type.ORDERS.trackingIdentifier + ) + ) + refreshTrigger.tryEmit(RefreshEvent(isForced = true)) + } + + sealed class ViewState { + data object Loading : ViewState() + data class Error(val message: String) : ViewState() + data class Content(val orders: List) : ViewState() + + @StringRes val title: Int = ORDERS.titleResource + + data class OrderItem( + val number: String, + val date: String, + val customerName: String, + val status: String, + @ColorRes val statusColor: Int, + val totalPrice: String + ) + } + + @AssistedFactory + interface Factory { + fun create(parentViewModel: DashboardViewModel): DashboardOrdersViewModel + } +} From 7e74cf245d0d4691f00d12e39b46acb8d751164c Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 13:49:08 +0200 Subject: [PATCH 02/21] Add orders card UI --- .../android/model/DashboardWidget.kt | 3 +- .../ui/dashboard/DashboardContainer.kt | 6 + .../dashboard/orders/DashboardOrdersCard.kt | 253 ++++++++++++++++++ WooCommerce/src/main/res/values/strings.xml | 2 + 4 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/DashboardWidget.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/DashboardWidget.kt index 214e62d78a7..5b75d2af877 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/DashboardWidget.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/DashboardWidget.kt @@ -28,7 +28,8 @@ data class DashboardWidget( ONBOARDING(R.string.my_store_widget_onboarding_title, "store_setup"), STATS(R.string.my_store_widget_stats_title, "performance"), POPULAR_PRODUCTS(R.string.my_store_widget_top_products_title, "top_performers"), - BLAZE(R.string.my_store_widget_blaze_title, "blaze") + BLAZE(R.string.my_store_widget_blaze_title, "blaze"), + ORDERS(R.string.my_store_widget_orders_title, "orders"), } sealed interface Status : Parcelable { 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 ea8f7461694..688f43f66ef 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 @@ -35,6 +35,7 @@ import com.woocommerce.android.ui.compose.component.WCOutlinedButton import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.OpenRangePicker import com.woocommerce.android.ui.dashboard.blaze.DashboardBlazeCard import com.woocommerce.android.ui.dashboard.onboarding.DashboardOnboardingCard +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersCard import com.woocommerce.android.ui.dashboard.stats.DashboardStatsCard import com.woocommerce.android.ui.dashboard.topperformers.DashboardTopPerformersWidgetCard @@ -132,6 +133,11 @@ private fun ConfigurableWidgetCard( parentViewModel = dashboardViewModel, modifier = modifier ) + + DashboardWidget.Type.ORDERS -> DashboardOrdersCard( + parentViewModel = dashboardViewModel, + modifier = modifier + ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt new file mode 100644 index 00000000000..7a641cb5373 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt @@ -0,0 +1,253 @@ +package com.woocommerce.android.ui.dashboard.orders + +import androidx.compose.foundation.layout.Column +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.Divider +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import com.woocommerce.android.R +import com.woocommerce.android.ui.compose.animations.SkeletonView +import com.woocommerce.android.ui.compose.component.WCTag +import com.woocommerce.android.ui.compose.viewModelWithFactory +import com.woocommerce.android.ui.dashboard.DashboardViewModel +import com.woocommerce.android.ui.dashboard.WidgetCard +import com.woocommerce.android.ui.dashboard.WidgetError +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Content +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Error +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Loading +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.OrderItem + +@Composable +fun DashboardOrdersCard( + parentViewModel: DashboardViewModel, + modifier: Modifier = Modifier, + viewModel: DashboardOrdersViewModel = viewModelWithFactory { factory: DashboardOrdersViewModel.Factory -> + factory.create(parentViewModel) + } +) { + viewModel.viewState.observeAsState().value?.let { state -> + WidgetCard( + titleResource = state.title, + menu = viewModel.menu, + button = viewModel.button, + modifier = modifier, + isError = state is Error + ) { + when (state) { + is Content -> { + TopOrders( + orders = state.orders + ) + } + is Error -> WidgetError( + onContactSupportClicked = parentViewModel::onContactSupportClicked, + onRetryClicked = viewModel::onRefresh + ) + is Loading -> Loading() + } + } + } +} + +@Composable +fun TopOrders( + orders: List +) { + Column { + orders.forEach { order -> + OrderListItem(order) + + Divider( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp) + ) + } + } +} + +@Composable +private fun Loading() { + Column { + repeat(3) { + LoadingItem() + Divider( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp) + ) + } + } +} + +@Composable +private fun LoadingItem() { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + val (number, date, name, status, total) = createRefs() + SkeletonView( + modifier = Modifier + .height(22.dp) + .width(50.dp) + .constrainAs(number) { + top.linkTo(parent.top) + start.linkTo(parent.start) + } + ) + + SkeletonView( + modifier = Modifier + .padding(start = 16.dp) + .height(22.dp) + .width(70.dp) + .constrainAs(date) { + top.linkTo(parent.top) + start.linkTo(number.end) + } + ) + + SkeletonView( + modifier = Modifier + .padding(top = 8.dp) + .height(20.dp) + .width(120.dp) + .constrainAs(name) { + top.linkTo(number.bottom) + start.linkTo(parent.start) + } + ) + + SkeletonView( + modifier = Modifier + .height(22.dp) + .width(100.dp) + .constrainAs(status) { + top.linkTo(parent.top) + end.linkTo(parent.end) + }, + ) + + SkeletonView( + modifier = Modifier + .padding(top = 8.dp) + .height(20.dp) + .width(70.dp) + .constrainAs(total) { + top.linkTo(status.bottom) + end.linkTo(parent.end) + } + ) + } +} + +@Composable +private fun OrderListItem(order: OrderItem) { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + val (number, date, name, status, total) = createRefs() + + Text( + text = order.number, + style = MaterialTheme.typography.body1, + color = colorResource(id = R.color.color_on_surface_medium), + modifier = Modifier.constrainAs(number) { + top.linkTo(parent.top) + start.linkTo(parent.start) + } + ) + + Text( + text = order.date, + style = MaterialTheme.typography.body1, + color = colorResource(id = R.color.color_on_surface_medium), + modifier = Modifier + .padding(start = 16.dp) + .constrainAs(date) { + top.linkTo(parent.top) + start.linkTo(number.end) + } + ) + + Text( + text = order.customerName, + style = MaterialTheme.typography.body1, + modifier = Modifier + .padding(top = 8.dp) + .constrainAs(name) { + top.linkTo(number.bottom) + start.linkTo(parent.start) + } + ) + + WCTag( + modifier = Modifier + .constrainAs(status) { + top.linkTo(parent.top) + end.linkTo(parent.end) + }, + text = order.status, + textColor = MaterialTheme.colors.onSurface, + backgroundColor = colorResource(id = order.statusColor), + fontWeight = FontWeight.Normal + ) + + Text( + text = order.totalPrice, + style = MaterialTheme.typography.body1, + modifier = Modifier + .padding(top = 8.dp) + .constrainAs(total) { + top.linkTo(status.bottom) + end.linkTo(parent.end) + } + ) + } +} + +@Composable +@Preview +fun PreviewTopOrders() { + TopOrders( + orders = listOf( + OrderItem( + number = "123", + date = "2021-09-01", + customerName = "John Doe", + status = "Processing", + statusColor = R.color.tag_bg_processing, + totalPrice = "$100.00" + ), + OrderItem( + number = "124", + date = "2021-09-02", + customerName = "Jane Doe", + status = "Completed", + statusColor = R.color.tag_bg_completed, + totalPrice = "$200.00" + ) + ), + ) +} + +@Composable +@Preview +fun PreviewLoadingCard() { + Loading() +} diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 9c8e9495bb2..92cd167b274 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -389,6 +389,7 @@ View Order View Orders + View all orders We can\'t display your\n store\'s analytics Make sure you are running the latest version of WooCommerce on your site and that you have WooCommerce Admin activated. @@ -409,6 +410,7 @@ Top performers Blaze campaigns Feedback + Most recent orders Unavailable Completed From 4d43a0a72d6ff31817ed1d0c3abdc4ebacdd3e91 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 13:49:36 +0200 Subject: [PATCH 03/21] Update the Order model to contain billing name --- .../main/kotlin/com/woocommerce/android/model/Order.kt | 1 + .../kotlin/com/woocommerce/android/model/OrderMapper.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt index 0b76ac65b63..21a8ed5d3cf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt @@ -52,6 +52,7 @@ data class Order( val selectedGiftCard: String?, val giftCardDiscountedAmount: BigDecimal?, val shippingTax: BigDecimal, + val billingName: String = "" ) : Parcelable { @IgnoredOnParcel val isOrderPaid = datePaid != null diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt index 19e7fab520f..c550fc65861 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt @@ -1,10 +1,13 @@ package com.woocommerce.android.model +import com.woocommerce.android.R import com.woocommerce.android.extensions.CASH_PAYMENTS import com.woocommerce.android.extensions.fastStripHtml +import com.woocommerce.android.extensions.getBillingName import com.woocommerce.android.model.Order.Item import com.woocommerce.android.util.DateUtils import com.woocommerce.android.util.StringUtils +import com.woocommerce.android.viewmodel.ResourceProvider import org.wordpress.android.fluxc.model.OrderEntity import org.wordpress.android.fluxc.model.WCMetaData import org.wordpress.android.fluxc.model.order.FeeLineTaxStatus @@ -22,7 +25,8 @@ import org.wordpress.android.fluxc.model.order.ShippingLine as WCShippingLine class OrderMapper @Inject constructor( private val getLocations: GetLocations, - private val dateUtils: DateUtils + private val dateUtils: DateUtils, + private val resourceProvider: ResourceProvider ) { fun toAppModel(databaseEntity: OrderEntity): Order { val metaDataList = databaseEntity.getMetaDataList() @@ -66,6 +70,9 @@ class OrderMapper @Inject constructor( giftCardDiscountedAmount = databaseEntity.giftCardAmount .toBigDecimalOrNull() ?: BigDecimal.ZERO, shippingTax = databaseEntity.shippingTax.toBigDecimalOrNull() ?: BigDecimal.ZERO, + billingName = databaseEntity.getBillingName( + resourceProvider.getString(R.string.orderdetail_customer_name_default) + ) ) } From 706c379e4bdabf531ef5fd286479d5b0563fd1e8 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 13:49:59 +0200 Subject: [PATCH 04/21] Update the Tag component UI to match the design --- .../woocommerce/android/ui/compose/component/Tag.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/Tag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/Tag.kt index 903caf183e4..afc1c53b392 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/Tag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/Tag.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.woocommerce.android.R @Composable @@ -24,26 +25,26 @@ fun WCTag( modifier: Modifier = Modifier, textColor: Color = colorResource(id = R.color.tag_text_main), backgroundColor: Color = colorResource(R.color.tag_bg_main), - textStyle: TextStyle = MaterialTheme.typography.caption + textStyle: TextStyle = MaterialTheme.typography.caption, + fontWeight: FontWeight = FontWeight.Bold ) { Box( modifier = modifier - .clip(RoundedCornerShape(percent = 35)) + .clip(RoundedCornerShape(4.dp)) .background(backgroundColor) .padding( - start = dimensionResource(id = R.dimen.minor_50), - end = dimensionResource(id = R.dimen.minor_50), + horizontal = dimensionResource(id = R.dimen.minor_50), ) ) { Text( modifier = Modifier.padding( horizontal = dimensionResource(id = R.dimen.minor_75), - vertical = dimensionResource(id = R.dimen.minor_25) + vertical = dimensionResource(id = R.dimen.minor_50) ), text = text, style = textStyle, color = textColor, - fontWeight = FontWeight.Bold + fontWeight = fontWeight ) } } From 9a61c90a8f3268fc1a1e24e42bb8ed4ba28e67eb Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 13:50:23 +0200 Subject: [PATCH 05/21] Add a method to observe the orders --- .../ui/orders/list/OrderListRepository.kt | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt index 14659c1bc7e..b727001a70e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt @@ -2,6 +2,8 @@ package com.woocommerce.android.ui.orders.list import com.woocommerce.android.AppConstants import com.woocommerce.android.WooException +import com.woocommerce.android.model.Order +import com.woocommerce.android.model.OrderMapper import com.woocommerce.android.model.RequestResult import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.util.ContinuationWrapper @@ -10,6 +12,7 @@ import com.woocommerce.android.util.ContinuationWrapper.ContinuationResult.Succe import com.woocommerce.android.util.CoroutineDispatchers import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T.ORDERS +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -31,7 +34,8 @@ class OrderListRepository @Inject constructor( private val orderStore: WCOrderStore, private val orderUpdateStore: OrderUpdateStore, private val gatewayStore: WCGatewayStore, - private val selectedSite: SelectedSite + private val selectedSite: SelectedSite, + private val orderMapper: OrderMapper, ) { companion object { private const val TAG = "OrderListRepository" @@ -122,6 +126,37 @@ class OrderListRepository @Inject constructor( } } + fun observeTopOrders(count: Int, statusFilter: Order.Status? = null) = flow { + emit(Result.success(emptyList())) + + orderStore.getOrdersForSite(selectedSite.get()) + .asSequence() + .filter { statusFilter == null || it.status == statusFilter.value } + .sortedByDescending { it.dateCreated } + .take(count) + .map { orderMapper.toAppModel(it) } + .let { orders -> + orders + .toList() + .takeIf { it.isNotEmpty() } + ?.let { emit(Result.success(it)) } + } + + val result = orderStore.fetchOrders( + site = selectedSite.get(), + count = count, + statusFilter = statusFilter?.value + ) + + if (result.isError) { + WooLog.e(ORDERS, "Error fetching top orders: ${result.error.message}") + emit(Result.failure(WooException(result.error))) + } else { + val orderList = result.model?.map { orderMapper.toAppModel(it) } ?: emptyList() + emit(Result.success(orderList)) + } + } + @Suppress("unused") @Subscribe(threadMode = ThreadMode.MAIN) fun onOrderStatusOptionsChanged(event: OnOrderStatusOptionsChanged) { From 4978ae947e95edb6d2d160ef8cd22ab376aad112 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 13:50:47 +0200 Subject: [PATCH 06/21] Add a way to navigate to orders tab --- .../woocommerce/android/ui/dashboard/DashboardFragment.kt | 3 +++ .../woocommerce/android/ui/dashboard/DashboardViewModel.kt | 6 ++++++ .../kotlin/com/woocommerce/android/ui/main/MainActivity.kt | 6 ++++++ .../com/woocommerce/android/ui/main/MainNavigationRouter.kt | 2 ++ 4 files changed, 17 insertions(+) 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 ae4ed3708ec..e8890bc9b3f 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 @@ -35,6 +35,7 @@ import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ContactSupport import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.FeedbackNegativeAction import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.FeedbackPositiveAction +import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.NavigateToOrders 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.ShareStore @@ -165,6 +166,8 @@ class DashboardFragment : is FeedbackNegativeAction -> mainNavigationRouter?.showFeedbackSurvey() + is NavigateToOrders -> mainNavigationRouter?.showOrders() + else -> event.isHandled = false } } 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 1241bb7bd9a..dce1cb3bc56 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 @@ -180,6 +180,10 @@ class DashboardViewModel @Inject constructor( triggerEvent(DashboardEvent.ContactSupport) } + fun onNavigateToOrders() { + triggerEvent(DashboardEvent.NavigateToOrders) + } + private fun mapWidgetsToUiModels( widgets: List, userFeedbackIsDue: Boolean @@ -303,6 +307,8 @@ class DashboardViewModel @Inject constructor( data object FeedbackPositiveAction : DashboardEvent() data object FeedbackNegativeAction : DashboardEvent() + + object NavigateToOrders : DashboardEvent() } data class RefreshEvent(val isForced: Boolean = false) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt index b7733c640d0..15db0a57f13 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt @@ -56,6 +56,7 @@ import com.woocommerce.android.extensions.expand import com.woocommerce.android.extensions.hide import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.model.Notification +import com.woocommerce.android.model.Order.Status import com.woocommerce.android.support.help.HelpActivity import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.tools.SelectedSite @@ -1167,6 +1168,11 @@ class MainActivity : crashLogging.recordEvent("Opening order $orderId") } + override fun showOrders() { + binding.bottomNav.currentPosition = ORDERS + binding.bottomNav.active(ORDERS.position) + } + override fun showOrderDetailWithSharedTransition( orderId: Long, allOrderIds: List, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt index 93ea98f1ad5..391b854ac70 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt @@ -23,6 +23,8 @@ interface MainNavigationRouter { startPaymentsFlow: Boolean = false, ) + fun showOrders() + fun showOrderDetailWithSharedTransition( orderId: Long, allOrderIds: List, From e78a4660b4c9b113c73509666c821a62b7576fe4 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 13:51:16 +0200 Subject: [PATCH 07/21] Add the missing type to dashboard repository --- .../android/ui/dashboard/data/DashboardRepository.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt index a8d70de26fd..af1746b4b67 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt @@ -88,10 +88,9 @@ class DashboardRepository @Inject constructor( isSelected = widget.isAdded, status = when (type) { DashboardWidget.Type.STATS, + DashboardWidget.Type.ORDERS, DashboardWidget.Type.POPULAR_PRODUCTS -> statsWidgetsStatus - DashboardWidget.Type.BLAZE -> blazeWidgetStatus - DashboardWidget.Type.ONBOARDING -> onboardingWidgetStatus } ) From 0af6cdae17720365b8611e813ba0598637e92c16 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 14:02:42 +0200 Subject: [PATCH 08/21] Fix ktlint issues --- .../android/ui/dashboard/orders/DashboardOrdersCard.kt | 2 ++ .../android/ui/dashboard/orders/DashboardOrdersViewModel.kt | 2 +- .../main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt index 7a641cb5373..ec54700f743 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt @@ -91,6 +91,7 @@ private fun Loading() { } } +@Suppress("DestructuringDeclarationWithTooManyEntries") @Composable private fun LoadingItem() { ConstraintLayout( @@ -154,6 +155,7 @@ private fun LoadingItem() { } } +@Suppress("DestructuringDeclarationWithTooManyEntries") @Composable private fun OrderListItem(order: OrderItem) { ConstraintLayout( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt index 1898904f86b..3e8653f16b8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt @@ -68,7 +68,7 @@ class DashboardOrdersViewModel @AssistedInject constructor( @OptIn(ExperimentalCoroutinesApi::class) val viewState = merge(parentViewModel.refreshTrigger, refreshTrigger) - .onStart { emit(RefreshEvent())} + .onStart { emit(RefreshEvent()) } .flatMapLatest { orderListRepository.observeTopOrders( MAX_NUMBER_OF_ORDERS_TO_DISPLAY_IN_CARD diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt index 15db0a57f13..e842f20e458 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt @@ -56,7 +56,6 @@ import com.woocommerce.android.extensions.expand import com.woocommerce.android.extensions.hide import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.model.Notification -import com.woocommerce.android.model.Order.Status import com.woocommerce.android.support.help.HelpActivity import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.tools.SelectedSite From 146964abb21290501d00ae369ba897ebe67bacb6 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 14:17:58 +0200 Subject: [PATCH 09/21] Update FluxC hash --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c19c855d5e4..1c0b583f557 100644 --- a/build.gradle +++ b/build.gradle @@ -98,7 +98,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-cfe34975d20df76a2b7b4522dccd2faf53ef0f7c' + fluxCVersion = '3008-f3ff9689a0a998537a624ce9a77b930a341bee48' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 6deab33186b173511c841b5a5f6a7e60c1700ba1 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 16:39:42 +0200 Subject: [PATCH 10/21] Hide new cards behind a feature flag --- .../android/ui/dashboard/data/DashboardDataStore.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt index edf6294c2b0..48b300a893d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt @@ -6,6 +6,7 @@ import com.woocommerce.android.model.DashboardWidget import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.mystore.data.DashboardDataModel import com.woocommerce.android.ui.mystore.data.DashboardWidgetDataModel +import com.woocommerce.android.util.FeatureFlag import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T import dagger.hilt.EntryPoints @@ -77,4 +78,8 @@ class DashboardDataStore @Inject constructor( // Use the feature flag [DYNAMIC_DASHBOARD_M2] to filter out unsupported widgets during development private val supportedWidgets: List = DashboardWidget.Type.entries + .filter { + FeatureFlag.DYNAMIC_DASHBOARD_M2.isEnabled() || + it != DashboardWidget.Type.ORDERS + } } From 6bfa2fd362efaea73fb50a9c84c69ade3c0127c5 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 10 May 2024 16:51:53 +0200 Subject: [PATCH 11/21] Explicitly specify we don't want to delete old orders --- .../woocommerce/android/ui/orders/list/OrderListRepository.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt index b727001a70e..381d72dd312 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt @@ -145,7 +145,8 @@ class OrderListRepository @Inject constructor( val result = orderStore.fetchOrders( site = selectedSite.get(), count = count, - statusFilter = statusFilter?.value + statusFilter = statusFilter?.value, + deleteOldData = false ) if (result.isError) { From 5d9d1b248350f2e8c9cd65b452da850f1f4d2ecf Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 13 May 2024 14:36:21 +0200 Subject: [PATCH 12/21] Change the way billing name is retrieved --- .../main/kotlin/com/woocommerce/android/model/Order.kt | 5 ++++- .../kotlin/com/woocommerce/android/model/OrderMapper.kt | 5 ----- .../ui/dashboard/orders/DashboardOrdersViewModel.kt | 8 ++++++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt index 21a8ed5d3cf..495cda870e6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Order.kt @@ -52,7 +52,6 @@ data class Order( val selectedGiftCard: String?, val giftCardDiscountedAmount: BigDecimal?, val shippingTax: BigDecimal, - val billingName: String = "" ) : Parcelable { @IgnoredOnParcel val isOrderPaid = datePaid != null @@ -67,6 +66,10 @@ data class Order( @IgnoredOnParcel val isRefundAvailable = !isOrderFullyRefunded && quantityOfItemsWhichPossibleToRefund > 0 && isOrderPaid + @IgnoredOnParcel + val billingName + get() = getBillingName("") + val hasMultipleShippingLines: Boolean get() = shippingLines.size > 1 diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt index c550fc65861..1cca2056dd8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt @@ -1,9 +1,7 @@ package com.woocommerce.android.model -import com.woocommerce.android.R import com.woocommerce.android.extensions.CASH_PAYMENTS import com.woocommerce.android.extensions.fastStripHtml -import com.woocommerce.android.extensions.getBillingName import com.woocommerce.android.model.Order.Item import com.woocommerce.android.util.DateUtils import com.woocommerce.android.util.StringUtils @@ -70,9 +68,6 @@ class OrderMapper @Inject constructor( giftCardDiscountedAmount = databaseEntity.giftCardAmount .toBigDecimalOrNull() ?: BigDecimal.ZERO, shippingTax = databaseEntity.shippingTax.toBigDecimalOrNull() ?: BigDecimal.ZERO, - billingName = databaseEntity.getBillingName( - resourceProvider.getString(R.string.orderdetail_customer_name_default) - ) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt index 3e8653f16b8..fe1ffc52d65 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt @@ -24,6 +24,7 @@ import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.Fact import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Content import com.woocommerce.android.ui.orders.list.OrderListRepository import com.woocommerce.android.util.CurrencyFormatter +import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -44,7 +45,8 @@ class DashboardOrdersViewModel @AssistedInject constructor( @Assisted private val parentViewModel: DashboardViewModel, private val orderListRepository: OrderListRepository, private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, - private val currencyFormatter: CurrencyFormatter + private val currencyFormatter: CurrencyFormatter, + private val resourceProvider: ResourceProvider ) : ScopedViewModel(savedStateHandle) { companion object { const val MAX_NUMBER_OF_ORDERS_TO_DISPLAY_IN_CARD = 3 @@ -88,7 +90,9 @@ class DashboardOrdersViewModel @AssistedInject constructor( ViewState.OrderItem( number = "#${it.number}", date = it.dateCreated.formatToMMMdd(), - customerName = it.billingName, + customerName = it.billingName.ifEmpty { + resourceProvider.getString(R.string.orderdetail_customer_name_default) + }, status = status, statusColor = it.status.color, totalPrice = currencyFormatter.formatCurrency(it.total, it.currency) From b9a104a7617a01d3cfae6184cbfc2bb717c078bc Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 13 May 2024 14:47:04 +0200 Subject: [PATCH 13/21] Simplify navigation to orders --- .../android/ui/dashboard/DashboardFragment.kt | 3 -- .../ui/dashboard/DashboardViewModel.kt | 6 ---- .../dashboard/orders/DashboardOrdersCard.kt | 36 +++++++++++++++++++ .../orders/DashboardOrdersViewModel.kt | 9 ++++- .../android/ui/main/MainActivity.kt | 5 --- .../android/ui/main/MainNavigationRouter.kt | 2 -- 6 files changed, 44 insertions(+), 17 deletions(-) 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 e8890bc9b3f..ae4ed3708ec 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 @@ -35,7 +35,6 @@ import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ContactSupport import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.FeedbackNegativeAction import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.FeedbackPositiveAction -import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.NavigateToOrders 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.ShareStore @@ -166,8 +165,6 @@ class DashboardFragment : is FeedbackNegativeAction -> mainNavigationRouter?.showFeedbackSurvey() - is NavigateToOrders -> mainNavigationRouter?.showOrders() - else -> event.isHandled = false } } 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 dce1cb3bc56..1241bb7bd9a 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 @@ -180,10 +180,6 @@ class DashboardViewModel @Inject constructor( triggerEvent(DashboardEvent.ContactSupport) } - fun onNavigateToOrders() { - triggerEvent(DashboardEvent.NavigateToOrders) - } - private fun mapWidgetsToUiModels( widgets: List, userFeedbackIsDue: Boolean @@ -307,8 +303,6 @@ class DashboardViewModel @Inject constructor( data object FeedbackPositiveAction : DashboardEvent() data object FeedbackNegativeAction : DashboardEvent() - - object NavigateToOrders : DashboardEvent() } data class RefreshEvent(val isForced: Boolean = false) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt index ec54700f743..8f021f0e79d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt @@ -9,24 +9,32 @@ import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer import com.woocommerce.android.R +import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.component.WCTag +import com.woocommerce.android.ui.compose.rememberNavController import com.woocommerce.android.ui.compose.viewModelWithFactory import com.woocommerce.android.ui.dashboard.DashboardViewModel import com.woocommerce.android.ui.dashboard.WidgetCard import com.woocommerce.android.ui.dashboard.WidgetError +import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.NavigateToOrders import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Content import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Error import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Loading import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.OrderItem +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event @Composable fun DashboardOrdersCard( @@ -58,6 +66,34 @@ fun DashboardOrdersCard( } } } + + HandleEvents(viewModel.event) +} + + + +@Composable +private fun HandleEvents( + event: LiveData +) { + val navController = rememberNavController() + val lifecycleOwner = LocalLifecycleOwner.current + + DisposableEffect(event, navController, lifecycleOwner) { + val observer = Observer { event: Event -> + when (event) { + is NavigateToOrders -> { + navController.navigateSafely(R.id.orders) + } + } + } + + event.observe(lifecycleOwner, observer) + + onDispose { + event.removeObserver(observer) + } + } } @Composable diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt index fe1ffc52d65..6ff194fb26b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt @@ -24,6 +24,7 @@ import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.Fact import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersViewModel.ViewState.Content import com.woocommerce.android.ui.orders.list.OrderListRepository import com.woocommerce.android.util.CurrencyFormatter +import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import dagger.assisted.Assisted @@ -65,7 +66,7 @@ class DashboardOrdersViewModel @AssistedInject constructor( val button = DashboardWidgetAction( titleResource = R.string.dashboard_action_view_all_orders, - action = parentViewModel::onNavigateToOrders + action = ::onNavigateToOrders ) @OptIn(ExperimentalCoroutinesApi::class) @@ -129,6 +130,10 @@ class DashboardOrdersViewModel @AssistedInject constructor( } } + private fun onNavigateToOrders() { + triggerEvent(NavigateToOrders) + } + fun onRefresh() { analyticsTrackerWrapper.track( AnalyticsEvent.DYNAMIC_DASHBOARD_CARD_RETRY_TAPPED, @@ -160,4 +165,6 @@ class DashboardOrdersViewModel @AssistedInject constructor( interface Factory { fun create(parentViewModel: DashboardViewModel): DashboardOrdersViewModel } + + data object NavigateToOrders : MultiLiveEvent.Event() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt index e842f20e458..b7733c640d0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt @@ -1167,11 +1167,6 @@ class MainActivity : crashLogging.recordEvent("Opening order $orderId") } - override fun showOrders() { - binding.bottomNav.currentPosition = ORDERS - binding.bottomNav.active(ORDERS.position) - } - override fun showOrderDetailWithSharedTransition( orderId: Long, allOrderIds: List, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt index 391b854ac70..93ea98f1ad5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainNavigationRouter.kt @@ -23,8 +23,6 @@ interface MainNavigationRouter { startPaymentsFlow: Boolean = false, ) - fun showOrders() - fun showOrderDetailWithSharedTransition( orderId: Long, allOrderIds: List, From c71448dbb82af2c730ff7ff672189e1e680c073f Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 13 May 2024 14:54:32 +0200 Subject: [PATCH 14/21] Rename the widget status to be more general --- .../ui/dashboard/data/DashboardRepository.kt | 14 +++++++------- ...sWidgetsStatus.kt => ObserveSiteOrdersState.kt} | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/{ObserveStatsWidgetsStatus.kt => ObserveSiteOrdersState.kt} (97%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt index af1746b4b67..be8fb05aa8e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt @@ -19,7 +19,7 @@ import javax.inject.Inject class DashboardRepository @Inject constructor( selectedSite: SelectedSite, private val dashboardDataStore: DashboardDataStore, - observeStatsWidgetsStatus: ObserveStatsWidgetsStatus, + observeSiteOrdersState: ObserveSiteOrdersState, observeBlazeWidgetStatus: ObserveBlazeWidgetStatus, observeOnboardingWidgetStatus: ObserveOnboardingWidgetStatus ) { @@ -28,7 +28,7 @@ class DashboardRepository @Inject constructor( SiteComponentEntryPoint::class.java ).siteCoroutineScope() - private val statsWidgetsStatus = observeStatsWidgetsStatus() + private val siteOrdersState = observeSiteOrdersState() .stateIn( scope = siteCoroutineScope, started = SharingStarted.Lazily, @@ -51,11 +51,11 @@ class DashboardRepository @Inject constructor( val widgets = combine( dashboardDataStore.widgets, - statsWidgetsStatus, + siteOrdersState, blazeWidgetStatus, onboardingWidgetStatus - ) { widgets, statsWidgetStatus, blazeWidgetStatus, onboardingWidgetStatus -> - widgets.toDomainModel(statsWidgetStatus, blazeWidgetStatus, onboardingWidgetStatus) + ) { widgets, siteOrdersState, blazeWidgetStatus, onboardingWidgetStatus -> + widgets.toDomainModel(siteOrdersState, blazeWidgetStatus, onboardingWidgetStatus) } suspend fun updateWidgets(widgets: List) = dashboardDataStore.updateDashboard( @@ -77,7 +77,7 @@ class DashboardRepository @Inject constructor( } private fun List.toDomainModel( - statsWidgetsStatus: DashboardWidget.Status, + siteOrdersState: DashboardWidget.Status, blazeWidgetStatus: DashboardWidget.Status, onboardingWidgetStatus: DashboardWidget.Status ): List { @@ -89,7 +89,7 @@ class DashboardRepository @Inject constructor( status = when (type) { DashboardWidget.Type.STATS, DashboardWidget.Type.ORDERS, - DashboardWidget.Type.POPULAR_PRODUCTS -> statsWidgetsStatus + DashboardWidget.Type.POPULAR_PRODUCTS -> siteOrdersState DashboardWidget.Type.BLAZE -> blazeWidgetStatus DashboardWidget.Type.ONBOARDING -> onboardingWidgetStatus } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/ObserveStatsWidgetsStatus.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/ObserveSiteOrdersState.kt similarity index 97% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/ObserveStatsWidgetsStatus.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/ObserveSiteOrdersState.kt index 74c81330b01..46de65908d2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/ObserveStatsWidgetsStatus.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/ObserveSiteOrdersState.kt @@ -16,7 +16,7 @@ import org.wordpress.android.fluxc.store.WCOrderStore.HasOrdersResult import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) -class ObserveStatsWidgetsStatus @Inject constructor( +class ObserveSiteOrdersState @Inject constructor( private val selectedSite: SelectedSite, private val orderStore: WCOrderStore, private val coroutineDispatchers: CoroutineDispatchers From c8e8d2219a207ede64bbb1d48af8e669f9124fbb Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 14 May 2024 09:23:34 +0200 Subject: [PATCH 15/21] Fix tests --- .../android/e2e/helpers/util/Screen.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt index 9869086faa9..01ac6cac25d 100644 --- a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt +++ b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt @@ -199,12 +199,17 @@ open class Screen { val node = onNode(matcher) if (node.isDisplayed() && (!requireFullVisibility || node.isFullyDisplayed())) return - val parentBounds = node.onParent().fetchSemanticsNode().touchBoundsInRoot + val bounds = node.onParent().fetchSemanticsNode().boundsInWindow.let { + it.copy( + top = it.top.coerceIn(0f, device.displayHeight.toFloat()), + bottom = it.bottom.coerceIn(0f, device.displayHeight.toFloat()) + ) + } device.swipe( - parentBounds.center.x.toInt(), - parentBounds.center.y.toInt(), - parentBounds.center.x.toInt(), - parentBounds.center.y.toInt() + if (scrollUp) -100 else 100, + bounds.center.x.toInt(), + bounds.center.y.toInt(), + bounds.center.x.toInt(), + bounds.center.y.toInt() + if (scrollUp) -100 else 100, 10 ) numberOfScrolls++ From 739b89b5af070492db2804319d1221d73edf1693 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 14 May 2024 13:53:40 +0200 Subject: [PATCH 16/21] Skip the cache if the loading is forced --- .../ui/orders/list/OrderListRepository.kt | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt index 381d72dd312..6f019c24bda 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt @@ -126,21 +126,18 @@ class OrderListRepository @Inject constructor( } } - fun observeTopOrders(count: Int, statusFilter: Order.Status? = null) = flow { - emit(Result.success(emptyList())) - - orderStore.getOrdersForSite(selectedSite.get()) - .asSequence() - .filter { statusFilter == null || it.status == statusFilter.value } - .sortedByDescending { it.dateCreated } - .take(count) - .map { orderMapper.toAppModel(it) } - .let { orders -> - orders - .toList() - .takeIf { it.isNotEmpty() } - ?.let { emit(Result.success(it)) } - } + fun observeTopOrders(count: Int, isForced: Boolean, statusFilter: Order.Status? = null) = flow { + if (!isForced) { + orderStore.getOrdersForSite(selectedSite.get()) + .asSequence() + .filter { statusFilter == null || it.status == statusFilter.value } + .sortedByDescending { it.dateCreated } + .take(count) + .map { orderMapper.toAppModel(it) } + .let { orders -> + emit(Result.success(orders.toList())) + } + } val result = orderStore.fetchOrders( site = selectedSite.get(), From b3565d9f25eb37d08cc781d982c1867faa0e9b6c Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 14 May 2024 16:17:05 +0200 Subject: [PATCH 17/21] Fix the loading status emission --- .../orders/DashboardOrdersViewModel.kt | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt index 6ff194fb26b..09e19aafc55 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersViewModel.kt @@ -34,9 +34,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch import java.util.Locale @@ -53,7 +54,7 @@ class DashboardOrdersViewModel @AssistedInject constructor( const val MAX_NUMBER_OF_ORDERS_TO_DISPLAY_IN_CARD = 3 } - private val orderStatusMap = MutableSharedFlow>(extraBufferCapacity = 1) + private val orderStatusMap = MutableSharedFlow>(replay = 1) private val refreshTrigger = MutableSharedFlow(extraBufferCapacity = 1) val menu = DashboardWidgetMenu( @@ -72,42 +73,40 @@ class DashboardOrdersViewModel @AssistedInject constructor( @OptIn(ExperimentalCoroutinesApi::class) val viewState = merge(parentViewModel.refreshTrigger, refreshTrigger) .onStart { emit(RefreshEvent()) } - .flatMapLatest { - orderListRepository.observeTopOrders( - MAX_NUMBER_OF_ORDERS_TO_DISPLAY_IN_CARD - ) - } - .combine(orderStatusMap) { result, statusMap -> - result.fold( - onSuccess = { orders -> - if (orders.isEmpty()) { - ViewState.Loading - } else { - Content( - orders.map { - val status = statusMap[it.status.value]?.label - ?: it.status.value.capitalize(Locale.getDefault()) - - ViewState.OrderItem( - number = "#${it.number}", - date = it.dateCreated.formatToMMMdd(), - customerName = it.billingName.ifEmpty { - resourceProvider.getString(R.string.orderdetail_customer_name_default) - }, - status = status, - statusColor = it.status.color, - totalPrice = currencyFormatter.formatCurrency(it.total, it.currency) - ) - } - ) - } - }, - onFailure = { - ViewState.Error(it.message ?: "") + .transformLatest { + emit(ViewState.Loading) + emitAll( + orderListRepository.observeTopOrders( + count = MAX_NUMBER_OF_ORDERS_TO_DISPLAY_IN_CARD, + isForced = it.isForced + ).combine(orderStatusMap) { result, statusMap -> + result.fold( + onSuccess = { orders -> + Content( + orders.map { order -> + val status = statusMap[order.status.value]?.label + ?: order.status.value.capitalize(Locale.getDefault()) + + ViewState.OrderItem( + number = "#${order.number}", + date = order.dateCreated.formatToMMMdd(), + customerName = order.billingName.ifEmpty { + resourceProvider.getString(R.string.orderdetail_customer_name_default) + }, + status = status, + statusColor = order.status.color, + totalPrice = currencyFormatter.formatCurrency(order.total, order.currency) + ) + } + ) + }, + onFailure = { error -> + ViewState.Error(error.message ?: "") + } + ) } ) - } - .asLiveData() + }.asLiveData() init { viewModelScope.launch { From 408328a8e6d5b243b72d4cf2ef0efe134b29aabb Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 14 May 2024 23:00:25 +0200 Subject: [PATCH 18/21] Fix the bottom navigation --- .../ui/dashboard/orders/DashboardOrdersCard.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt index 8f021f0e79d..c2e732fdceb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.navOptions import com.woocommerce.android.R import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.compose.animations.SkeletonView @@ -83,7 +85,14 @@ private fun HandleEvents( val observer = Observer { event: Event -> when (event) { is NavigateToOrders -> { - navController.navigateSafely(R.id.orders) + navController.navigateSafely( + resId = R.id.orders, + navOptions = navOptions { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + } + ) } } } From f54a93c5e884f55de5d64d3eea592b7cdeed1e02 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 14 May 2024 23:02:27 +0200 Subject: [PATCH 19/21] Fix ktlint issues --- .../main/kotlin/com/woocommerce/android/model/OrderMapper.kt | 2 -- .../android/ui/dashboard/orders/DashboardOrdersCard.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt index 1cca2056dd8..e5b2070049e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/OrderMapper.kt @@ -5,7 +5,6 @@ import com.woocommerce.android.extensions.fastStripHtml import com.woocommerce.android.model.Order.Item import com.woocommerce.android.util.DateUtils import com.woocommerce.android.util.StringUtils -import com.woocommerce.android.viewmodel.ResourceProvider import org.wordpress.android.fluxc.model.OrderEntity import org.wordpress.android.fluxc.model.WCMetaData import org.wordpress.android.fluxc.model.order.FeeLineTaxStatus @@ -24,7 +23,6 @@ import org.wordpress.android.fluxc.model.order.ShippingLine as WCShippingLine class OrderMapper @Inject constructor( private val getLocations: GetLocations, private val dateUtils: DateUtils, - private val resourceProvider: ResourceProvider ) { fun toAppModel(databaseEntity: OrderEntity): Order { val metaDataList = databaseEntity.getMetaDataList() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt index c2e732fdceb..ec0a06ca709 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/orders/DashboardOrdersCard.kt @@ -72,8 +72,6 @@ fun DashboardOrdersCard( HandleEvents(viewModel.event) } - - @Composable private fun HandleEvents( event: LiveData From 8fb4e82aeada619148df247c234de2b477418374 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Wed, 15 May 2024 15:34:09 +0200 Subject: [PATCH 20/21] Filter out empty list when loading cached data --- .../android/ui/orders/list/OrderListRepository.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt index 6f019c24bda..b3bb495c90b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListRepository.kt @@ -134,8 +134,10 @@ class OrderListRepository @Inject constructor( .sortedByDescending { it.dateCreated } .take(count) .map { orderMapper.toAppModel(it) } - .let { orders -> - emit(Result.success(orders.toList())) + .toList() + .takeIf { it.isNotEmpty() } + ?.let { orders -> + emit(Result.success(orders)) } } From b9d261148edd9b82bf9d84ab478892258e9d2c62 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Wed, 15 May 2024 22:38:02 +0200 Subject: [PATCH 21/21] Fix the build --- .../com/woocommerce/android/ui/dashboard/DashboardContainer.kt | 1 + 1 file changed, 1 insertion(+) 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 4f8d6dd0525..228c9ad8d5e 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 @@ -36,6 +36,7 @@ import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.Op import com.woocommerce.android.ui.dashboard.blaze.DashboardBlazeCard import com.woocommerce.android.ui.dashboard.onboarding.DashboardOnboardingCard import com.woocommerce.android.ui.dashboard.orders.DashboardOrdersCard +import com.woocommerce.android.ui.dashboard.reviews.DashboardReviewsCard import com.woocommerce.android.ui.dashboard.stats.DashboardStatsCard import com.woocommerce.android.ui.dashboard.topperformers.DashboardTopPerformersWidgetCard