From d773c388677797414544a9b10b00eaef80436a79 Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 2 Mar 2025 22:57:11 -0600 Subject: [PATCH 01/18] add notification colors --- .../purchased/WooShippingLabelPurchasedScreen.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt index e2dd4e144aa..4eeb97e1c5b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt @@ -67,10 +67,20 @@ private val darkGreen = Color(0xFF005C12) @Suppress("MagicNumber") private val lightGreen = Color(0xFFEDFAEF) +@Suppress("MagicNumber") +private val darkRed = Color(0xFFB32D2E) + +@Suppress("MagicNumber") +private val lightRed = Color(0xFFF7EBEC) + val Colors.successColor: Color get() = if (isLight) darkGreen else lightGreen val Colors.successSurface: Color get() = if (isLight) lightGreen else darkGreen +val Colors.errorColor: Color get() = if (isLight) darkRed else lightRed + +val Colors.errorSurface: Color get() = if (isLight) lightRed else darkRed + @Composable fun WooShippingLabelPurchasedScreen(viewModel: WooShippingLabelPurchasedViewModel) { val viewState = viewModel.viewState.observeAsState() From b35d33b0c270216f6b26dbf67ab0dad079ecf745 Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 2 Mar 2025 22:58:08 -0600 Subject: [PATCH 02/18] add use case to trigger notifications --- .../address/ObserveAddressNotification.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt new file mode 100644 index 00000000000..d37cc3d9f52 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt @@ -0,0 +1,76 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.address + +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses +import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import com.woocommerce.android.R +import javax.inject.Inject + +class ObserveAddressNotification @Inject constructor( + private val resourceProvider: ResourceProvider +) { + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) + operator fun invoke(addresses: Flow): Flow { + return addresses.map { addresses -> + when { + addresses.shipFrom.isVerified.not() -> { + AddressNotification( + isSuccess = false, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_origin_unverified + ), + isDestinationNotification = false + ) + } + + addresses.shipTo.address.hasInfo().not() -> { + AddressNotification( + isSuccess = false, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_destination_missing + ), + isDestinationNotification = true + ) + } + + addresses.shipTo.isVerified.not() -> { + AddressNotification( + isSuccess = false, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_destination_unverified + ), + isDestinationNotification = true + ) + } + + addresses.shipTo.isVerified -> { + AddressNotification( + isSuccess = true, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_destination_verified + ), + expireAfter = 2_000, + isDestinationNotification = true + ) + } + + else -> null + } + } + } +} + +data class AddressNotification( + val isSuccess: Boolean, + val message: String, + val expireAfter: Long? = null, + private val timestamp: Long = System.currentTimeMillis(), + val isDestinationNotification: Boolean = false, +) { + fun isExpired(): Boolean = expireAfter?.let { + timestamp + it < System.currentTimeMillis() + } == true +} From 929ccf5a7e8d219d0b4f7315adb937f832e58384 Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 2 Mar 2025 22:58:22 -0600 Subject: [PATCH 03/18] add string resources --- WooCommerce/src/main/res/values/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 252afa29e68..34805be8cab 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4510,6 +4510,13 @@ The address verification failed. Please try again. We couldn\'t update your address. Please try again. + Destination address unverified + Origin address unverified + Destination address missing + + Verified destination address + Verified origin address + Validate & Save Add missing information From ee69d7d0e37de3c231b8add8b67742bbfb24fc65 Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 2 Mar 2025 22:59:03 -0600 Subject: [PATCH 04/18] display address notifications --- .../WooShippingLabelCreationViewModel.kt | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 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 a10f8411c7e..15b25cfdfa3 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 @@ -17,6 +17,8 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreat 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.AddressNotification +import com.woocommerce.android.ui.orders.wooshippinglabels.address.ObserveAddressNotification import com.woocommerce.android.ui.orders.wooshippinglabels.address.destination.VerifyDestinationAddress import com.woocommerce.android.ui.orders.wooshippinglabels.address.origin.FetchOriginAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.address.origin.ObserveOriginAddresses @@ -39,12 +41,14 @@ import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CancellationException import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.update import kotlinx.coroutines.joinAll @@ -66,7 +70,8 @@ class WooShippingLabelCreationViewModel @Inject constructor( private val observeStoreOptions: ObserveStoreOptions, private val fetchAccountSettings: FetchAccountSettings, private val shouldRequireCustoms: ShouldRequireCustomsForm, - private val verifyDestinationAddress: VerifyDestinationAddress + private val verifyDestinationAddress: VerifyDestinationAddress, + private val observeAddressNotification: ObserveAddressNotification ) : ScopedViewModel(savedState) { private val navArgs: WooShippingLabelCreationFragmentArgs by savedState.navArgs() @@ -123,6 +128,7 @@ class WooShippingLabelCreationViewModel @Inject constructor( launch { observeShippingRates() } launch { observeShippingRatesState() } launch { observeCustomsDataChanges() } + launch { observeNotifications() } } private suspend fun getOrderInformation() { @@ -134,6 +140,25 @@ class WooShippingLabelCreationViewModel @Inject constructor( } } + @OptIn(FlowPreview::class) + private suspend fun observeNotifications() { + observeAddressNotification( + shippingAddresses.filterNotNull() + ).onStart { + delay(NOTIFICATIONS_DELAY) + } + .collectLatest { notification -> + if (notification != null && notification.isSuccess && uiState.value.addressNotification == null) { + return@collectLatest + } + uiState.update { it.copy(addressNotification = notification) } + } + } + + fun dismissAddressNotification() { + uiState.update { it.copy(addressNotification = null) } + } + private suspend fun getStoreOptions() { observeStoreOptions().collectLatest { options -> storeOptions.value = options @@ -626,7 +651,8 @@ class WooShippingLabelCreationViewModel @Inject constructor( data class UIControlsState( val markOrderComplete: Boolean, val isShipmentDetailsExpanded: Boolean, - val isAddressSelectionExpanded: Boolean + val isAddressSelectionExpanded: Boolean, + val addressNotification: AddressNotification? = null ) data class ShippingRatesInfo( @@ -645,6 +671,7 @@ class WooShippingLabelCreationViewModel @Inject constructor( } companion object { + private const val NOTIFICATIONS_DELAY = 2_000L private const val TYPING_DELAY = 800L private const val MULTIPLE_CALLS_DELAY = 50L } From 1eba6cbe40dcfe5e47befa4de54c019e21d0ad2e Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 2 Mar 2025 22:59:33 -0600 Subject: [PATCH 05/18] address notifications/warnings UI --- .../wooshippinglabels/ShipmentDetails.kt | 115 +++++++++++++++++- .../WooShippingLabelCreationScreen.kt | 24 +++- .../WooShippingLabelPurchasedScreen.kt | 4 +- 3 files changed, 136 insertions(+), 7 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 30c8d1d1ab8..6f6b3ddde55 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 @@ -19,7 +19,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.BottomSheetScaffoldState import androidx.compose.material.Divider @@ -29,8 +31,13 @@ import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Surface import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CheckCircleOutline +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.outlined.Info import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -48,13 +55,19 @@ import com.woocommerce.android.R import com.woocommerce.android.extensions.appendWithIfNotEmpty import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.orders.wooshippinglabels.address.AddressNotification import com.woocommerce.android.ui.orders.wooshippinglabels.address.AddressSectionLandscape import com.woocommerce.android.ui.orders.wooshippinglabels.address.AddressSectionPortrait import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipFrom import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipTo import com.woocommerce.android.ui.orders.wooshippinglabels.models.DestinationShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.errorColor +import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.errorSurface +import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.successColor +import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.successSurface import com.woocommerce.android.util.StringUtils +import kotlinx.coroutines.delay import kotlinx.parcelize.Parcelize @Composable @@ -65,12 +78,15 @@ fun ShipmentDetails( shippingLines: List, shippingAddresses: WooShippingAddresses, shippingRateSummary: ShippingRateSummaryUI?, + addressNotification: AddressNotification?, modifier: Modifier = Modifier, isShipmentDetailsExpanded: Boolean = false, onShipmentDetailsExpandedChange: (Boolean) -> Boolean, onEditDestinationAddress: (DestinationShippingAddress) -> Unit, + onEditOriginAddress: (OriginShippingAddress) -> Unit, markOrderComplete: Boolean = false, onMarkOrderCompleteChange: (Boolean) -> Unit = {}, + dismissAddressNotification: () -> Unit = {}, handlerModifier: Modifier = Modifier, isReadOnly: Boolean = false ) { @@ -98,13 +114,33 @@ fun ShipmentDetails( tint = colorResource(id = R.color.color_primary), ) AnimatedVisibility(isShipmentDetailsExpanded.not()) { - Column { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { Text( text = stringResource(R.string.shipping_label_shipment_details_title), color = MaterialTheme.colors.primary, modifier = Modifier .padding(top = dimensionResource(R.dimen.minor_100)) ) + + ShippingAddressNotification( + addressNotification = addressNotification, + onDismiss = dismissAddressNotification, + onAction = { + addressNotification?.let { + when { + it.isSuccess.not() && it.isDestinationNotification -> { + onEditDestinationAddress(shippingAddresses.shipTo) + } + it.isSuccess.not() && it.isDestinationNotification.not() -> { + onEditOriginAddress(shippingAddresses.shipFrom) + } + } + } + } + ) + Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.major_200))) } } @@ -505,6 +541,83 @@ private fun ShipmentCostRow( } } +@Composable +private fun ShippingAddressNotification( + addressNotification: AddressNotification?, + modifier: Modifier = Modifier, + onAction: () -> Unit = {}, + onDismiss: () -> Unit = {} +) { + if (addressNotification != null && addressNotification.isExpired().not()) { + if (addressNotification.expireAfter != null) { + LaunchedEffect(addressNotification) { + delay(addressNotification.expireAfter) + onDismiss() + } + } + + val color = if (addressNotification.isSuccess) { + MaterialTheme.colors.successColor + } else { + MaterialTheme.colors.errorColor + } + + val backgroundColor = if (addressNotification.isSuccess) { + MaterialTheme.colors.successSurface + } else { + MaterialTheme.colors.errorSurface + } + + val icon = if (addressNotification.isSuccess) { + Icons.Outlined.CheckCircleOutline + } else { + Icons.Outlined.Info + } + + val configuration = LocalConfiguration.current + val rowModifier = when (configuration.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + modifier.widthIn(max = 600.dp).fillMaxWidth() + } + else -> { + modifier.fillMaxWidth() + } + } + + Row( + rowModifier + .padding(dimensionResource(R.dimen.major_100)) + .background( + color = backgroundColor, + shape = RoundedCornerShape(dimensionResource(R.dimen.corner_radius_large)) + ) + .clickable { onAction() } + .padding(vertical = 8.dp, horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = color, + modifier = Modifier.padding(end = 8.dp) + ) + Text( + text = addressNotification.message, + color = color, + modifier = Modifier.weight(1f) + ) + if (addressNotification.isSuccess.not()) { + Icon( + imageVector = Icons.Outlined.Close, + contentDescription = null, + tint = color, + modifier = Modifier.clickable { onDismiss() } + ) + } + } + } +} + @Preview @Composable private fun ShipmentCostSectionPreview() { 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 60e6836f8c2..5c350ab5c02 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 @@ -114,7 +114,8 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) onShipmentDetailsExpandedChange = viewModel::onShipmentDetailsExpandedChange, onSelectAddressExpandedChange = viewModel::onSelectAddressExpandedChange, onEditCustomsClick = viewModel::onEditCustomsClick, - onEditDestinationAddress = viewModel::onEditDestinationAddress + onEditDestinationAddress = viewModel::onEditDestinationAddress, + dismissAddressNotification = viewModel::dismissAddressNotification ) } @@ -154,6 +155,7 @@ fun WooShippingLabelCreationScreen( onEditCustomsClick: () -> Unit, onNavigateBack: () -> Unit, onEditDestinationAddress: (DestinationShippingAddress) -> Unit, + dismissAddressNotification: () -> Unit = {}, modifier: Modifier = Modifier ) { val shipmentDetailsValue = if (uiState.isShipmentDetailsExpanded) { @@ -215,7 +217,8 @@ fun WooShippingLabelCreationScreen( shipFromSelectionBottomSheetState = shipFromSelectionBottomSheetState, onShipmentDetailsExpandedChange = onShipmentDetailsExpandedChange, onEditCustomsClick = onEditCustomsClick, - onEditDestinationAddress = onEditDestinationAddress + onEditDestinationAddress = onEditDestinationAddress, + dismissAddressNotification = dismissAddressNotification ) val isDarkTheme = isSystemInDarkTheme() val isCollapsed = scaffoldState.bottomSheetState.isCollapsed @@ -290,11 +293,19 @@ private fun LabelCreationScreenWithBottomSheet( onShipmentDetailsExpandedChange: (Boolean) -> Boolean, onEditCustomsClick: () -> Unit, onEditDestinationAddress: (DestinationShippingAddress) -> Unit, + dismissAddressNotification: () -> Unit = {}, modifier: Modifier = Modifier ) { val isPurchaseButtonDisplayed = shippingRatesState is WooShippingLabelCreationViewModel.ShippingRatesState.DataState - val bottomSheetPeekHeight = if (isPurchaseButtonDisplayed) 132.dp else 76.dp - val paddingBottom = if (isPurchaseButtonDisplayed) 72.dp else 0.dp + + val bottomSheetPeekHeight = when { + isPurchaseButtonDisplayed || uiState.addressNotification != null -> 132.dp + else -> 76.dp + } + val paddingBottom = when { + isPurchaseButtonDisplayed -> 72.dp + else -> 0.dp + } val shippingRateSummary = (shippingRatesState as? WooShippingLabelCreationViewModel.ShippingRatesState.DataState)?.selectedRate?.summary @@ -319,7 +330,10 @@ private fun LabelCreationScreenWithBottomSheet( isShipmentDetailsExpanded = uiState.isShipmentDetailsExpanded, markOrderComplete = uiState.markOrderComplete, onShipmentDetailsExpandedChange = onShipmentDetailsExpandedChange, - onEditDestinationAddress = onEditDestinationAddress + onEditDestinationAddress = onEditDestinationAddress, + addressNotification = uiState.addressNotification, + dismissAddressNotification = dismissAddressNotification, + onEditOriginAddress = onEditOriginAddress ) } }, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt index 4eeb97e1c5b..deb2beb76ca 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt @@ -136,7 +136,9 @@ internal fun WooShippingLabelPurchasedWithBottomSheetScreen( } true }, - onEditDestinationAddress = {} + addressNotification = null, + onEditDestinationAddress = {}, + onEditOriginAddress = {} ) } }, From 1851ffc1d2dd7595f1d9939dd34b993b5818517e Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 3 Mar 2025 07:51:32 -0600 Subject: [PATCH 06/18] fix unit tests --- .../wooshippinglabels/WooShippingLabelCreationViewModelTest.kt | 3 +++ 1 file changed, 3 insertions(+) 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 164a932226d..1ba04cc98ab 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 @@ -12,6 +12,7 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreat import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PurchaseState 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.address.ObserveAddressNotification import com.woocommerce.android.ui.orders.wooshippinglabels.address.destination.VerifyDestinationAddress import com.woocommerce.android.ui.orders.wooshippinglabels.address.origin.ObserveOriginAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.customs.ShouldRequireCustomsForm @@ -217,6 +218,7 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { private val purchaseShippingLabel: PurchaseShippingLabel = mock() private val observeStoreOptions: ObserveStoreOptions = mock() private val verifyDestinationAddress: VerifyDestinationAddress = mock() + private val observeAddressNotification: ObserveAddressNotification = mock() private lateinit var sut: WooShippingLabelCreationViewModel @@ -233,6 +235,7 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { fetchAccountSettings = mock(), shouldRequireCustoms = shouldRequireCustomsForm, verifyDestinationAddress = verifyDestinationAddress, + observeAddressNotification = observeAddressNotification, savedState = savedState ) } From 4d33de45e6338b8966f63c10011027566df734b3 Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 3 Mar 2025 10:38:01 -0600 Subject: [PATCH 07/18] refactor -> move all notification logic to the getAddressNotification use case --- .../WooShippingLabelCreationViewModel.kt | 21 +++-- .../address/GetAddressNotification.kt | 88 +++++++++++++++++++ .../address/ObserveAddressNotification.kt | 76 ---------------- .../WooShippingLabelCreationViewModelTest.kt | 6 +- 4 files changed, 101 insertions(+), 90 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt 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 15b25cfdfa3..f8cefe3d140 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 @@ -18,7 +18,7 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreat 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.AddressNotification -import com.woocommerce.android.ui.orders.wooshippinglabels.address.ObserveAddressNotification +import com.woocommerce.android.ui.orders.wooshippinglabels.address.GetAddressNotification import com.woocommerce.android.ui.orders.wooshippinglabels.address.destination.VerifyDestinationAddress import com.woocommerce.android.ui.orders.wooshippinglabels.address.origin.FetchOriginAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.address.origin.ObserveOriginAddresses @@ -40,6 +40,7 @@ import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -50,6 +51,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.runningFold import kotlinx.coroutines.flow.update import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch @@ -71,7 +73,7 @@ class WooShippingLabelCreationViewModel @Inject constructor( private val fetchAccountSettings: FetchAccountSettings, private val shouldRequireCustoms: ShouldRequireCustomsForm, private val verifyDestinationAddress: VerifyDestinationAddress, - private val observeAddressNotification: ObserveAddressNotification + private val getAddressNotification: GetAddressNotification ) : ScopedViewModel(savedState) { private val navArgs: WooShippingLabelCreationFragmentArgs by savedState.navArgs() @@ -140,17 +142,14 @@ class WooShippingLabelCreationViewModel @Inject constructor( } } - @OptIn(FlowPreview::class) + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) private suspend fun observeNotifications() { - observeAddressNotification( - shippingAddresses.filterNotNull() - ).onStart { - delay(NOTIFICATIONS_DELAY) - } + shippingAddresses.filterNotNull() + .onStart { delay(NOTIFICATIONS_DELAY) } + .runningFold(initial = null as AddressNotification?) { previousNotification, addresses -> + getAddressNotification(addresses, previousNotification) + } .collectLatest { notification -> - if (notification != null && notification.isSuccess && uiState.value.addressNotification == null) { - return@collectLatest - } uiState.update { it.copy(addressNotification = notification) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt new file mode 100644 index 00000000000..ccd07f1a7be --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt @@ -0,0 +1,88 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.address + +import com.woocommerce.android.R +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses +import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import javax.inject.Inject + +class GetAddressNotification @Inject constructor( + private val resourceProvider: ResourceProvider +) { + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) + operator fun invoke( + addresses: WooShippingAddresses, + previousNotification: AddressNotification? = null + ): AddressNotification? { + return when { + addresses.shipFrom.isVerified.not() -> { + AddressNotification( + isSuccess = false, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_origin_unverified + ), + isDestinationNotification = false + ) + } + + addresses.shipTo.address.hasInfo().not() -> { + AddressNotification( + isSuccess = false, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_destination_missing + ), + isDestinationNotification = true + ) + } + + addresses.shipTo.isVerified.not() -> { + AddressNotification( + isSuccess = false, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_destination_unverified + ), + isDestinationNotification = true + ) + } + + addresses.shipTo.isVerified && + previousNotification?.let { it.isSuccess.not() && it.isDestinationNotification } == true -> { + AddressNotification( + isSuccess = true, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_destination_verified + ), + expireAfter = 2_000, + isDestinationNotification = true + ) + } + + addresses.shipFrom.isVerified && + previousNotification?.let { it.isSuccess.not() && it.isDestinationNotification.not() } == true -> { + AddressNotification( + isSuccess = true, + message = resourceProvider.getString( + R.string.woo_shipping_address_notification_origin_verified + ), + expireAfter = 2_000, + isDestinationNotification = true + ) + } + + else -> null + } + } +} + +data class AddressNotification( + val isSuccess: Boolean, + val message: String, + val expireAfter: Long? = null, + private val timestamp: Long = System.currentTimeMillis(), + val isDestinationNotification: Boolean = false, +) { + fun isExpired(): Boolean = expireAfter?.let { + timestamp + it < System.currentTimeMillis() + } == true +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt deleted file mode 100644 index d37cc3d9f52..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveAddressNotification.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.woocommerce.android.ui.orders.wooshippinglabels.address - -import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses -import com.woocommerce.android.viewmodel.ResourceProvider -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import com.woocommerce.android.R -import javax.inject.Inject - -class ObserveAddressNotification @Inject constructor( - private val resourceProvider: ResourceProvider -) { - @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) - operator fun invoke(addresses: Flow): Flow { - return addresses.map { addresses -> - when { - addresses.shipFrom.isVerified.not() -> { - AddressNotification( - isSuccess = false, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_origin_unverified - ), - isDestinationNotification = false - ) - } - - addresses.shipTo.address.hasInfo().not() -> { - AddressNotification( - isSuccess = false, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_destination_missing - ), - isDestinationNotification = true - ) - } - - addresses.shipTo.isVerified.not() -> { - AddressNotification( - isSuccess = false, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_destination_unverified - ), - isDestinationNotification = true - ) - } - - addresses.shipTo.isVerified -> { - AddressNotification( - isSuccess = true, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_destination_verified - ), - expireAfter = 2_000, - isDestinationNotification = true - ) - } - - else -> null - } - } - } -} - -data class AddressNotification( - val isSuccess: Boolean, - val message: String, - val expireAfter: Long? = null, - private val timestamp: Long = System.currentTimeMillis(), - val isDestinationNotification: Boolean = false, -) { - fun isExpired(): Boolean = expireAfter?.let { - timestamp + it < System.currentTimeMillis() - } == true -} 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 1ba04cc98ab..aca5afafd97 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 @@ -12,7 +12,7 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreat import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PurchaseState 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.address.ObserveAddressNotification +import com.woocommerce.android.ui.orders.wooshippinglabels.address.GetAddressNotification import com.woocommerce.android.ui.orders.wooshippinglabels.address.destination.VerifyDestinationAddress import com.woocommerce.android.ui.orders.wooshippinglabels.address.origin.ObserveOriginAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.customs.ShouldRequireCustomsForm @@ -218,7 +218,7 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { private val purchaseShippingLabel: PurchaseShippingLabel = mock() private val observeStoreOptions: ObserveStoreOptions = mock() private val verifyDestinationAddress: VerifyDestinationAddress = mock() - private val observeAddressNotification: ObserveAddressNotification = mock() + private val getAddressNotification: GetAddressNotification = mock() private lateinit var sut: WooShippingLabelCreationViewModel @@ -235,7 +235,7 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { fetchAccountSettings = mock(), shouldRequireCustoms = shouldRequireCustomsForm, verifyDestinationAddress = verifyDestinationAddress, - observeAddressNotification = observeAddressNotification, + getAddressNotification = getAddressNotification, savedState = savedState ) } From 1f631aeab1a43378c555d7a9d26a36572081680d Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 3 Mar 2025 11:36:53 -0600 Subject: [PATCH 08/18] fix origin success notification --- .../orders/wooshippinglabels/address/GetAddressNotification.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt index ccd07f1a7be..46911815de4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt @@ -66,7 +66,7 @@ class GetAddressNotification @Inject constructor( R.string.woo_shipping_address_notification_origin_verified ), expireAfter = 2_000, - isDestinationNotification = true + isDestinationNotification = false ) } From 0c3b156d63a70cd1c8fbe2733f2999c0c310b09f Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 3 Mar 2025 11:37:12 -0600 Subject: [PATCH 09/18] add get notifications unit tests --- .../address/GetAddressNotificationTests.kt | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt new file mode 100644 index 00000000000..40d11973da7 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt @@ -0,0 +1,163 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.address + +import com.woocommerce.android.R +import com.woocommerce.android.model.Address +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses +import com.woocommerce.android.ui.orders.wooshippinglabels.models.DestinationShippingAddress +import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import kotlin.test.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class GetAddressNotificationTests : BaseUnitTest() { + + private val resourceProvider: ResourceProvider = mock { + on { getString(any()) } doAnswer { invocationOnMock -> invocationOnMock.arguments[0].toString() } + } + private val sut = GetAddressNotification(resourceProvider) + + private val defaultAddresses = WooShippingAddresses( + shipFrom = OriginShippingAddress.EMPTY.copy( + id = "1", + firstName = "John", + lastName = "Doe", + address1 = "123 Main St", + isVerified = true + ), + shipTo = DestinationShippingAddress( + address = Address.EMPTY.copy( + firstName = "John", + lastName = "Doe", + address1 = "123 Main St" + ), + isVerified = true + ), + originAddresses = emptyList() + ) + + @Test + fun `when addresses as no issues, then don't display any notification`(){ + val result = sut.invoke(defaultAddresses) + assertThat(result).isNull() + } + + @Test + fun `when addresses as no issues and previous was a destination warning, then display destination success`(){ + val previous = AddressNotification( + isSuccess = false, + message = "Destination warning", + isDestinationNotification = true + ) + val result = sut.invoke(defaultAddresses, previous) + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isTrue + assertThat(result.isDestinationNotification).isTrue + } + + @Test + fun `when addresses as no issues and previous was a origin warning, then display origin success`(){ + val previous = AddressNotification( + isSuccess = false, + message = "Origin warning", + isDestinationNotification = false + ) + + val result = sut.invoke(defaultAddresses, previous) + + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isTrue + assertThat(result.isDestinationNotification).isFalse + } + + @Test + fun `when shipTo is not verified, then display destination not verified`(){ + val addresses = defaultAddresses.copy(shipTo = defaultAddresses.shipTo.copy(isVerified = false)) + + val result = sut.invoke(addresses, null) + + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isFalse + assertThat(result.isDestinationNotification).isTrue + assertThat(result.message).isEqualTo( + R.string.woo_shipping_address_notification_destination_unverified.toString() + ) + } + + @Test + fun `when shipFrom is not verified, then display origin not verified`(){ + val addresses = defaultAddresses.copy(shipFrom = defaultAddresses.shipFrom.copy(isVerified = false)) + + val result = sut.invoke(addresses, null) + + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isFalse + assertThat(result.isDestinationNotification).isFalse + assertThat(result.message).isEqualTo( + R.string.woo_shipping_address_notification_origin_unverified.toString() + ) + } + + @Test + fun `when shipTo is missing, then display destination missing`(){ + val addresses = defaultAddresses.copy(shipTo = DestinationShippingAddress.EMPTY) + + val result = sut.invoke(addresses, null) + + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isFalse + assertThat(result.isDestinationNotification).isTrue + assertThat(result.message).isEqualTo( + R.string.woo_shipping_address_notification_destination_missing.toString() + ) + } + + @Test + fun `testing both addresses with issues flow`() { + var addresses = defaultAddresses.copy( + shipTo = defaultAddresses.shipTo.copy(isVerified = false), + shipFrom = defaultAddresses.shipFrom.copy(isVerified = false) + ) + + // When address have issues, then display origin warnings first + + var result = sut.invoke(addresses, null) + + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isFalse + assertThat(result.isDestinationNotification).isFalse + assertThat(result.message).isEqualTo( + R.string.woo_shipping_address_notification_origin_unverified.toString() + ) + + // Fix origin issue + addresses = defaultAddresses.copy( + shipTo = defaultAddresses.shipTo.copy(isVerified = false) + ) + + result = sut.invoke(addresses, result) + + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isFalse + assertThat(result.isDestinationNotification).isTrue + assertThat(result.message).isEqualTo( + R.string.woo_shipping_address_notification_destination_unverified.toString() + ) + + // Fix destination issue + addresses = defaultAddresses + result = sut.invoke(addresses, result) + + assertThat(result).isNotNull + assertThat(result!!.isSuccess).isTrue + assertThat(result.isDestinationNotification).isTrue + assertThat(result.message).isEqualTo( + R.string.woo_shipping_address_notification_destination_verified.toString() + ) + } +} From 91846a26f8577acc6fdd0caed8edf2128c13e985 Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 3 Mar 2025 11:54:59 -0600 Subject: [PATCH 10/18] unit tests dismissing notification --- .../WooShippingLabelCreationViewModelTest.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) 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 aca5afafd97..c29c83e6dad 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 @@ -12,6 +12,7 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreat import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PurchaseState 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.address.AddressNotification import com.woocommerce.android.ui.orders.wooshippinglabels.address.GetAddressNotification import com.woocommerce.android.ui.orders.wooshippinglabels.address.destination.VerifyDestinationAddress import com.woocommerce.android.ui.orders.wooshippinglabels.address.origin.ObserveOriginAddresses @@ -42,6 +43,7 @@ import kotlinx.coroutines.test.advanceUntilIdle import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @@ -857,4 +859,61 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { val shouldNavigateBack = sut.allowBackNavigation() assertThat(shouldNavigateBack).isTrue() } + + @Test + fun `when there are address notifications then display the notification`() = testBlocking { + val order = OrderTestUtils.generateTestOrder(orderId = orderId) + val notification = AddressNotification( + isSuccess = false, + message = "Origin warning", + isDestinationNotification = false + ) + + whenever(orderDetailRepository.getOrderById(any())) doReturn order + whenever(getShippableItems(any())) doReturn defaultShippableItems + whenever(observeOriginAddresses()) doReturn flowOf(defaultOriginAddresses) + whenever(observeStoreOptions()) doReturn flowOf(defaultStoreOptions) + whenever(getAddressNotification(any(), anyOrNull())) doReturn notification + + createViewModel() + + advanceUntilIdle() + + val currentViewState = sut.viewState.value + assertThat(currentViewState).isInstanceOf(DataState::class.java) + val dataState = currentViewState as DataState + assertThat(dataState.uiState.addressNotification).isEqualTo(notification) + } + + @Test + fun `when an address notifications is displayed then dismissAddressNotification should dismiss the notification`() = testBlocking { + val order = OrderTestUtils.generateTestOrder(orderId = orderId) + val notification = AddressNotification( + isSuccess = false, + message = "Origin warning", + isDestinationNotification = false + ) + + whenever(orderDetailRepository.getOrderById(any())) doReturn order + whenever(getShippableItems(any())) doReturn defaultShippableItems + whenever(observeOriginAddresses()) doReturn flowOf(defaultOriginAddresses) + whenever(observeStoreOptions()) doReturn flowOf(defaultStoreOptions) + whenever(getAddressNotification(any(), anyOrNull())) doReturn notification + + createViewModel() + + advanceUntilIdle() + + var currentViewState = sut.viewState.value + assertThat(currentViewState).isInstanceOf(DataState::class.java) + var dataState = currentViewState as DataState + assertThat(dataState.uiState.addressNotification).isEqualTo(notification) + + sut.dismissAddressNotification() + + currentViewState = sut.viewState.value + assertThat(currentViewState).isInstanceOf(DataState::class.java) + dataState = currentViewState as DataState + assertThat(dataState.uiState.addressNotification).isNull() + } } From 2941dc632d5f21114cec6f2958ee5de2c20bd4e2 Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 3 Mar 2025 16:39:07 -0600 Subject: [PATCH 11/18] fix detekt warnings --- .../address/GetAddressNotificationTests.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt index 40d11973da7..62283ae072f 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt @@ -42,13 +42,13 @@ class GetAddressNotificationTests : BaseUnitTest() { ) @Test - fun `when addresses as no issues, then don't display any notification`(){ + fun `when addresses as no issues, then don't display any notification`() { val result = sut.invoke(defaultAddresses) assertThat(result).isNull() } @Test - fun `when addresses as no issues and previous was a destination warning, then display destination success`(){ + fun `when addresses as no issues and previous was a destination warning, then display destination success`() { val previous = AddressNotification( isSuccess = false, message = "Destination warning", @@ -61,7 +61,7 @@ class GetAddressNotificationTests : BaseUnitTest() { } @Test - fun `when addresses as no issues and previous was a origin warning, then display origin success`(){ + fun `when addresses as no issues and previous was a origin warning, then display origin success`() { val previous = AddressNotification( isSuccess = false, message = "Origin warning", @@ -76,7 +76,7 @@ class GetAddressNotificationTests : BaseUnitTest() { } @Test - fun `when shipTo is not verified, then display destination not verified`(){ + fun `when shipTo is not verified, then display destination not verified`() { val addresses = defaultAddresses.copy(shipTo = defaultAddresses.shipTo.copy(isVerified = false)) val result = sut.invoke(addresses, null) @@ -90,7 +90,7 @@ class GetAddressNotificationTests : BaseUnitTest() { } @Test - fun `when shipFrom is not verified, then display origin not verified`(){ + fun `when shipFrom is not verified, then display origin not verified`() { val addresses = defaultAddresses.copy(shipFrom = defaultAddresses.shipFrom.copy(isVerified = false)) val result = sut.invoke(addresses, null) @@ -104,7 +104,7 @@ class GetAddressNotificationTests : BaseUnitTest() { } @Test - fun `when shipTo is missing, then display destination missing`(){ + fun `when shipTo is missing, then display destination missing`() { val addresses = defaultAddresses.copy(shipTo = DestinationShippingAddress.EMPTY) val result = sut.invoke(addresses, null) From fd7fdade2c132d674e5cbe4e2a90f965fe2da027 Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 3 Mar 2025 17:17:21 -0600 Subject: [PATCH 12/18] add notification animation --- .../wooshippinglabels/ShipmentDetails.kt | 135 +++++++++++------- 1 file changed, 81 insertions(+), 54 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 6f6b3ddde55..eb2c942d194 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 @@ -3,6 +3,10 @@ package com.woocommerce.android.ui.orders.wooshippinglabels import android.content.res.Configuration import android.os.Parcelable import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -548,71 +552,94 @@ private fun ShippingAddressNotification( onAction: () -> Unit = {}, onDismiss: () -> Unit = {} ) { - if (addressNotification != null && addressNotification.isExpired().not()) { - if (addressNotification.expireAfter != null) { - LaunchedEffect(addressNotification) { - delay(addressNotification.expireAfter) - onDismiss() - } - } + AnimatedVisibility( + visible = addressNotification != null && addressNotification.isExpired().not(), + enter = fadeIn( + animationSpec = androidx.compose.animation.core.tween( + durationMillis = 180 + ) + ) + scaleIn( + animationSpec = androidx.compose.animation.core.tween( + durationMillis = 180 + ) + ), + exit = fadeOut( + animationSpec = androidx.compose.animation.core.tween( + durationMillis = 90 + ) + ) + scaleOut( + animationSpec = androidx.compose.animation.core.tween( + durationMillis = 90 + ) - val color = if (addressNotification.isSuccess) { - MaterialTheme.colors.successColor - } else { - MaterialTheme.colors.errorColor - } + ) + ) { + if (addressNotification != null && addressNotification.isExpired().not()) { + if (addressNotification.expireAfter != null) { + LaunchedEffect(addressNotification) { + delay(addressNotification.expireAfter) + onDismiss() + } + } - val backgroundColor = if (addressNotification.isSuccess) { - MaterialTheme.colors.successSurface - } else { - MaterialTheme.colors.errorSurface - } + val color = if (addressNotification.isSuccess) { + MaterialTheme.colors.successColor + } else { + MaterialTheme.colors.errorColor + } - val icon = if (addressNotification.isSuccess) { - Icons.Outlined.CheckCircleOutline - } else { - Icons.Outlined.Info - } + val backgroundColor = if (addressNotification.isSuccess) { + MaterialTheme.colors.successSurface + } else { + MaterialTheme.colors.errorSurface + } - val configuration = LocalConfiguration.current - val rowModifier = when (configuration.orientation) { - Configuration.ORIENTATION_LANDSCAPE -> { - modifier.widthIn(max = 600.dp).fillMaxWidth() + val icon = if (addressNotification.isSuccess) { + Icons.Outlined.CheckCircleOutline + } else { + Icons.Outlined.Info } - else -> { - modifier.fillMaxWidth() + + val configuration = LocalConfiguration.current + val rowModifier = when (configuration.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + modifier.widthIn(max = 600.dp).fillMaxWidth() + } + else -> { + modifier.fillMaxWidth() + } } - } - Row( - rowModifier - .padding(dimensionResource(R.dimen.major_100)) - .background( - color = backgroundColor, - shape = RoundedCornerShape(dimensionResource(R.dimen.corner_radius_large)) - ) - .clickable { onAction() } - .padding(vertical = 8.dp, horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = color, - modifier = Modifier.padding(end = 8.dp) - ) - Text( - text = addressNotification.message, - color = color, - modifier = Modifier.weight(1f) - ) - if (addressNotification.isSuccess.not()) { + Row( + rowModifier + .padding(dimensionResource(R.dimen.major_100)) + .background( + color = backgroundColor, + shape = RoundedCornerShape(dimensionResource(R.dimen.corner_radius_large)) + ) + .clickable { onAction() } + .padding(vertical = 8.dp, horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { Icon( - imageVector = Icons.Outlined.Close, + imageVector = icon, contentDescription = null, tint = color, - modifier = Modifier.clickable { onDismiss() } + modifier = Modifier.padding(end = 8.dp) ) + Text( + text = addressNotification.message, + color = color, + modifier = Modifier.weight(1f) + ) + if (addressNotification.isSuccess.not()) { + Icon( + imageVector = Icons.Outlined.Close, + contentDescription = null, + tint = color, + modifier = Modifier.clickable { onDismiss() } + ) + } } } } From 5a46fee24abd04d4237f3524ed33653e6edda3b4 Mon Sep 17 00:00:00 2001 From: Alejo Date: Tue, 4 Mar 2025 12:34:07 -0600 Subject: [PATCH 13/18] fix huge font size --- .../ui/orders/wooshippinglabels/ShipmentDetails.kt | 8 ++++++-- .../wooshippinglabels/WooShippingLabelCreationScreen.kt | 7 ++++--- 2 files changed, 10 insertions(+), 5 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 eb2c942d194..4b29f899a88 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 @@ -125,7 +125,7 @@ fun ShipmentDetails( text = stringResource(R.string.shipping_label_shipment_details_title), color = MaterialTheme.colors.primary, modifier = Modifier - .padding(top = dimensionResource(R.dimen.minor_100)) + .padding(top = dimensionResource(R.dimen.minor_100) * LocalConfiguration.current.fontScale) ) ShippingAddressNotification( @@ -145,7 +145,11 @@ fun ShipmentDetails( } ) - Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.major_200))) + Spacer( + modifier = Modifier.size( + dimensionResource(R.dimen.major_200) * LocalConfiguration.current.fontScale + ) + ) } } } 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 5c350ab5c02..dd9e5e6c77f 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 @@ -299,9 +299,10 @@ private fun LabelCreationScreenWithBottomSheet( val isPurchaseButtonDisplayed = shippingRatesState is WooShippingLabelCreationViewModel.ShippingRatesState.DataState val bottomSheetPeekHeight = when { - isPurchaseButtonDisplayed || uiState.addressNotification != null -> 132.dp - else -> 76.dp - } + isPurchaseButtonDisplayed || uiState.addressNotification != null -> 128.dp + else -> 72.dp + } * LocalConfiguration.current.fontScale + val paddingBottom = when { isPurchaseButtonDisplayed -> 72.dp else -> 0.dp From dd69830c67ca5d1efd1713785ddb5acd402f85ed Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 5 Mar 2025 16:22:29 -0600 Subject: [PATCH 14/18] refactor -> use resource colors --- .../wooshippinglabels/ShipmentDetails.kt | 12 ++---- .../address/AddressSection.kt | 5 +-- .../WooShippingLabelPurchasedScreen.kt | 38 ++++--------------- .../src/main/res/values/colors_base.xml | 9 +++++ 4 files changed, 23 insertions(+), 41 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 4b29f899a88..9b42ad4a6e7 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 @@ -66,10 +66,6 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipFrom import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipTo import com.woocommerce.android.ui.orders.wooshippinglabels.models.DestinationShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress -import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.errorColor -import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.errorSurface -import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.successColor -import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.successSurface import com.woocommerce.android.util.StringUtils import kotlinx.coroutines.delay import kotlinx.parcelize.Parcelize @@ -587,15 +583,15 @@ private fun ShippingAddressNotification( } val color = if (addressNotification.isSuccess) { - MaterialTheme.colors.successColor + colorResource(id = R.color.woo_shipping_label_success) } else { - MaterialTheme.colors.errorColor + colorResource(id = R.color.woo_shipping_label_error) } val backgroundColor = if (addressNotification.isSuccess) { - MaterialTheme.colors.successSurface + colorResource(id = R.color.woo_shipping_label_success_surface) } else { - MaterialTheme.colors.errorSurface + colorResource(id = R.color.woo_shipping_label_error_surface) } val icon = if (addressNotification.isSuccess) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/AddressSection.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/AddressSection.kt index d477763e19f..046fc6d8eee 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/AddressSection.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/AddressSection.kt @@ -54,7 +54,6 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.VerticalDivider import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.models.DestinationShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress -import com.woocommerce.android.ui.orders.wooshippinglabels.purchased.successColor import com.woocommerce.android.ui.orders.wooshippinglabels.rates.ui.shippingSelectedBackgroundColor import com.woocommerce.android.ui.orders.wooshippinglabels.toShippingFromString import kotlinx.coroutines.launch @@ -577,8 +576,8 @@ fun AddressStatusIndicator( } val color = when (addressStatus) { - AddressStatus.VERIFIED -> MaterialTheme.colors.successColor - else -> MaterialTheme.colors.error + AddressStatus.VERIFIED -> colorResource(id = R.color.woo_shipping_label_success) + else -> colorResource(id = R.color.woo_shipping_label_error) } val icon = when (addressStatus) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt index deb2beb76ca..1bbea6aa5dc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/purchased/WooShippingLabelPurchasedScreen.kt @@ -16,7 +16,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.ButtonDefaults.buttonColors -import androidx.compose.material.Colors import androidx.compose.material.Divider import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenuItem @@ -41,7 +40,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -61,26 +59,6 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.ShippingProductsCard import com.woocommerce.android.ui.orders.wooshippinglabels.generateItems import kotlinx.coroutines.launch -@Suppress("MagicNumber") -private val darkGreen = Color(0xFF005C12) - -@Suppress("MagicNumber") -private val lightGreen = Color(0xFFEDFAEF) - -@Suppress("MagicNumber") -private val darkRed = Color(0xFFB32D2E) - -@Suppress("MagicNumber") -private val lightRed = Color(0xFFF7EBEC) - -val Colors.successColor: Color get() = if (isLight) darkGreen else lightGreen - -val Colors.successSurface: Color get() = if (isLight) lightGreen else darkGreen - -val Colors.errorColor: Color get() = if (isLight) darkRed else lightRed - -val Colors.errorSurface: Color get() = if (isLight) lightRed else darkRed - @Composable fun WooShippingLabelPurchasedScreen(viewModel: WooShippingLabelPurchasedViewModel) { val viewState = viewModel.viewState.observeAsState() @@ -342,12 +320,12 @@ private fun PrintShippingLabelCard( Column( modifier = modifier .background( - color = MaterialTheme.colors.successSurface, + color = colorResource(id = R.color.woo_shipping_label_success), shape = RoundedCornerShape(dimensionResource(R.dimen.corner_radius_large)) ) .padding(16.dp) ) { - RoundedCornerBoxWithBorder(backgroundColor = MaterialTheme.colors.successSurface) { + RoundedCornerBoxWithBorder(backgroundColor = colorResource(id = R.color.woo_shipping_label_success_surface)) { LabelPaperSizeDropdownMenu( selectedLabelPaperSizeOption = selectedLabelPaperSizeOption, onLabelPaperSizeOptionSelected = onLabelPaperSizeOptionSelected, @@ -365,7 +343,7 @@ private fun PrintShippingLabelCard( .fillMaxWidth() .padding(top = 12.dp, bottom = 4.dp), colors = buttonColors( - backgroundColor = MaterialTheme.colors.successColor, + backgroundColor = colorResource(id = R.color.woo_shipping_label_success), contentColor = MaterialTheme.colors.surface ) ) @@ -381,12 +359,12 @@ private fun PrintShippingLabelCard( modifier = Modifier .padding(end = 8.dp) .size(16.dp), - tint = MaterialTheme.colors.successColor + tint = colorResource(id = R.color.woo_shipping_label_success) ) Text( text = stringResource(id = R.string.shipping_label_purchased_learn_how_to_print), style = MaterialTheme.typography.caption, - color = MaterialTheme.colors.successColor + color = colorResource(id = R.color.woo_shipping_label_success) ) } Divider(modifier = Modifier.padding(vertical = 8.dp)) @@ -445,7 +423,7 @@ private fun LabelPaperSizeDropdownMenu( R.string.sorted_by, stringResource(selectedLabelPaperSizeOption.stringResource) ), - tint = MaterialTheme.colors.successColor + tint = colorResource(id = R.color.woo_shipping_label_success) ) } @@ -485,14 +463,14 @@ private fun ShippingLabelLink( Text( text = text, style = MaterialTheme.typography.subtitle1, - color = MaterialTheme.colors.successColor, + color = colorResource(id = R.color.woo_shipping_label_success), fontWeight = FontWeight.Bold ) if (showIcon) { Icon( imageVector = Icons.AutoMirrored.Outlined.OpenInNew, contentDescription = null, - tint = MaterialTheme.colors.successColor, + tint = colorResource(id = R.color.woo_shipping_label_success), modifier = Modifier.padding(start = 8.dp) ) } diff --git a/WooCommerce/src/main/res/values/colors_base.xml b/WooCommerce/src/main/res/values/colors_base.xml index b9ab2c29e5f..f99000a2fdb 100644 --- a/WooCommerce/src/main/res/values/colors_base.xml +++ b/WooCommerce/src/main/res/values/colors_base.xml @@ -327,4 +327,13 @@ @color/woo_red_70 @color/woo_gray_6 @color/woo_gray_80 + + + + @color/woo_green_50 + @color/woo_green_0 + @color/woo_red_50 + @color/woo_red_5 From d10fbb3e840f05bb27c24efaacbf30c466b3cc04 Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 5 Mar 2025 16:32:01 -0600 Subject: [PATCH 15/18] refactor -> use string resources --- .../wooshippinglabels/ShipmentDetails.kt | 2 +- .../address/GetAddressNotification.kt | 31 +++++-------------- 2 files changed, 9 insertions(+), 24 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 9b42ad4a6e7..a53e3e2a2cd 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 @@ -628,7 +628,7 @@ private fun ShippingAddressNotification( modifier = Modifier.padding(end = 8.dp) ) Text( - text = addressNotification.message, + text = stringResource(addressNotification.message), color = color, modifier = Modifier.weight(1f) ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt index 46911815de4..11331a25f01 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt @@ -1,16 +1,11 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.address +import androidx.annotation.StringRes import com.woocommerce.android.R import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses -import com.woocommerce.android.viewmodel.ResourceProvider -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview import javax.inject.Inject -class GetAddressNotification @Inject constructor( - private val resourceProvider: ResourceProvider -) { - @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) +class GetAddressNotification @Inject constructor() { operator fun invoke( addresses: WooShippingAddresses, previousNotification: AddressNotification? = null @@ -19,9 +14,7 @@ class GetAddressNotification @Inject constructor( addresses.shipFrom.isVerified.not() -> { AddressNotification( isSuccess = false, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_origin_unverified - ), + message = R.string.woo_shipping_address_notification_origin_unverified, isDestinationNotification = false ) } @@ -29,9 +22,7 @@ class GetAddressNotification @Inject constructor( addresses.shipTo.address.hasInfo().not() -> { AddressNotification( isSuccess = false, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_destination_missing - ), + message = R.string.woo_shipping_address_notification_destination_missing, isDestinationNotification = true ) } @@ -39,9 +30,7 @@ class GetAddressNotification @Inject constructor( addresses.shipTo.isVerified.not() -> { AddressNotification( isSuccess = false, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_destination_unverified - ), + message = R.string.woo_shipping_address_notification_destination_unverified, isDestinationNotification = true ) } @@ -50,9 +39,7 @@ class GetAddressNotification @Inject constructor( previousNotification?.let { it.isSuccess.not() && it.isDestinationNotification } == true -> { AddressNotification( isSuccess = true, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_destination_verified - ), + message = R.string.woo_shipping_address_notification_destination_verified, expireAfter = 2_000, isDestinationNotification = true ) @@ -62,9 +49,7 @@ class GetAddressNotification @Inject constructor( previousNotification?.let { it.isSuccess.not() && it.isDestinationNotification.not() } == true -> { AddressNotification( isSuccess = true, - message = resourceProvider.getString( - R.string.woo_shipping_address_notification_origin_verified - ), + message = R.string.woo_shipping_address_notification_origin_verified, expireAfter = 2_000, isDestinationNotification = false ) @@ -77,7 +62,7 @@ class GetAddressNotification @Inject constructor( data class AddressNotification( val isSuccess: Boolean, - val message: String, + @StringRes val message: Int, val expireAfter: Long? = null, private val timestamp: Long = System.currentTimeMillis(), val isDestinationNotification: Boolean = false, From 706e4d45647b4f9210e14edd1e951fc3b9ae0ba6 Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 5 Mar 2025 16:34:14 -0600 Subject: [PATCH 16/18] add const for expire after --- .../wooshippinglabels/address/GetAddressNotification.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt index 11331a25f01..b7a0c492032 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotification.kt @@ -40,7 +40,7 @@ class GetAddressNotification @Inject constructor() { AddressNotification( isSuccess = true, message = R.string.woo_shipping_address_notification_destination_verified, - expireAfter = 2_000, + expireAfter = SUCCESS_EXPIRE_TIME, isDestinationNotification = true ) } @@ -50,7 +50,7 @@ class GetAddressNotification @Inject constructor() { AddressNotification( isSuccess = true, message = R.string.woo_shipping_address_notification_origin_verified, - expireAfter = 2_000, + expireAfter = SUCCESS_EXPIRE_TIME, isDestinationNotification = false ) } @@ -58,6 +58,11 @@ class GetAddressNotification @Inject constructor() { else -> null } } + + companion object { + private const val SUCCESS_EXPIRE_TIME = 2_000L + } + } data class AddressNotification( From ee92eb6dc6037b18dd67c251130ae114a07667a6 Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 5 Mar 2025 17:03:40 -0600 Subject: [PATCH 17/18] refactor dismissAddressNotification -> onDismissAddressNotification --- .../ui/orders/wooshippinglabels/ShipmentDetails.kt | 4 ++-- .../WooShippingLabelCreationScreen.kt | 10 +++++----- .../WooShippingLabelCreationViewModel.kt | 2 +- .../WooShippingLabelCreationViewModelTest.kt | 2 +- 4 files changed, 9 insertions(+), 9 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 f5aa4dee31c..09303755ccb 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 @@ -88,7 +88,7 @@ fun ShipmentDetails( destinationStatus: AddressStatus, markOrderComplete: Boolean = false, onMarkOrderCompleteChange: (Boolean) -> Unit = {}, - dismissAddressNotification: () -> Unit = {}, + onDismissAddressNotification: () -> Unit = {}, handlerModifier: Modifier = Modifier, isReadOnly: Boolean = false ) { @@ -128,7 +128,7 @@ fun ShipmentDetails( ShippingAddressNotification( addressNotification = addressNotification, - onDismiss = dismissAddressNotification, + onDismiss = onDismissAddressNotification, onAction = { addressNotification?.let { when { 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 0a9eae597b1..6047d70754a 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 @@ -115,7 +115,7 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) onEditCustomsClick = viewModel::onEditCustomsClick, onEditDestinationAddress = viewModel::onEditDestinationAddress, destinationStatus = viewState.destinationStatus, - dismissAddressNotification = viewModel::dismissAddressNotification + onDismissAddressNotification = viewModel::onDismissAddressNotification ) } @@ -155,7 +155,7 @@ fun WooShippingLabelCreationScreen( onEditCustomsClick: () -> Unit, onNavigateBack: () -> Unit, onEditDestinationAddress: (DestinationShippingAddress) -> Unit, - dismissAddressNotification: () -> Unit = {}, + onDismissAddressNotification: () -> Unit = {}, destinationStatus: AddressStatus, modifier: Modifier = Modifier ) { @@ -219,7 +219,7 @@ fun WooShippingLabelCreationScreen( onShipmentDetailsExpandedChange = onShipmentDetailsExpandedChange, onEditCustomsClick = onEditCustomsClick, onEditDestinationAddress = onEditDestinationAddress, - dismissAddressNotification = dismissAddressNotification, + onDismissAddressNotification = onDismissAddressNotification, destinationStatus = destinationStatus ) val isDarkTheme = isSystemInDarkTheme() @@ -296,7 +296,7 @@ private fun LabelCreationScreenWithBottomSheet( onEditCustomsClick: () -> Unit, onEditDestinationAddress: (DestinationShippingAddress) -> Unit, destinationStatus: AddressStatus, - dismissAddressNotification: () -> Unit = {}, + onDismissAddressNotification: () -> Unit = {}, modifier: Modifier = Modifier ) { val isPurchaseButtonDisplayed = shippingRatesState is WooShippingLabelCreationViewModel.ShippingRatesState.DataState @@ -337,7 +337,7 @@ private fun LabelCreationScreenWithBottomSheet( onEditDestinationAddress = onEditDestinationAddress, destinationStatus = destinationStatus, addressNotification = uiState.addressNotification, - dismissAddressNotification = dismissAddressNotification, + onDismissAddressNotification = onDismissAddressNotification, onEditOriginAddress = onEditOriginAddress ) } 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 f9c77516e6b..25c05b24d0c 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 @@ -161,7 +161,7 @@ class WooShippingLabelCreationViewModel @Inject constructor( } } - fun dismissAddressNotification() { + fun onDismissAddressNotification() { uiState.update { it.copy(addressNotification = null) } } 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 c29c83e6dad..e65e7aeb4b2 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 @@ -909,7 +909,7 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { var dataState = currentViewState as DataState assertThat(dataState.uiState.addressNotification).isEqualTo(notification) - sut.dismissAddressNotification() + sut.onDismissAddressNotification() currentViewState = sut.viewState.value assertThat(currentViewState).isInstanceOf(DataState::class.java) From 04a6dbcf4797dbeeb7a6f5a8af56a365a0817ed6 Mon Sep 17 00:00:00 2001 From: Alejo Date: Mon, 10 Mar 2025 11:08:28 -0600 Subject: [PATCH 18/18] fix unit tests --- .../WooShippingLabelCreationViewModelTest.kt | 5 ++-- .../address/GetAddressNotificationTests.kt | 26 +++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) 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 e65e7aeb4b2..13d77a9a0ae 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 @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.orders.wooshippinglabels import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import com.woocommerce.android.R import com.woocommerce.android.model.Address import com.woocommerce.android.model.Order import com.woocommerce.android.ui.orders.OrderTestUtils @@ -865,7 +866,7 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { val order = OrderTestUtils.generateTestOrder(orderId = orderId) val notification = AddressNotification( isSuccess = false, - message = "Origin warning", + message = R.string.woo_shipping_address_notification_destination_missing, isDestinationNotification = false ) @@ -890,7 +891,7 @@ class WooShippingLabelCreationViewModelTest : BaseUnitTest() { val order = OrderTestUtils.generateTestOrder(orderId = orderId) val notification = AddressNotification( isSuccess = false, - message = "Origin warning", + message = R.string.woo_shipping_address_notification_destination_missing, isDestinationNotification = false ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt index 62283ae072f..e8e83d02a05 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/GetAddressNotificationTests.kt @@ -6,21 +6,13 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.models.DestinationShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.viewmodel.BaseUnitTest -import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat -import org.mockito.kotlin.any -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.mock import kotlin.test.Test @OptIn(ExperimentalCoroutinesApi::class) class GetAddressNotificationTests : BaseUnitTest() { - - private val resourceProvider: ResourceProvider = mock { - on { getString(any()) } doAnswer { invocationOnMock -> invocationOnMock.arguments[0].toString() } - } - private val sut = GetAddressNotification(resourceProvider) + private val sut = GetAddressNotification() private val defaultAddresses = WooShippingAddresses( shipFrom = OriginShippingAddress.EMPTY.copy( @@ -51,7 +43,7 @@ class GetAddressNotificationTests : BaseUnitTest() { fun `when addresses as no issues and previous was a destination warning, then display destination success`() { val previous = AddressNotification( isSuccess = false, - message = "Destination warning", + message = R.string.woo_shipping_address_notification_destination_missing, isDestinationNotification = true ) val result = sut.invoke(defaultAddresses, previous) @@ -64,7 +56,7 @@ class GetAddressNotificationTests : BaseUnitTest() { fun `when addresses as no issues and previous was a origin warning, then display origin success`() { val previous = AddressNotification( isSuccess = false, - message = "Origin warning", + message = R.string.woo_shipping_address_notification_destination_missing, isDestinationNotification = false ) @@ -85,7 +77,7 @@ class GetAddressNotificationTests : BaseUnitTest() { assertThat(result!!.isSuccess).isFalse assertThat(result.isDestinationNotification).isTrue assertThat(result.message).isEqualTo( - R.string.woo_shipping_address_notification_destination_unverified.toString() + R.string.woo_shipping_address_notification_destination_unverified ) } @@ -99,7 +91,7 @@ class GetAddressNotificationTests : BaseUnitTest() { assertThat(result!!.isSuccess).isFalse assertThat(result.isDestinationNotification).isFalse assertThat(result.message).isEqualTo( - R.string.woo_shipping_address_notification_origin_unverified.toString() + R.string.woo_shipping_address_notification_origin_unverified ) } @@ -113,7 +105,7 @@ class GetAddressNotificationTests : BaseUnitTest() { assertThat(result!!.isSuccess).isFalse assertThat(result.isDestinationNotification).isTrue assertThat(result.message).isEqualTo( - R.string.woo_shipping_address_notification_destination_missing.toString() + R.string.woo_shipping_address_notification_destination_missing ) } @@ -132,7 +124,7 @@ class GetAddressNotificationTests : BaseUnitTest() { assertThat(result!!.isSuccess).isFalse assertThat(result.isDestinationNotification).isFalse assertThat(result.message).isEqualTo( - R.string.woo_shipping_address_notification_origin_unverified.toString() + R.string.woo_shipping_address_notification_origin_unverified ) // Fix origin issue @@ -146,7 +138,7 @@ class GetAddressNotificationTests : BaseUnitTest() { assertThat(result!!.isSuccess).isFalse assertThat(result.isDestinationNotification).isTrue assertThat(result.message).isEqualTo( - R.string.woo_shipping_address_notification_destination_unverified.toString() + R.string.woo_shipping_address_notification_destination_unverified ) // Fix destination issue @@ -157,7 +149,7 @@ class GetAddressNotificationTests : BaseUnitTest() { assertThat(result!!.isSuccess).isTrue assertThat(result.isDestinationNotification).isTrue assertThat(result.message).isEqualTo( - R.string.woo_shipping_address_notification_destination_verified.toString() + R.string.woo_shipping_address_notification_destination_verified ) } }