diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/GetShippingRates.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/GetShippingRates.kt index 6195b736449..43a0a3e1b14 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/GetShippingRates.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/GetShippingRates.kt @@ -1,7 +1,7 @@ package com.woocommerce.android.ui.orders.wooshippinglabels import com.woocommerce.android.R -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource.PackageDAO +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData import kotlinx.coroutines.delay import javax.inject.Inject import kotlin.random.Random @@ -16,7 +16,7 @@ class GetShippingRates @Inject constructor() { } suspend operator fun invoke( - selectedPackage: PackageDAO, + selectedPackage: PackageData, sortOrder: ShippingSortOption ): Result>> { delay(1_000) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt index 8c5b6ea2ac8..657f857d646 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt @@ -9,11 +9,14 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.StartPackageSelection +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationFragment.Companion.PACKAGE_SELECTION_RESULT +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -36,6 +39,7 @@ class WooShippingLabelCreationFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupObservers() + setupResultHandlers() } override val activityAppBarStatus: AppBarStatus = AppBarStatus.Hidden @@ -59,4 +63,10 @@ class WooShippingLabelCreationFragment : BaseFragment() { } } } + + private fun setupResultHandlers() { + handleResult(PACKAGE_SELECTION_RESULT) { + viewModel.onPackageSelected(it) + } + } } 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 108190d6a12..d2e85affef7 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 @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.orders.wooshippinglabels import android.content.res.Configuration import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement @@ -26,6 +27,9 @@ import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.outlined.Star import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -48,7 +52,11 @@ import com.woocommerce.android.model.Address import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.modifiers.dashedBorder import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.DataAvailable +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.NotSelected import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData @Composable fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) { @@ -67,6 +75,7 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) shippingLines = viewState.shippingLines, shippingAddresses = viewState.shippingAddresses, shippingRatesState = viewState.shippingRates, + packageSelectionState = viewState.packageSelection, onShippingFromAddressChange = viewModel::onShippingFromAddressChange, onShippingToAddressChange = viewModel::onShippingToAddressChange, onSelectedRateSortOrderChanged = viewModel::onSelectedRateSortOrderChanged, @@ -87,6 +96,7 @@ fun WooShippingLabelCreationScreen( shippableItems: ShippableItemsUI, shippingLines: List, shippingRatesState: WooShippingLabelCreationViewModel.ShippingRatesState, + packageSelectionState: PackageSelectionState, shippingAddresses: WooShippingAddresses, onShippingFromAddressChange: (OriginShippingAddress) -> Unit, onShippingToAddressChange: (Address) -> Unit, @@ -106,6 +116,7 @@ fun WooShippingLabelCreationScreen( shippingLines = shippingLines, shippingAddresses = shippingAddresses, shippingRatesState = shippingRatesState, + packageSelectionState = packageSelectionState, onShippingFromAddressChange = onShippingFromAddressChange, onShippingToAddressChange = onShippingToAddressChange, onSelectedRateSortOrderChanged = onSelectedRateSortOrderChanged, @@ -145,6 +156,7 @@ private fun LabelCreationScreenWithBottomSheet( shippableItems: ShippableItemsUI, shippingLines: List, shippingRatesState: WooShippingLabelCreationViewModel.ShippingRatesState, + packageSelectionState: PackageSelectionState, onSelectPackageClick: () -> Unit, shippingAddresses: WooShippingAddresses, onShippingFromAddressChange: (OriginShippingAddress) -> Unit, @@ -209,6 +221,7 @@ private fun LabelCreationScreenWithBottomSheet( ) PackageCard( modifier = Modifier.padding(16.dp), + packageSelectionState = packageSelectionState, onSelectPackageClick = onSelectPackageClick ) WooShippingShippingRatesSection( @@ -272,35 +285,6 @@ private fun WooShippingShippingRatesSection( } } -@Preview(name = "dark", uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL) -@Preview(name = "light", uiMode = Configuration.UI_MODE_NIGHT_NO, device = Devices.PIXEL) -@Composable -private fun WooShippingLabelCreationScreenPreview() { - WooThemeWithBackground { - WooShippingLabelCreationScreen( - shippableItems = ShippableItemsUI( - shippableItems = generateItems(6), - formattedTotalWeight = "8.5kg", - formattedTotalPrice = "$92.78" - ), - shippingLines = getShippingLines(), - modifier = Modifier.fillMaxSize(), - onSelectPackageClick = {}, - onPurchaseShippingLabel = {}, - shippingAddresses = WooShippingAddresses( - shipFrom = getShipFrom(), - shipTo = getShipTo(), - originAddresses = listOf(getShipFrom()) - ), - shippingRatesState = WooShippingLabelCreationViewModel.ShippingRatesState.NoAvailable, - onShippingFromAddressChange = {}, - onShippingToAddressChange = {}, - onRefreshShippingRates = {}, - onSelectedRateSortOrderChanged = {} - ) - } -} - @Composable internal fun HazmatCard( modifier: Modifier = Modifier, @@ -337,16 +321,27 @@ internal fun HazmatCard( } } -@Preview @Composable -private fun HazmatCardPreview() { - WooThemeWithBackground { - HazmatCard(modifier = Modifier.padding(16.dp)) +private fun PackageCard( + modifier: Modifier = Modifier, + packageSelectionState: PackageSelectionState, + onSelectPackageClick: () -> Unit +) { + when (packageSelectionState) { + is NotSelected -> SelectPackageCard( + modifier = modifier, + onSelectPackageClick = onSelectPackageClick + ) + is DataAvailable -> PackageSelectionAvailableCard( + modifier = modifier, + packageData = packageSelectionState.selectedPackage, + onSelectPackageClick = onSelectPackageClick + ) } } @Composable -private fun PackageCard( +private fun SelectPackageCard( modifier: Modifier = Modifier, onSelectPackageClick: () -> Unit ) { @@ -395,14 +390,86 @@ private fun PackageCard( } } -@Preview @Composable -private fun PackageCardPreview() { - WooThemeWithBackground { - PackageCard( - modifier = Modifier.padding(16.dp), - onSelectPackageClick = {} - ) +private fun PackageSelectionAvailableCard( + modifier: Modifier = Modifier, + packageData: PackageData, + onSelectPackageClick: () -> Unit +) { + Column(modifier = modifier.background(color = MaterialTheme.colors.surface)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = stringResource(id = R.string.shipping_label_package_selected_title), + style = MaterialTheme.typography.subtitle1, + fontWeight = FontWeight.SemiBold + ) + IconButton( + onClick = onSelectPackageClick + ) { + Icon( + imageVector = Icons.Filled.Edit, + tint = colorResource(id = R.color.color_icon_menu), + contentDescription = stringResource(id = R.string.shipping_label_package_selected_description) + ) + } + } + Column( + modifier = Modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = colorResource(id = R.color.divider_color), + shape = RoundedCornerShape(8.dp) + ) + .padding(dimensionResource(id = R.dimen.major_125)), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text( + text = packageData.groupName + ?.takeIf { it.isNotEmpty() } + ?: stringResource(id = packageData.descriptionResId), + style = MaterialTheme.typography.caption, + color = colorResource(id = R.color.color_on_surface_disabled) + ) + Text( + text = packageData.name + .takeIf { it.isNotEmpty() } + ?: stringResource(id = R.string.shipping_label_package_default_name), + style = MaterialTheme.typography.body1 + ) + Text( + text = packageData.weight + .takeIf { it.isNotEmpty() } + ?.let { "${packageData.dimensionForDisplay} • ${packageData.weightForDisplay}" } + ?: packageData.dimensionForDisplay, + style = MaterialTheme.typography.body2 + ) + } + + if (packageData.isPredefined) { + Icon( + tint = colorResource(id = R.color.woo_yellow_20), + imageVector = Icons.Filled.Star, + contentDescription = "Star", + ) + } else { + Icon( + tint = colorResource(id = R.color.color_on_surface_disabled), + imageVector = Icons.Outlined.Star, + contentDescription = "Star", + ) + } + } + } } } @@ -422,3 +489,74 @@ data class ShippableItemsUI( val formattedTotalWeight: String, val formattedTotalPrice: String ) + +@Preview(name = "dark", uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL) +@Preview(name = "light", uiMode = Configuration.UI_MODE_NIGHT_NO, device = Devices.PIXEL) +@Composable +private fun WooShippingLabelCreationScreenPreview() { + WooThemeWithBackground { + WooShippingLabelCreationScreen( + shippableItems = ShippableItemsUI( + shippableItems = generateItems(6), + formattedTotalWeight = "8.5kg", + formattedTotalPrice = "$92.78" + ), + shippingLines = getShippingLines(), + modifier = Modifier.fillMaxSize(), + onSelectPackageClick = {}, + onPurchaseShippingLabel = {}, + shippingAddresses = WooShippingAddresses( + shipFrom = getShipFrom(), + shipTo = getShipTo(), + originAddresses = listOf(getShipFrom()) + ), + shippingRatesState = WooShippingLabelCreationViewModel.ShippingRatesState.NoAvailable, + packageSelectionState = NotSelected, + onShippingFromAddressChange = {}, + onShippingToAddressChange = {}, + onRefreshShippingRates = {}, + onSelectedRateSortOrderChanged = {} + ) + } +} + +@Preview +@Composable +private fun HazmatCardPreview() { + WooThemeWithBackground { + HazmatCard(modifier = Modifier.padding(16.dp)) + } +} + +@Preview +@Composable +private fun PackageNotSelectedPreview() { + WooThemeWithBackground { + PackageCard( + modifier = Modifier.padding(16.dp), + packageSelectionState = NotSelected, + onSelectPackageClick = {} + ) + } +} + +@Preview +@Composable +private fun PackageSelectedPreview() { + WooThemeWithBackground { + PackageCard( + modifier = Modifier.padding(16.dp), + packageSelectionState = DataAvailable( + selectedPackage = PackageData( + name = "Package 1", + dimensions = "10 x 10 x 10", + weight = "1.5", + isSelected = true, + isLetter = false + ), + totalWeight = "1.5" + ), + onSelectPackageClick = {} + ) + } +} 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 79b85a93908..ce116ce55a1 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 @@ -6,10 +6,12 @@ import com.woocommerce.android.extensions.sumByFloat import com.woocommerce.android.model.Address import com.woocommerce.android.model.Order import com.woocommerce.android.ui.orders.details.OrderDetailRepository +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.DataAvailable +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.NotSelected import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippableItemModel import com.woocommerce.android.ui.orders.wooshippinglabels.models.StoreOptionsModel -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource.PackageDAO +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import com.woocommerce.android.viewmodel.ScopedViewModel @@ -23,6 +25,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -43,30 +46,24 @@ class WooShippingLabelCreationViewModel @Inject constructor( originCountry = "US" ) - private val mockSelectedPackage = PackageDAO( - id = "small_flat_box", - name = "Small Flat Rate Box", - dimensions = "21.91 x 13.65 x 4.13", - isLetter = false, - weight = "0.5", - dimensionUnit = "cm", - weightUnit = "kg" - ) - private val shippableItems = MutableStateFlow>(emptyList()) - private val selectedPackage = MutableStateFlow(mockSelectedPackage) private val storeOptions = MutableStateFlow(mockStoreOptions) private val selectedRatesSortOrder = MutableStateFlow(ShippingSortOption.FASTEST) private val refreshShippingRates = MutableSharedFlow() + private val packageSelection = MutableStateFlow(NotSelected) + @OptIn(ExperimentalCoroutinesApi::class) private val shippingRates = combine( - selectedPackage, + packageSelection, selectedRatesSortOrder, refreshShippingRates.onStart { emit(Unit) } ) { selectedPackage, sortOrder, _ -> - Pair(selectedPackage, sortOrder) + when (selectedPackage) { + is NotSelected -> PackageData.EMPTY + is DataAvailable -> selectedPackage.selectedPackage + }.let { Pair(it, sortOrder) } }.flatMapLatest { val (selectedPackage, sortOrder) = it refreshShippingRates(selectedPackage, sortOrder) @@ -78,7 +75,10 @@ class WooShippingLabelCreationViewModel @Inject constructor( launch { observeShippingLabelInformation() } } - private fun refreshShippingRates(selectedPackage: PackageDAO, sortOrder: ShippingSortOption) = flow { + private fun refreshShippingRates( + selectedPackage: PackageData, + sortOrder: ShippingSortOption + ) = flow { emit(ShippingRatesState.Loading(sortOrder)) val shippingRatesResult = getShippingRates(selectedPackage, sortOrder) if (shippingRatesResult.isSuccess) { @@ -93,8 +93,9 @@ class WooShippingLabelCreationViewModel @Inject constructor( storeOptions, flowOf(orderDetailRepository.getOrderById(navArgs.orderId)), observeOriginAddresses(), - shippingRates - ) { storeOptions, order, originAddresses, shippingRates -> + shippingRates, + packageSelection + ) { storeOptions, order, originAddresses, shippingRates, packageSelection -> val selectedOriginAddress = getSelectedOriginAddress(originAddresses) if (order == null || selectedOriginAddress == null) { return@combine WooShippingViewState.Error @@ -120,7 +121,8 @@ class WooShippingLabelCreationViewModel @Inject constructor( originAddresses = originAddresses, shipTo = order.shippingAddress ), - shippingRates = shippingRates + shippingRates = shippingRates, + packageSelection = packageSelection ) }.collect { viewState.value = it @@ -193,6 +195,18 @@ class WooShippingLabelCreationViewModel @Inject constructor( selectedRatesSortOrder.value = option } + fun onPackageSelected(packageData: PackageData) { + packageSelection.update { content -> + when (content) { + is NotSelected -> DataAvailable( + selectedPackage = packageData, + totalWeight = packageData.weight + ) + is DataAvailable -> content.copy(selectedPackage = packageData) + } + } + } + data object StartPackageSelection : Event() data object LabelPurchased : Event() @@ -204,6 +218,7 @@ class WooShippingLabelCreationViewModel @Inject constructor( val shippingLines: List, val shippingAddresses: WooShippingAddresses, val shippingRates: ShippingRatesState, + val packageSelection: PackageSelectionState ) : WooShippingViewState() } @@ -220,6 +235,14 @@ class WooShippingLabelCreationViewModel @Inject constructor( val shippingRates: Map> ) : ShippingRatesState() } + + sealed class PackageSelectionState { + data object NotSelected : PackageSelectionState() + data class DataAvailable( + val selectedPackage: PackageData, + val totalWeight: String + ) : PackageSelectionState() + } } data class WooShippingAddresses( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationFragment.kt index 790675534ab..fcdffb3796d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationFragment.kt @@ -6,18 +6,17 @@ import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed -import androidx.fragment.app.setFragmentResult import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.R import com.woocommerce.android.extensions.handleDialogResult +import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PackageSelected import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PackageType import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.ShowPackageTypeDialog -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -45,7 +44,7 @@ class WooShippingLabelPackageCreationFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { is ShowPackageTypeDialog -> handlePackageTypeSelection(event.currentSelection) - is PackageSelected -> handlePackageDataAsResult(event.packageData) + is PackageSelected -> navigateBackWithResult(PACKAGE_SELECTION_RESULT, event.packageData) } } } @@ -73,13 +72,6 @@ class WooShippingLabelPackageCreationFragment : BaseFragment() { ).let { findNavController().navigateSafely(it) } } - private fun handlePackageDataAsResult(packageData: PackageData) { - setFragmentResult( - PACKAGE_SELECTION_RESULT, - Bundle().apply { putParcelable(PACKAGE_SELECTION_RESULT, packageData) } - ) - } - companion object { const val SELECTOR_REQUEST_KEY = "package_type" const val PACKAGE_SELECTION_RESULT = "package_selection" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt index 25927bf1ddf..d9cc1cea51f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt @@ -31,9 +31,11 @@ class FetchPredefinedPackagesFromStore @Inject constructor( dimensions = packageDAO.dimensions, weight = packageDAO.weight, isSelected = false, + isPredefined = true, isLetter = packageDAO.isLetter, dimensionUnit = packageDAO.dimensionUnit, - weightUnit = packageDAO.weightUnit + weightUnit = packageDAO.weightUnit, + groupName = packageDAO.groupName ) } @@ -59,9 +61,11 @@ class FetchPredefinedPackagesFromStore @Inject constructor( dimensions = packageItem.dimensions, weight = packageItem.weight, isSelected = false, + isPredefined = true, isLetter = packageItem.isLetter, dimensionUnit = packageItem.dimensionUnit, - weightUnit = packageItem.weightUnit + weightUnit = packageItem.weightUnit, + groupName = packageItem.groupName ) } ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/PackageDAOs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/PackageDAOs.kt index 828d0753b82..8dac306da6d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/PackageDAOs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/PackageDAOs.kt @@ -12,7 +12,8 @@ data class PackageDAO( val weight: String, val isLetter: Boolean, val dimensionUnit: String, - val weightUnit: String + val weightUnit: String, + val groupName: String? = null ) data class CarrierDAO( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt index a491240f716..14d6b23ac0b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt @@ -90,7 +90,8 @@ class WooShippingLabelPackageMapper @Inject constructor() { weight = it.boxWeight?.toString().orEmpty(), isLetter = it.isLetter ?: false, dimensionUnit = storeOptions?.dimensionUnit.orEmpty(), - weightUnit = storeOptions?.weightUnit.orEmpty() + weightUnit = storeOptions?.weightUnit.orEmpty(), + groupName = title ) } ?: emptyList() ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt index 3975be27171..6708dda82af 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt @@ -14,8 +14,10 @@ data class PackageData( val weight: String, val isSelected: Boolean, val isLetter: Boolean, + val isPredefined: Boolean = false, val dimensionUnit: String = "cm", - val weightUnit: String = "kg" + val weightUnit: String = "kg", + val groupName: String? = null ) : Parcelable { @IgnoredOnParcel val length: String @@ -44,6 +46,17 @@ data class PackageData( val weightForDisplay get() = "$weight $weightUnit" + + companion object { + val EMPTY = PackageData( + name = "", + dimensions = "", + weight = "", + isSelected = false, + isLetter = false, + groupName = null + ) + } } @Parcelize @@ -70,11 +83,13 @@ data class CustomPackageCreationData( } fun toPackageData(dimensionUnit: String = "cm") = PackageData( - name = "", - dimensions = "$length x $width x $height $dimensionUnit", + name = name.orEmpty(), + dimensions = "$length x $width x $height", weight = weight.orEmpty(), isSelected = true, - isLetter = type == PackageType.ENVELOPE + isLetter = type == PackageType.ENVELOPE, + dimensionUnit = dimensionUnit, + isPredefined = saveAsTemplate ) companion object { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt index d39f544cbbf..b4307b531e5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt @@ -210,7 +210,11 @@ private fun PackageList( packageGroups: List, onPackageSelected: (PackageData, Boolean) -> Unit ) { - Column(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .fillMaxSize() + ) { packageGroups.forEach { group -> Spacer(modifier = Modifier.height(8.dp)) PackageListSection( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCustomPackageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCustomPackageScreen.kt index d2e91b9298d..12b568b4583 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCustomPackageScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCustomPackageScreen.kt @@ -80,10 +80,11 @@ fun WooShippingCustomPackageCreationScreen( modifier = modifier .fillMaxSize() .padding(16.dp) - .verticalScroll(rememberScrollState()) ) { Column( - modifier = modifier.weight(1f), + modifier = modifier + .weight(1f) + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Column(modifier = modifier) { diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 8103794b8ca..ce30b82b398 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -1263,6 +1263,9 @@ Are you shipping dangerous goods or hazardous materials? Select a Package Select a package to get shipping rates + Custom Package + Package + Change package selection Enter your package\'s dimensions or pick a carrier package option to see the available shipping rates. Shipment details Order details @@ -1281,6 +1284,7 @@ Schedule pickup Request refund + 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 index 497fc4b2b8b..600b453ff61 100644 --- 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 @@ -1,16 +1,21 @@ package com.woocommerce.android.ui.orders.wooshippinglabels import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.asLiveData 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.PackageSelectionState.DataAvailable import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.WooShippingViewState +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.WooShippingViewState.DataState import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippableItemModel +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.BaseUnitTest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf +import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer @@ -110,8 +115,8 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { createViewModel() val currentViewState = sut.viewState.value - assert(currentViewState is WooShippingViewState.DataState) - val dataState = currentViewState as WooShippingViewState.DataState + assert(currentViewState is DataState) + val dataState = currentViewState as DataState assert(dataState.shippingLines.isEmpty()) } @@ -128,8 +133,8 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { createViewModel() val currentViewState = sut.viewState.value - assert(currentViewState is WooShippingViewState.DataState) - val dataState = currentViewState as WooShippingViewState.DataState + assert(currentViewState is DataState) + val dataState = currentViewState as DataState assert(dataState.shippingLines.isNotEmpty()) assertEquals(dataState.shippingLines.size, defaultShippingLines.size) } @@ -175,8 +180,8 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { createViewModel() val currentViewState = sut.viewState.value - assert(currentViewState is WooShippingViewState.DataState) - val dataState = currentViewState as WooShippingViewState.DataState + assert(currentViewState is DataState) + val dataState = currentViewState as DataState assertEquals(dataState.shippingAddresses.originAddresses.size, defaultOriginAddresses.size) val ids = dataState.shippingAddresses.originAddresses.map { it.id } assert(ids.containsAll(defaultOriginAddresses.map { it.id })) @@ -195,8 +200,8 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { createViewModel() val currentViewState = sut.viewState.value - assert(currentViewState is WooShippingViewState.DataState) - val dataState = currentViewState as WooShippingViewState.DataState + assert(currentViewState is DataState) + val dataState = currentViewState as DataState assertIs(dataState.shippingRates) } @@ -213,8 +218,8 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { createViewModel() val currentViewState = sut.viewState.value - assert(currentViewState is WooShippingViewState.DataState) - val dataState = currentViewState as WooShippingViewState.DataState + assert(currentViewState is DataState) + val dataState = currentViewState as DataState assertIs(dataState.shippingRates) } @@ -267,4 +272,82 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { verify(getShippingRates, times(1)).invoke(any(), any()) } + + @Test + fun `onPackageSelected updates state to DataAvailable when current state is NotSelected`() = testBlocking { + var currentViewState: WooShippingViewState? = null + val order = OrderTestUtils.generateTestOrder(orderId = orderId).copy( + shippingLines = defaultShippingLines + ) + whenever(orderDetailRepository.getOrderById(any())) doReturn order + whenever(getShippableItems(any())) doReturn defaultShippableItems + whenever(observeOriginAddresses()) doReturn flowOf(defaultOriginAddresses) + whenever(getShippingRates(any(), any())) doReturn Result.success(defaultShippingRates) + + createViewModel() + sut.viewState.asLiveData().observeForever { + currentViewState = it + } + + val initialPackageData = PackageData( + name = "Initial Package", + dimensions = "5 x 5 x 5", + weight = "0.5", + isSelected = true, + isLetter = false + ) + + sut.onPackageSelected(initialPackageData) + + assertThat(currentViewState).isInstanceOf(DataState::class.java) + val dataState = currentViewState as DataState + + assertThat(dataState.packageSelection).isInstanceOf(DataAvailable::class.java) + val dataAvailable = dataState.packageSelection as DataAvailable + assertThat(dataAvailable.selectedPackage).isEqualTo(initialPackageData) + } + + @Test + fun `onPackageSelected updates state to DataAvailable when current state is DataAvailable`() = testBlocking { + var currentViewState: WooShippingViewState? = null + val order = OrderTestUtils.generateTestOrder(orderId = orderId).copy( + shippingLines = defaultShippingLines + ) + whenever(orderDetailRepository.getOrderById(any())) doReturn order + whenever(getShippableItems(any())) doReturn defaultShippableItems + whenever(observeOriginAddresses()) doReturn flowOf(defaultOriginAddresses) + whenever(getShippingRates(any(), any())) doReturn Result.success(defaultShippingRates) + + createViewModel() + sut.viewState.asLiveData().observeForever { + currentViewState = it + } + + val initialPackageData = PackageData( + name = "Initial Package", + dimensions = "5 x 5 x 5", + weight = "0.5", + isSelected = true, + isLetter = false + ) + + sut.onPackageSelected(initialPackageData) + + val newPackageData = PackageData( + name = "New Package", + dimensions = "10 x 10 x 10", + weight = "1.5", + isSelected = true, + isLetter = false + ) + + sut.onPackageSelected(newPackageData) + + assertThat(currentViewState).isInstanceOf(DataState::class.java) + val dataState = currentViewState as DataState + + assertThat(dataState.packageSelection).isInstanceOf(DataAvailable::class.java) + val dataAvailable = dataState.packageSelection as DataAvailable + assertThat(dataAvailable.selectedPackage).isEqualTo(newPackageData) + } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt index 3ed7d7910ca..6e9d590ad66 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt @@ -42,14 +42,16 @@ class FetchPredefinedPackagesFromStoreTest : BaseUnitTest() { dimensions = "dimensions", weight = "weight", isSelected = false, - isLetter = false + isLetter = false, + isPredefined = true ), PackageData( name = "Saved Package 2", dimensions = "dimensions", weight = "weight", isSelected = false, - isLetter = false + isLetter = false, + isPredefined = true ) ) assertThat(result.carrierPackages[Carrier.USPS]).containsExactly( @@ -61,7 +63,8 @@ class FetchPredefinedPackagesFromStoreTest : BaseUnitTest() { dimensions = "dimensions", weight = "weight", isSelected = false, - isLetter = false + isLetter = false, + isPredefined = true ) ) ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/CustomPackageCreationDataTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/CustomPackageCreationDataTest.kt index 67549d82e48..e011b4d93c3 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/CustomPackageCreationDataTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/CustomPackageCreationDataTest.kt @@ -75,7 +75,7 @@ class CustomPackageCreationDataTest { val packageData = data.toPackageData() assertThat(packageData.name).isEqualTo("") - assertThat(packageData.dimensions).isEqualTo("10 x 10 x 10 cm") + assertThat(packageData.dimensions).isEqualTo("10 x 10 x 10") assertThat(packageData.isSelected).isTrue assertThat(packageData.isLetter).isFalse } @@ -93,7 +93,7 @@ class CustomPackageCreationDataTest { val packageData = data.toPackageData() assertThat(packageData.name).isEqualTo("") - assertThat(packageData.dimensions).isEqualTo("10 x 10 x 10 cm") + assertThat(packageData.dimensions).isEqualTo("10 x 10 x 10") assertThat(packageData.isSelected).isTrue assertThat(packageData.isLetter).isTrue }