From ce78ed250c6618f798e7cfea2f99a8af1a106bf7 Mon Sep 17 00:00:00 2001 From: Alejo Date: Thu, 28 Nov 2024 20:13:37 -0300 Subject: [PATCH 1/3] include shipping lines in data state --- .../WooShippingLabelCreationViewModel.kt | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt index bb1e2d6df115..aaa6614c2dd4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.orders.wooshippinglabels import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.extensions.formatToString import com.woocommerce.android.extensions.sumByFloat +import com.woocommerce.android.model.Order import com.woocommerce.android.ui.orders.details.OrderDetailRepository import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippableItemModel import com.woocommerce.android.ui.orders.wooshippinglabels.models.StoreOptionsModel @@ -12,7 +13,6 @@ import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import javax.inject.Inject @@ -37,22 +37,22 @@ class WooShippingLabelCreationViewModel @Inject constructor( init { launch { orderDetailRepository.getOrderById(navArgs.orderId)?.let { order -> - shippableItems.value = getShippableItems(order) - } - } + val items = getShippableItems(order) + shippableItems.value = items - launch { - shippableItems.filter { it.isNotEmpty() }.collect { items -> - val shippableItems = items.map { item -> item.toUIModel(currencyFormatter, storeOptions) } + val shippableItemsUI = items.map { item -> item.toUIModel(currencyFormatter, storeOptions) } val formattedTotalPrice = getTotalPrice(items) val formattedTotalWeight = getTotalWeight(items) + val shippingLineSummary = getShippingLinesSummary(order) + viewState.value = WooShippingViewState.DataState( shippableItems = ShippableItemsUI( - shippableItems = shippableItems, + shippableItems = shippableItemsUI, formattedTotalWeight = formattedTotalWeight, formattedTotalPrice = formattedTotalPrice - ) + ), + shippingLines = shippingLineSummary ) } } @@ -71,6 +71,15 @@ class WooShippingLabelCreationViewModel @Inject constructor( return "${totalWeight.formatToString()} ${storeOptions.weightUnit}" } + private fun getShippingLinesSummary(order: Order): List { + return order.shippingLines.map { + ShippingLineSummaryUI( + title = it.methodTitle, + amount = currencyFormatter.formatCurrency(it.total, order.currency) + ) + } + } + fun onSelectPackageClicked() { triggerEvent(StartPackageSelection) } @@ -85,7 +94,8 @@ class WooShippingLabelCreationViewModel @Inject constructor( sealed class WooShippingViewState { data object Loading : WooShippingViewState() data class DataState( - val shippableItems: ShippableItemsUI + val shippableItems: ShippableItemsUI, + val shippingLines: List ) : WooShippingViewState() } } From be4ef6c9789dee27c4da1ab595a754be2241ce22 Mon Sep 17 00:00:00 2001 From: Alejo Date: Thu, 28 Nov 2024 20:14:44 -0300 Subject: [PATCH 2/3] using shipping lines and items in shipment details section --- .../wooshippinglabels/ShipmentDetails.kt | 53 +++++++++++++------ .../WooShippingLabelCreationScreen.kt | 15 ++++-- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt index 9e5d71554c5e..44d7c1bc88a2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt @@ -52,6 +52,8 @@ import kotlinx.coroutines.launch @Composable fun ShipmentDetails( scaffoldState: BottomSheetScaffoldState, + shippableItems: ShippableItemsUI, + shippingLines: List, markOrderComplete: Boolean, onMarkOrderCompleteChange: (Boolean) -> Unit, modifier: Modifier = Modifier, @@ -98,18 +100,26 @@ fun ShipmentDetails( } } if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) { - ShipmentDetailsLandscape(modifier = modifier) + ShipmentDetailsLandscape( + shippableItems = shippableItems, + shippingLines = shippingLines, + modifier = modifier + ) } else { ShipmentDetailsPortrait( - modifier = modifier, + shippableItems = shippableItems, + shippingLines = shippingLines, markOrderComplete = markOrderComplete, - onMarkOrderCompleteChange = onMarkOrderCompleteChange + onMarkOrderCompleteChange = onMarkOrderCompleteChange, + modifier = modifier ) } } @Composable private fun ShipmentDetailsPortrait( + shippableItems: ShippableItemsUI, + shippingLines: List, markOrderComplete: Boolean, onMarkOrderCompleteChange: (Boolean) -> Unit, modifier: Modifier = Modifier, @@ -126,9 +136,9 @@ private fun ShipmentDetailsPortrait( OrderDetailsSection( shipFrom = getShipFrom(), shipTo = getShipTo(), - totalItems = 5, - totalItemsCost = "$120.99", - shippingLines = getShippingLines(3) + totalItems = shippableItems.shippableItems.size, + totalItemsCost = shippableItems.formattedTotalPrice, + shippingLines = shippingLines ) Divider(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.major_100))) ShipmentCostSection( @@ -147,6 +157,8 @@ private fun ShipmentDetailsPortrait( @Composable private fun ShipmentDetailsLandscape( + shippableItems: ShippableItemsUI, + shippingLines: List, modifier: Modifier = Modifier, ) { Column(modifier) { @@ -169,9 +181,9 @@ private fun ShipmentDetailsLandscape( .fillMaxWidth() ) { OrderDetailsSectionLandscape( - totalItems = 5, - totalItemsCost = "$120.99", - shippingLines = getShippingLines(3), + totalItems = shippableItems.shippableItems.size, + totalItemsCost = shippableItems.formattedTotalPrice, + shippingLines = shippingLines, modifier = Modifier.weight(1f) ) VerticalDivider(modifier = Modifier.padding(top = dimensionResource(R.dimen.major_100))) @@ -214,7 +226,7 @@ private fun OrderDetailsSection( shipTo: Address, totalItems: Int, totalItemsCost: String, - shippingLines: List, + shippingLines: List, modifier: Modifier = Modifier, ) { Column(modifier.fillMaxWidth()) { @@ -241,7 +253,7 @@ private fun OrderDetailsSection( private fun OrderDetailsSectionLandscape( totalItems: Int, totalItemsCost: String, - shippingLines: List, + shippingLines: List, modifier: Modifier = Modifier, ) { Column(modifier.fillMaxWidth()) { @@ -266,7 +278,14 @@ private fun OrderDetailsSectionLandscape( fun ShipmentDetailsLandscapePreview() { WooThemeWithBackground { Surface { - ShipmentDetailsLandscape() + ShipmentDetailsLandscape( + shippableItems = ShippableItemsUI( + shippableItems = generateItems(6), + formattedTotalWeight = "8.5kg", + formattedTotalPrice = "$92.78" + ), + shippingLines = getShippingLines() + ) } } } @@ -275,7 +294,7 @@ fun ShipmentDetailsLandscapePreview() { private fun TotalCard( totalItems: Int, totalItemsCost: String, - shippingLines: List, + shippingLines: List, modifier: Modifier = Modifier ) { Column(modifier) { @@ -314,7 +333,7 @@ private fun ItemsCostPreview() { @Composable private fun ShippingLines( - shippingLines: List, + shippingLines: List, modifier: Modifier = Modifier ) { Column(modifier) { @@ -445,8 +464,8 @@ private fun ShipmentCostSectionPreview() { } } -private fun getShippingLines(number: Int = 3) = List(number) { i -> - ShippingLineSummary( +fun getShippingLines(number: Int = 3) = List(number) { i -> + ShippingLineSummaryUI( title = "Shipping $i", amount = "$12.99" ) @@ -454,7 +473,7 @@ private fun getShippingLines(number: Int = 3) = List(number) { i -> fun Address.toShippingFromString() = this.getEnvelopeAddress().replace("\n", " ") -data class ShippingLineSummary( +data class ShippingLineSummaryUI( val title: String, val amount: String ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt index ad14d79083d4..f87c6065b985 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt @@ -60,7 +60,8 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) WooShippingLabelCreationScreen( onSelectPackageClick = viewModel::onSelectPackageClicked, onPurchaseShippingLabel = viewModel::onPurchaseShippingLabel, - shippableItems = viewState.shippableItems + shippableItems = viewState.shippableItems, + shippingLines = viewState.shippingLines ) } } @@ -70,6 +71,7 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) @Composable fun WooShippingLabelCreationScreen( shippableItems: ShippableItemsUI, + shippingLines: List, modifier: Modifier = Modifier, onSelectPackageClick: () -> Unit, onPurchaseShippingLabel: () -> Unit @@ -80,7 +82,8 @@ fun WooShippingLabelCreationScreen( shippableItems = shippableItems, modifier = modifier, onSelectPackageClick = onSelectPackageClick, - scaffoldState = scaffoldState + scaffoldState = scaffoldState, + shippingLines = shippingLines ) val isDarkTheme = isSystemInDarkTheme() val isCollapsed = scaffoldState.bottomSheetState.isCollapsed @@ -115,6 +118,7 @@ fun WooShippingLabelCreationScreen( @Composable private fun LabelCreationScreenWithBottomSheet( shippableItems: ShippableItemsUI, + shippingLines: List, modifier: Modifier = Modifier, onSelectPackageClick: () -> Unit, scaffoldState: BottomSheetScaffoldState @@ -123,10 +127,12 @@ private fun LabelCreationScreenWithBottomSheet( sheetContent = { val markOrderComplete = remember { mutableStateOf(false) } ShipmentDetails( - modifier = Modifier.padding(bottom = 74.dp), + shippableItems = shippableItems, + shippingLines = shippingLines, scaffoldState = scaffoldState, markOrderComplete = markOrderComplete.value, - onMarkOrderCompleteChange = { markOrderComplete.value = it } + onMarkOrderCompleteChange = { markOrderComplete.value = it }, + modifier = Modifier.padding(bottom = 74.dp), ) }, sheetPeekHeight = 132.dp, @@ -204,6 +210,7 @@ private fun WooShippingLabelCreationScreenPreview() { formattedTotalWeight = "8.5kg", formattedTotalPrice = "$92.78" ), + shippingLines = getShippingLines(), modifier = Modifier.fillMaxSize(), onSelectPackageClick = {}, onPurchaseShippingLabel = {} From e425df27bd999fca4aff482aeb5b83b30324b035 Mon Sep 17 00:00:00 2001 From: Alejo Date: Thu, 28 Nov 2024 20:15:13 -0300 Subject: [PATCH 3/3] adding unit tests --- .../WooShippingLabelCreationViewModelTest.kt | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt new file mode 100644 index 000000000000..33115d9e766e --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt @@ -0,0 +1,102 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels + +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.model.Order +import com.woocommerce.android.ui.orders.OrderTestUtils +import com.woocommerce.android.ui.orders.details.OrderDetailRepository +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.WooShippingViewState +import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippableItemModel +import com.woocommerce.android.util.CurrencyFormatter +import com.woocommerce.android.viewmodel.BaseUnitTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.math.BigDecimal +import kotlin.test.assertEquals + +@OptIn(ExperimentalCoroutinesApi::class) +class WooShippingLabelCreationViewModelTest : BaseUnitTest() { + private val orderId = 1L + private val defaultShippableItems = List(3) { + ShippableItemModel( + itemId = it.toLong(), + productId = it.toLong(), + title = "Product $it", + price = BigDecimal(it), + quantity = it.toFloat(), + weight = it.toFloat(), + currency = "USD", + imageUrl = "https://example.com/image.jpg", + width = it.toFloat(), + height = it.toFloat(), + length = it.toFloat() + ) + } + private val defaultShippingLines = List(3) { + Order.ShippingLine( + methodTitle = "Shipping Line $it", + total = BigDecimal(it), + methodId = it.toString(), + itemId = it.toLong(), + totalTax = BigDecimal.ZERO, + ) + } + private val orderDetailRepository: OrderDetailRepository = mock() + private val getShippableItems: GetShippableItems = mock() + private val currencyFormatter: CurrencyFormatter = mock { + on { formatCurrency(any(), any(), any()) } doAnswer { + val amount = it.getArgument(0) as BigDecimal + "$ ${amount.toPlainString()}" + } + } + private val savedState: SavedStateHandle = + WooShippingLabelCreationFragmentArgs(orderId = orderId).toSavedStateHandle() + + private lateinit var sut: WooShippingLabelCreationViewModel + + fun createViewModel() { + sut = WooShippingLabelCreationViewModel( + orderDetailRepository = orderDetailRepository, + getShippableItems = getShippableItems, + currencyFormatter = currencyFormatter, + savedState = savedState + ) + } + + @Test + fun `when the order NO contains shipping lines, then NO shipping lines summary is displayed`() = testBlocking { + val order = OrderTestUtils.generateTestOrder(orderId = orderId).copy( + shippingLines = emptyList() + ) + whenever(orderDetailRepository.getOrderById(any())) doReturn order + whenever(getShippableItems(any())) doReturn defaultShippableItems + + createViewModel() + + val currentViewState = sut.viewState.value + assert(currentViewState is WooShippingViewState.DataState) + val dataState = currentViewState as WooShippingViewState.DataState + assert(dataState.shippingLines.isEmpty()) + } + + @Test + fun `when the order contains shipping lines, then shipping lines summary is displayed`() = testBlocking { + val order = OrderTestUtils.generateTestOrder(orderId = orderId).copy( + shippingLines = defaultShippingLines + ) + whenever(orderDetailRepository.getOrderById(any())) doReturn order + whenever(getShippableItems(any())) doReturn defaultShippableItems + + createViewModel() + + val currentViewState = sut.viewState.value + assert(currentViewState is WooShippingViewState.DataState) + val dataState = currentViewState as WooShippingViewState.DataState + assert(dataState.shippingLines.isNotEmpty()) + assertEquals(dataState.shippingLines.size, defaultShippingLines.size) + } +}