Skip to content

Commit

Permalink
Merge pull request #13651 from woocommerce/issue/12440-address-notifi…
Browse files Browse the repository at this point in the history
…cations

Address notifications
  • Loading branch information
irfano authored Mar 10, 2025
2 parents 1d8ab75 + 04a6dbc commit 02d9b8e
Show file tree
Hide file tree
Showing 10 changed files with 512 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,7 +23,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
Expand All @@ -29,8 +35,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
Expand All @@ -48,6 +59,7 @@ 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.AddressStatus
Expand All @@ -56,6 +68,7 @@ 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.util.StringUtils
import kotlinx.coroutines.delay
import kotlinx.parcelize.Parcelize

@Composable
Expand All @@ -66,13 +79,16 @@ fun ShipmentDetails(
shippingLines: List<ShippingLineSummaryUI>,
shippingAddresses: WooShippingAddresses,
shippingRateSummary: ShippingRateSummaryUI?,
addressNotification: AddressNotification?,
modifier: Modifier = Modifier,
isShipmentDetailsExpanded: Boolean = false,
onShipmentDetailsExpandedChange: (Boolean) -> Boolean,
onEditDestinationAddress: (DestinationShippingAddress) -> Unit,
onEditOriginAddress: (OriginShippingAddress) -> Unit,
destinationStatus: AddressStatus,
markOrderComplete: Boolean = false,
onMarkOrderCompleteChange: (Boolean) -> Unit = {},
onDismissAddressNotification: () -> Unit = {},
handlerModifier: Modifier = Modifier,
isReadOnly: Boolean = false
) {
Expand Down Expand Up @@ -100,14 +116,38 @@ 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))
.padding(top = dimensionResource(R.dimen.minor_100) * LocalConfiguration.current.fontScale)
)

ShippingAddressNotification(
addressNotification = addressNotification,
onDismiss = onDismissAddressNotification,
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(R.dimen.major_200) * LocalConfiguration.current.fontScale
)
)
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.major_200)))
}
}
}
Expand Down Expand Up @@ -516,6 +556,106 @@ private fun ShipmentCostRow(
}
}

@Composable
private fun ShippingAddressNotification(
addressNotification: AddressNotification?,
modifier: Modifier = Modifier,
onAction: () -> Unit = {},
onDismiss: () -> Unit = {}
) {
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
)

)
) {
if (addressNotification != null && addressNotification.isExpired().not()) {
if (addressNotification.expireAfter != null) {
LaunchedEffect(addressNotification) {
delay(addressNotification.expireAfter)
onDismiss()
}
}

val color = if (addressNotification.isSuccess) {
colorResource(id = R.color.woo_shipping_label_success)
} else {
colorResource(id = R.color.woo_shipping_label_error)
}

val backgroundColor = if (addressNotification.isSuccess) {
colorResource(id = R.color.woo_shipping_label_success_surface)
} else {
colorResource(id = R.color.woo_shipping_label_error_surface)
}

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 = stringResource(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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel)
onSelectAddressExpandedChange = viewModel::onSelectAddressExpandedChange,
onEditCustomsClick = viewModel::onEditCustomsClick,
onEditDestinationAddress = viewModel::onEditDestinationAddress,
destinationStatus = viewState.destinationStatus
destinationStatus = viewState.destinationStatus,
onDismissAddressNotification = viewModel::onDismissAddressNotification
)
}

Expand Down Expand Up @@ -154,6 +155,7 @@ fun WooShippingLabelCreationScreen(
onEditCustomsClick: () -> Unit,
onNavigateBack: () -> Unit,
onEditDestinationAddress: (DestinationShippingAddress) -> Unit,
onDismissAddressNotification: () -> Unit = {},
destinationStatus: AddressStatus,
modifier: Modifier = Modifier
) {
Expand Down Expand Up @@ -217,6 +219,7 @@ fun WooShippingLabelCreationScreen(
onShipmentDetailsExpandedChange = onShipmentDetailsExpandedChange,
onEditCustomsClick = onEditCustomsClick,
onEditDestinationAddress = onEditDestinationAddress,
onDismissAddressNotification = onDismissAddressNotification,
destinationStatus = destinationStatus
)
val isDarkTheme = isSystemInDarkTheme()
Expand Down Expand Up @@ -293,11 +296,20 @@ private fun LabelCreationScreenWithBottomSheet(
onEditCustomsClick: () -> Unit,
onEditDestinationAddress: (DestinationShippingAddress) -> Unit,
destinationStatus: AddressStatus,
onDismissAddressNotification: () -> 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 -> 128.dp
else -> 72.dp
} * LocalConfiguration.current.fontScale

val paddingBottom = when {
isPurchaseButtonDisplayed -> 72.dp
else -> 0.dp
}
val shippingRateSummary =
(shippingRatesState as? WooShippingLabelCreationViewModel.ShippingRatesState.DataState)?.selectedRate?.summary

Expand All @@ -323,7 +335,10 @@ private fun LabelCreationScreenWithBottomSheet(
markOrderComplete = uiState.markOrderComplete,
onShipmentDetailsExpandedChange = onShipmentDetailsExpandedChange,
onEditDestinationAddress = onEditDestinationAddress,
destinationStatus = destinationStatus
destinationStatus = destinationStatus,
addressNotification = uiState.addressNotification,
onDismissAddressNotification = onDismissAddressNotification,
onEditOriginAddress = onEditOriginAddress
)
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ 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.AddressStatus
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
Expand All @@ -41,7 +43,9 @@ 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
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
Expand All @@ -53,6 +57,7 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
Expand All @@ -73,7 +78,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 getAddressNotification: GetAddressNotification
) : ScopedViewModel(savedState) {
private val navArgs: WooShippingLabelCreationFragmentArgs by savedState.navArgs()

Expand Down Expand Up @@ -131,6 +137,7 @@ class WooShippingLabelCreationViewModel @Inject constructor(
launch { observeShippingRates() }
launch { observeShippingRatesState() }
launch { observeCustomsDataChanges() }
launch { observeNotifications() }
}

private suspend fun getOrderInformation() {
Expand All @@ -142,6 +149,22 @@ class WooShippingLabelCreationViewModel @Inject constructor(
}
}

@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
private suspend fun observeNotifications() {
shippingAddresses.filterNotNull()
.onStart { delay(NOTIFICATIONS_DELAY) }
.runningFold(initial = null as AddressNotification?) { previousNotification, addresses ->
getAddressNotification(addresses, previousNotification)
}
.collectLatest { notification ->
uiState.update { it.copy(addressNotification = notification) }
}
}

fun onDismissAddressNotification() {
uiState.update { it.copy(addressNotification = null) }
}

private suspend fun getStoreOptions() {
observeStoreOptions().collectLatest { options ->
storeOptions.value = options
Expand Down Expand Up @@ -670,7 +693,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(
Expand All @@ -690,6 +714,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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -573,8 +572,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) {
Expand Down
Loading

0 comments on commit 02d9b8e

Please sign in to comment.