diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt index 4a5a8518a36..1f5a293b553 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationFragment.kt @@ -17,6 +17,7 @@ import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.ui.main.MainActivity.Companion.BackPressListener import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.StartCustomsFormEdit +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.StartHazmatFormEdit import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.StartPackageSelection import com.woocommerce.android.ui.orders.wooshippinglabels.address.EditAddressFlow import com.woocommerce.android.ui.orders.wooshippinglabels.address.WooShippingEditAddressFragment.Companion.DESTINATION_ADDRESS_UPDATE_RESULT @@ -109,6 +110,12 @@ class WooShippingLabelCreationFragment : BaseFragment(), BackPressListener { findNavController().navigateSafely(it) } } + + is StartHazmatFormEdit -> { + WooShippingLabelCreationFragmentDirections + .actionWooShippingLabelCreationFragmentToWooShippingLabelHazmatFormFragment() + .let { findNavController().navigateSafely(it) } + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt index 0c96710e678..ba0d58a40bc 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 @@ -124,6 +124,7 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) destinationStatus = viewState.destinationStatus, actionSnackbar = viewModel.actionSnackbar, onSplitShipment = viewModel::onSplitShipment, + onHazmatNoticeClick = viewModel::onHazmatNoticeClick, ) } @@ -166,7 +167,8 @@ fun WooShippingLabelCreationScreen( destinationStatus: AddressStatus, modifier: Modifier = Modifier, actionSnackbar: ActionSnackbar? = null, - onSplitShipment: () -> Unit = {} + onSplitShipment: () -> Unit = {}, + onHazmatNoticeClick: () -> Unit = {} ) { val shipmentDetailsValue = if (uiState.isShipmentDetailsExpanded) { BottomSheetValue.Expanded @@ -230,7 +232,8 @@ fun WooShippingLabelCreationScreen( onEditDestinationAddress = onEditDestinationAddress, destinationStatus = destinationStatus, actionSnackbar = actionSnackbar, - onSplitShipment = onSplitShipment + onSplitShipment = onSplitShipment, + onHazmatNoticeClick = onHazmatNoticeClick ) val isDarkTheme = isSystemInDarkTheme() val isCollapsed = scaffoldState.bottomSheetState.isCollapsed @@ -309,7 +312,8 @@ private fun LabelCreationScreenWithBottomSheet( destinationStatus: AddressStatus, modifier: Modifier = Modifier, actionSnackbar: ActionSnackbar? = null, - onSplitShipment: () -> Unit = {} + onSplitShipment: () -> Unit = {}, + onHazmatNoticeClick: () -> Unit = {} ) { val snackbarHostState = remember { SnackbarHostState() } @@ -418,6 +422,7 @@ private fun LabelCreationScreenWithBottomSheet( onExpand = { isExpanded.value = it } ) HazmatCard( + onClick = onHazmatNoticeClick, modifier = Modifier .fillMaxWidth() .padding(start = 4.dp, end = 8.dp) 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 f7dc4eff82b..548b97eed16 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 @@ -631,6 +631,10 @@ class WooShippingLabelCreationViewModel @Inject constructor( triggerEvent(event) } + fun onHazmatNoticeClick() { + triggerEvent(StartHazmatFormEdit) + } + fun allowBackNavigation(): Boolean { val state = uiState.value return when { @@ -699,6 +703,8 @@ class WooShippingLabelCreationViewModel @Inject constructor( val customData: CustomsData? ) : Event() + data object StartHazmatFormEdit : Event() + sealed class WooShippingViewState { data object Error : WooShippingViewState() data object Loading : WooShippingViewState() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormFragment.kt new file mode 100644 index 00000000000..d7f3e30bced --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormFragment.kt @@ -0,0 +1,31 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.hazmat + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.material.Surface +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.viewModels +import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class WooShippingLabelHazmatFormFragment : BaseFragment() { + private val viewModel: WooShippingLabelHazmatFormViewModel by viewModels() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + WooThemeWithBackground { + Surface { + WooShippingLabelHazmatFormScreen(viewModel) + } + } + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormScreen.kt new file mode 100644 index 00000000000..7d534cc31f4 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormScreen.kt @@ -0,0 +1,112 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.hazmat + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.woocommerce.android.R +import com.woocommerce.android.ui.compose.component.WCColoredButton +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground + +@Composable +fun WooShippingLabelHazmatFormScreen(viewModel: WooShippingLabelHazmatFormViewModel) { + val viewState by viewModel.viewState.observeAsState() + WooShippingLabelHazmatFormScreen( + containsHazmatChecked = viewState?.containsHazmatChecked == true, + onContainsHazmatChanged = viewModel::onContainsHazmatChanged, + onSelectCategoryClick = viewModel::onSelectCategoryClick + ) +} + +@Composable +fun WooShippingLabelHazmatFormScreen( + containsHazmatChecked: Boolean, + onContainsHazmatChanged: (Boolean) -> Unit, + onSelectCategoryClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()) + ) { + Text( + text = stringResource(R.string.woo_shipping_labels_hazmat_info_title), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = colorResource(id = R.color.color_on_surface) + ) + Row( + horizontalArrangement = Arrangement.Absolute.SpaceBetween, + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + ) { + Text( + text = stringResource(R.string.woo_shipping_labels_hazmat_info_contains_hazmat), + style = MaterialTheme.typography.bodyLarge, + color = colorResource(id = R.color.color_on_surface), + modifier = modifier + .align(Alignment.CenterVertically) + .weight(1f) + ) + Checkbox( + checked = containsHazmatChecked, + onCheckedChange = onContainsHazmatChanged, + colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary, + uncheckedColor = colorResource(id = R.color.color_on_surface_disabled), + ) + ) + } + WCColoredButton( + text = stringResource(R.string.woo_shipping_labels_hazmat_info_select_category), + onClick = onSelectCategoryClick, + enabled = containsHazmatChecked, + modifier = modifier.fillMaxWidth() + ) + + HorizontalDivider(modifier = modifier.padding(vertical = 8.dp)) + + Text( + text = stringResource(R.string.woo_shipping_labels_hazmat_info_full_description), + style = MaterialTheme.typography.bodyMedium, + color = colorResource(id = R.color.color_on_surface), + ) + } +} + +@Preview +@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun WooShippingLabelHazmatFormScreenPreview() { + WooThemeWithBackground { + WooShippingLabelHazmatFormScreen( + containsHazmatChecked = false, + onContainsHazmatChanged = {}, + onSelectCategoryClick = {} + ) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormViewModel.kt new file mode 100644 index 00000000000..a47b7615dc9 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormViewModel.kt @@ -0,0 +1,42 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.hazmat + +import android.os.Parcelable +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event +import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.getStateFlow +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.update +import kotlinx.parcelize.Parcelize +import javax.inject.Inject + +@HiltViewModel +class WooShippingLabelHazmatFormViewModel @Inject constructor( + savedState: SavedStateHandle +) : ScopedViewModel(savedState) { + + private val _viewState = savedState.getStateFlow( + scope = viewModelScope, + initialValue = ViewState() + ) + val viewState = _viewState.asLiveData() + + fun onContainsHazmatChanged(containsHazmatChecked: Boolean) { + _viewState.update { + _viewState.value.copy(containsHazmatChecked = containsHazmatChecked) + } + } + + fun onSelectCategoryClick() { + triggerEvent(OnSelectCategoryClicked) + } + + @Parcelize + data class ViewState( + val containsHazmatChecked: Boolean = false + ) : Parcelable + + data object OnSelectCategoryClicked : Event() +} diff --git a/WooCommerce/src/main/res/navigation/nav_graph_orders.xml b/WooCommerce/src/main/res/navigation/nav_graph_orders.xml index 932cdbd31a2..3922ca62692 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_orders.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_orders.xml @@ -716,6 +716,9 @@ + + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 0225c3afb2c..cb94b7bbdd7 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4516,6 +4516,10 @@ Origin country Missing country Select a country + Are you shipping dangerous goods or hazardous materials? + Contains hazardous materials + Potentially hazardous material includes items such as batteries, dry ice, flammable liquids, aerosols, ammunition, fireworks, nail polish, perfume, paint, solvents, and more. Hazardous items must ship in separate packages. + Select Category Hmm, we can\'t find a WordPress.com account connected to this email address. diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormViewModelTest.kt new file mode 100644 index 00000000000..0c93ac20b33 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/hazmat/WooShippingLabelHazmatFormViewModelTest.kt @@ -0,0 +1,92 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.hazmat + +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.viewmodel.BaseUnitTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class WooShippingLabelHazmatFormViewModelTest : BaseUnitTest() { + private lateinit var viewModel: WooShippingLabelHazmatFormViewModel + + @Before + fun setup() { + viewModel = WooShippingLabelHazmatFormViewModel(SavedStateHandle()) + } + + @Test + fun `when initialized, then containsHazmatChecked is false by default`() = testBlocking { + // Given/When + var capturedViewState: WooShippingLabelHazmatFormViewModel.ViewState? = null + viewModel.viewState.observeForever { + capturedViewState = it + } + + // Then + assertThat(capturedViewState?.containsHazmatChecked).isFalse() + } + + @Test + fun `when onContainsHazmatChanged is called with true, then viewState is updated accordingly`() = testBlocking { + // Given + var capturedViewState: WooShippingLabelHazmatFormViewModel.ViewState? = null + viewModel.viewState.observeForever { + capturedViewState = it + } + + // When + viewModel.onContainsHazmatChanged(true) + + // Then + assertThat(capturedViewState?.containsHazmatChecked).isTrue() + } + + @Test + fun `when onContainsHazmatChanged is called with false, then viewState is updated accordingly`() = testBlocking { + // Given + var capturedViewState: WooShippingLabelHazmatFormViewModel.ViewState? = null + viewModel.viewState.observeForever { + capturedViewState = it + } + + // When + viewModel.onContainsHazmatChanged(false) + + // Then + assertThat(capturedViewState?.containsHazmatChecked).isFalse() + } + + @Test + fun `when onSelectCategoryClick is called, then OnSelectCategoryClicked event is triggered`() = testBlocking { + // Given + var capturedEvent: WooShippingLabelHazmatFormViewModel.OnSelectCategoryClicked? = null + viewModel.event.observeForever { + capturedEvent = it as? WooShippingLabelHazmatFormViewModel.OnSelectCategoryClicked + } + + // When + viewModel.onSelectCategoryClick() + + // Then + assertThat(capturedEvent).isInstanceOf(WooShippingLabelHazmatFormViewModel.OnSelectCategoryClicked::class.java) + } + + @Test + fun `when multiple calls to onContainsHazmatChanged, then viewState reflects latest value`() = testBlocking { + // Given + var capturedViewState: WooShippingLabelHazmatFormViewModel.ViewState? = null + viewModel.viewState.observeForever { + capturedViewState = it + } + + // When + viewModel.onContainsHazmatChanged(true) + viewModel.onContainsHazmatChanged(false) + viewModel.onContainsHazmatChanged(true) + + // Then + assertThat(capturedViewState?.containsHazmatChecked).isTrue() + } +}