Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Shipping Labels Revamp] Add Customs requirement check #13413

Open
wants to merge 23 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cf3b844
Define ShouldRequireCustomsForm use case
ThomazFB Jan 28, 2025
8b9dce0
Add US military state logic into ShouldRequireCustomsForm use case
ThomazFB Jan 28, 2025
0006023
Finish ShouldRequireCustomsForm business logic
ThomazFB Jan 28, 2025
7ba07c7
Make ShouldRequireCustomsForm injectable
ThomazFB Jan 28, 2025
7fd47ed
Add ShouldRequireCustomsFormTest file
ThomazFB Jan 28, 2025
c6bf739
Add ShouldRequireCustomsFormTest unit test scenarios
ThomazFB Jan 28, 2025
3ea8d07
Fix lint issues
ThomazFB Jan 28, 2025
ca67034
Define the CustomsState
ThomazFB Jan 29, 2025
2a1fae8
Adjust CustomsState to match the strategy used inside the WooShipping…
ThomazFB Jan 29, 2025
7b087d2
Combine the Customs state with the ViewState flow
ThomazFB Jan 29, 2025
0aea6b4
Add Flow logic to control Customs requirement changes
ThomazFB Jan 29, 2025
e820ab4
Wire the Customs state with the WooShippingLabelCreationScreen
ThomazFB Jan 29, 2025
f7aab61
Declare CustomsCard associated with the Customs state
ThomazFB Jan 29, 2025
306519d
Add missing Customs string resources
ThomazFB Jan 29, 2025
8b6601b
Define initial scaffold for the Customs section
ThomazFB Jan 29, 2025
50fee13
Adjust Customs card to follow design requirements
ThomazFB Jan 29, 2025
510b5b2
Introduce Badge UI for the Customs card
ThomazFB Jan 29, 2025
a4a4f56
Add Edit action into the Customs section
ThomazFB Jan 29, 2025
bae7fb8
Fix lint issues
ThomazFB Jan 29, 2025
8d042c3
Fix Customs Card paddings
ThomazFB Jan 29, 2025
5057b5c
Fix build errors in WooShippingLabelCreationViewModelTest
ThomazFB Jan 29, 2025
515f0ec
Add unit test coverage for the Custom State values
ThomazFB Jan 29, 2025
092eeb8
Fix lint issues
ThomazFB Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ import com.woocommerce.android.R
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.CustomsState
import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.CustomsState.NotRequired
import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.CustomsState.Unavailable
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
Expand Down Expand Up @@ -93,6 +96,7 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel)
shippingAddresses = viewState.shippingAddresses,
shippingRatesState = viewState.shippingRates,
packageSelectionState = viewState.packageSelection,
customsState = viewState.customsState,
onShippingFromAddressChange = viewModel::onShippingFromAddressChange,
onEditOriginAddress = viewModel::onEditOriginAddress,
onSelectedRateSortOrderChanged = viewModel::onSelectedRateSortOrderChanged,
Expand All @@ -105,7 +109,8 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel)
onNavigateBack = viewModel::onNavigateBack,
purchaseState = viewState.purchaseState,
onShipmentDetailsExpandedChange = viewModel::onShipmentDetailsExpandedChange,
onSelectAddressExpandedChange = viewModel::onSelectAddressExpandedChange
onSelectAddressExpandedChange = viewModel::onSelectAddressExpandedChange,
onEditCustomsClick = {}
)
}

Expand All @@ -124,6 +129,7 @@ fun WooShippingLabelCreationScreen(
shippingRatesState: WooShippingLabelCreationViewModel.ShippingRatesState,
packageSelectionState: PackageSelectionState,
shippingAddresses: WooShippingAddresses,
customsState: CustomsState,
onShippingFromAddressChange: (OriginShippingAddress) -> Unit,
onEditOriginAddress: (OriginShippingAddress) -> Unit,
onSelectPackageClick: () -> Unit,
Expand All @@ -138,6 +144,7 @@ fun WooShippingLabelCreationScreen(
onShipmentDetailsExpandedChange: (Boolean) -> Boolean,
onSelectAddressExpandedChange: (Boolean) -> Boolean,
purchaseState: WooShippingLabelCreationViewModel.PurchaseState,
onEditCustomsClick: () -> Unit,
onNavigateBack: () -> Unit,
modifier: Modifier = Modifier
) {
Expand Down Expand Up @@ -184,6 +191,7 @@ fun WooShippingLabelCreationScreen(
scaffoldState = scaffoldState,
shippingLines = shippingLines,
shippingAddresses = shippingAddresses,
customsState = customsState,
shippingRatesState = shippingRatesState,
packageSelectionState = packageSelectionState,
onShippingFromAddressChange = onShippingFromAddressChange,
Expand All @@ -197,7 +205,8 @@ fun WooShippingLabelCreationScreen(
onNavigateBack = onNavigateBack,
onMarkOrderCompleteChange = onMarkOrderCompleteChange,
shipFromSelectionBottomSheetState = shipFromSelectionBottomSheetState,
onShipmentDetailsExpandedChange = onShipmentDetailsExpandedChange
onShipmentDetailsExpandedChange = onShipmentDetailsExpandedChange,
onEditCustomsClick = onEditCustomsClick
)
val isDarkTheme = isSystemInDarkTheme()
val isCollapsed = scaffoldState.bottomSheetState.isCollapsed
Expand Down Expand Up @@ -254,6 +263,7 @@ private fun LabelCreationScreenWithBottomSheet(
shippingLines: List<ShippingLineSummaryUI>,
shippingRatesState: WooShippingLabelCreationViewModel.ShippingRatesState,
packageSelectionState: PackageSelectionState,
customsState: CustomsState,
onSelectPackageClick: () -> Unit,
shippingAddresses: WooShippingAddresses,
onEditOriginAddress: (OriginShippingAddress) -> Unit,
Expand All @@ -269,6 +279,7 @@ private fun LabelCreationScreenWithBottomSheet(
onMarkOrderCompleteChange: (Boolean) -> Unit,
onNavigateBack: () -> Unit,
onShipmentDetailsExpandedChange: (Boolean) -> Boolean,
onEditCustomsClick: () -> Unit,
modifier: Modifier = Modifier
) {
val isPurchaseButtonDisplayed = shippingRatesState is WooShippingLabelCreationViewModel.ShippingRatesState.DataState
Expand Down Expand Up @@ -339,6 +350,13 @@ private fun LabelCreationScreenWithBottomSheet(
.fillMaxWidth()
.padding(start = 4.dp, end = 8.dp)
)
CustomsCard(
customsState = customsState,
onEditCustomsClick = onEditCustomsClick,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
PackageCard(
modifier = Modifier.padding(16.dp),
packageSelectionState = packageSelectionState,
Expand Down Expand Up @@ -393,6 +411,68 @@ internal fun HazmatCard(
}
}

@Composable
private fun CustomsCard(
modifier: Modifier = Modifier,
customsState: CustomsState,
onEditCustomsClick: () -> Unit
) {
if (customsState !is NotRequired) {
Row(
modifier = modifier
.background(
color = MaterialTheme.colors.surface,
shape = RoundedCornerShape(dimensionResource(R.dimen.corner_radius_large))
)
.border(
width = dimensionResource(R.dimen.minor_10),
color = colorResource(R.color.divider_color),
shape = RoundedCornerShape(dimensionResource(R.dimen.corner_radius_large))
)
.padding(start = 16.dp, top = 6.dp, bottom = 6.dp, end = 8.dp)
) {
Text(
text = stringResource(id = R.string.shipping_labels_customs_title),
style = MaterialTheme.typography.subtitle1,
color = MaterialTheme.colors.onSurface,
textAlign = TextAlign.Start,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.align(Alignment.CenterVertically)
.weight(1f)
)
Box(
modifier = Modifier
.background(
color = colorResource(id = R.color.woo_red_20),
shape = RoundedCornerShape(dimensionResource(R.dimen.corner_radius_medium))
)
.align(Alignment.CenterVertically)
) {
Text(
text = stringResource(id = R.string.shipping_labels_customs_missing_info_badge),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSurface,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}

IconButton(
onClick = onEditCustomsClick,
modifier = Modifier
.align(Alignment.CenterVertically)
) {
Icon(
imageVector = Icons.Filled.Edit,
tint = colorResource(id = R.color.color_icon_menu),
contentDescription = stringResource(id = R.string.shipping_label_package_selected_description)
)
}
}
}
}

@Composable
private fun PackageCard(
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -635,6 +715,7 @@ private fun WooShippingLabelCreationScreenPreview() {
),
shippingRatesState = WooShippingLabelCreationViewModel.ShippingRatesState.NoAvailable,
packageSelectionState = NotSelected,
customsState = Unavailable,
onShippingFromAddressChange = {},
onRefreshShippingRates = {},
onSelectedRateSortOrderChanged = {},
Expand All @@ -651,7 +732,8 @@ private fun WooShippingLabelCreationScreenPreview() {
isAddressSelectionExpanded = false
),
onShipmentDetailsExpandedChange = { true },
onSelectAddressExpandedChange = { true }
onSelectAddressExpandedChange = { true },
onEditCustomsClick = {}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,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.CustomsState.NotRequired
import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.CustomsState.Unavailable
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.address.ObserveOriginAddresses
import com.woocommerce.android.ui.orders.wooshippinglabels.customs.ShouldRequireCustomsForm
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
Expand Down Expand Up @@ -52,7 +55,8 @@ class WooShippingLabelCreationViewModel @Inject constructor(
private val observeOriginAddresses: ObserveOriginAddresses,
private val getShippingRates: GetShippingRates,
private val purchaseShippingLabel: PurchaseShippingLabel,
private val observeStoreOptions: ObserveStoreOptions
private val observeStoreOptions: ObserveStoreOptions,
private val shouldRequireCustoms: ShouldRequireCustomsForm
) : ScopedViewModel(savedState) {
private val navArgs: WooShippingLabelCreationFragmentArgs by savedState.navArgs()

Expand All @@ -66,6 +70,7 @@ class WooShippingLabelCreationViewModel @Inject constructor(
private val packageSelected = MutableStateFlow<PackageData?>(null)
private val packageWeight = MutableStateFlow<PackageWeight?>(null)
private val packageSelection = MutableStateFlow<PackageSelectionState>(NotSelected)
private val customsState = MutableStateFlow<CustomsState>(NotRequired)

private val uiState = MutableStateFlow(
UIControlsState(
Expand Down Expand Up @@ -104,6 +109,7 @@ class WooShippingLabelCreationViewModel @Inject constructor(
launch { observePackageChanges() }
launch { observeShippingRates() }
launch { observeShippingRatesState() }
launch { observeCustomsDataChanges() }
}

private suspend fun getOrderInformation() {
Expand Down Expand Up @@ -210,6 +216,19 @@ class WooShippingLabelCreationViewModel @Inject constructor(
}
}

// This logic will be updated later once the Customs data state is available
private suspend fun observeCustomsDataChanges() {
combine(
shippingAddresses,
customsState
) { addresses, _ ->
when {
addresses != null && shouldRequireCustoms(addresses) -> Unavailable
else -> NotRequired
}
}.collectLatest { customsState.value = it }
}

private suspend fun getShippingAddresses() {
order.combine(observeOriginAddresses()) { order, originAddresses ->
if (order != null && !originAddresses.isNullOrEmpty()) {
Expand Down Expand Up @@ -260,7 +279,8 @@ class WooShippingLabelCreationViewModel @Inject constructor(
packageSelection,
uiState,
purchaseState,
) { storeOptions, order, addresses, shippingRates, packageSelection, uiState, purchaseState ->
customsState
) { storeOptions, order, addresses, shippingRates, packageSelection, uiState, purchaseState, customsState ->
if (order == null || storeOptions == null || addresses == null || purchaseState is PurchaseState.Error) {
return@combine WooShippingViewState.Error
}
Expand All @@ -285,7 +305,8 @@ class WooShippingLabelCreationViewModel @Inject constructor(
shippingRates = shippingRates,
packageSelection = packageSelection,
uiState = uiState,
purchaseState = purchaseState
purchaseState = purchaseState,
customsState = customsState
)
}.collectLatest {
viewState.value = it
Expand Down Expand Up @@ -480,7 +501,8 @@ class WooShippingLabelCreationViewModel @Inject constructor(
val shippingRates: ShippingRatesState,
val packageSelection: PackageSelectionState,
val uiState: UIControlsState,
val purchaseState: PurchaseState
val purchaseState: PurchaseState,
val customsState: CustomsState
) : WooShippingViewState()
}

Expand Down Expand Up @@ -541,6 +563,12 @@ class WooShippingLabelCreationViewModel @Inject constructor(
val currencyCode: String?
)

// This will be extended later introducing the state with data coming from the Customs form
sealed class CustomsState {
data object NotRequired : CustomsState()
data object Unavailable : CustomsState()
}

companion object {
private const val TYPING_DELAY = 800L
private const val MULTIPLE_CALLS_DELAY = 50L
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.woocommerce.android.ui.orders.wooshippinglabels.customs

import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses
import javax.inject.Inject

class ShouldRequireCustomsForm @Inject constructor() {
operator fun invoke(addressData: WooShippingAddresses): Boolean {
if (addressData.isDifferentCountryShipment) return true

val isOriginAddressMilitary = isAddressInMilitaryState(
addressData.shipFrom.country,
addressData.shipFrom.state.orEmpty()
)

val isShippingAddressMilitary = isAddressInMilitaryState(
addressData.shipTo.country.code,
addressData.shipTo.state.codeOrRaw
)

return isOriginAddressMilitary || isShippingAddressMilitary
}

private val WooShippingAddresses.isDifferentCountryShipment
get() = shipFrom.country != shipTo.country.code

private fun isAddressInMilitaryState(
countryCode: String,
stateCode: String
) = countryCode == US_COUNTRY_CODE && stateCode in US_MILITARY_STATES

companion object {
const val US_COUNTRY_CODE = "US"
val US_MILITARY_STATES = arrayOf("AA", "AE", "AP")
}
}
3 changes: 3 additions & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,9 @@
<string name="shipping_label_purchased_purchase_failed_error">We can\'t confirm the Shipping Label purchase right now, try again later</string>
<string name="shipping_label_select_origin_default_address">%s (default)</string>
<string name="shipping_label_select_origin_address">Address</string>
<string name="shipping_labels_customs_title">Customs</string>
<string name="shipping_labels_customs_missing_info_badge">Missing info</string>
<string name="shipping_labels_customs_completed_badge">Completed</string>


<!--
Expand Down Expand Up @@ -1524,7 +1527,7 @@
<string name="order_shipment_tracking_provider_toolbar_title">Shipping Carriers</string>
<string name="order_shipment_tracking_provider_list_item">Selected Shipment carrier</string>
<string name="order_shipment_tracking_provider_list_error_fetch_generic">Error fetching carriers</string>
<string name="order_shipment_tracking_provider_list_error_empty_list">No carriers found</string>

Check warning

Code scanning / Android Lint

Unused resources Warning

The resource R.string.order_shipment_tracking_confirm_discard appears to be unused
<string name="order_shipment_tracking_added">Shipment tracking added</string>
<string name="order_shipment_tracking_error">Unable to add tracking</string>
<string name="order_shipment_tracking_confirm_discard">Are you sure you want to discard this tracking?</string>
Expand Down
Loading