Skip to content
New issue

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

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

Already on GitHub? # to your account

[Shipping Labels Revamp] Introduce hazmat initial navigation and screen #13784

Merged
merged 23 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b846032
Add Hazmat notice click event UI wiring
ThomazFB Mar 17, 2025
35232fb
Define Hazmat Form click event and call function
ThomazFB Mar 17, 2025
0933a9f
Declare WooShippingLabelHazmatFormFragment with compose scaffold
ThomazFB Mar 17, 2025
502efb0
Define navigation route between the Label Creation fragment and Hazma…
ThomazFB Mar 17, 2025
4dec8f1
Define WooShippingLabelHazmatFormScreen.kt
ThomazFB Mar 17, 2025
f6d7edc
Define WooShippingLabelHazmatFormScreen main UI elements and behaviors
ThomazFB Mar 17, 2025
95f80b1
Define WooShippingLabelHazmatFormViewModel with initial ViewState
ThomazFB Mar 17, 2025
fab3fcd
Add ViewModel main interface with the Composable UI
ThomazFB Mar 17, 2025
047a928
Wire the Hazmat full call path between Fragment, Composable and ViewM…
ThomazFB Mar 17, 2025
97738e4
Add proper event trigger for the onSelectCategoryClick function call
ThomazFB Mar 17, 2025
1662e7e
Fix missing injection into WooShippingLabelHazmatFormViewModel
ThomazFB Mar 17, 2025
e812c34
Introduce WooShippingLabelHazmatFormViewModelTest coverage
ThomazFB Mar 17, 2025
d3f1866
Fix lint issues
ThomazFB Mar 17, 2025
0a0373e
Add Preview to WooShippingLabelHazmatFormScreen
ThomazFB Mar 18, 2025
9b6bfe2
Declare and assign proper string resources to the WooShippingLabelHaz…
ThomazFB Mar 18, 2025
2770463
Add proper spacing to the WooShippingLabelHazmatFormScreen
ThomazFB Mar 18, 2025
1c7780a
Add missing Hazmat click event wiring
ThomazFB Mar 18, 2025
cb73765
Move away from Material3 surface for now
ThomazFB Mar 18, 2025
2d3ecf6
Add Dark mode support and vertical scrolling to WooShippingLabelHazma…
ThomazFB Mar 24, 2025
385d7d3
Remove redundant isNotNull check from WooShippingLabelHazmatFormViewM…
ThomazFB Mar 24, 2025
0344978
Update onSurface color for WooShippingLabelHazmatFormScreen texts
ThomazFB Mar 24, 2025
02d4beb
Merge branch 'trunk' into issue/introduce-hazmat-initial-navigation
ThomazFB Mar 24, 2025
e4e5012
Fix WooShippingLabelHazmatFormScreen checkbox color
ThomazFB Mar 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -109,6 +110,12 @@ class WooShippingLabelCreationFragment : BaseFragment(), BackPressListener {
findNavController().navigateSafely(it)
}
}

is StartHazmatFormEdit -> {
WooShippingLabelCreationFragmentDirections
.actionWooShippingLabelCreationFragmentToWooShippingLabelHazmatFormFragment()
.let { findNavController().navigateSafely(it) }
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel)
destinationStatus = viewState.destinationStatus,
actionSnackbar = viewModel.actionSnackbar,
onSplitShipment = viewModel::onSplitShipment,
onHazmatNoticeClick = viewModel::onHazmatNoticeClick,
)
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() }

Expand Down Expand Up @@ -418,6 +422,7 @@ private fun LabelCreationScreenWithBottomSheet(
onExpand = { isExpanded.value = it }
)
HazmatCard(
onClick = onHazmatNoticeClick,
modifier = Modifier
.fillMaxWidth()
.padding(start = 4.dp, end = 8.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,10 @@ class WooShippingLabelCreationViewModel @Inject constructor(
triggerEvent(event)
}

fun onHazmatNoticeClick() {
triggerEvent(StartHazmatFormEdit)
}

fun allowBackNavigation(): Boolean {
val state = uiState.value
return when {
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Check warning

Code scanning / Android Lint

material and material3 are separate, incompatible design system libraries Warning

Using a material import while also using the material3 library
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)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
7 changes: 7 additions & 0 deletions WooCommerce/src/main/res/navigation/nav_graph_orders.xml
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,9 @@
<action
android:id="@+id/action_wooShippingLabelCreationFragment_to_wooShippingSplitShipmentFragment"
app:destination="@id/wooShippingSplitShipmentFragment" />
<action
android:id="@+id/action_wooShippingLabelCreationFragment_to_wooShippingLabelHazmatFormFragment"
app:destination="@id/wooShippingLabelHazmatFormFragment" />
</fragment>
<fragment
android:id="@+id/wooShippingLabelPackageCreationFragment"
Expand Down Expand Up @@ -779,4 +782,8 @@
android:id="@+id/wooShippingSplitShipmentFragment"
android:name="com.woocommerce.android.ui.orders.wooshippinglabels.split.WooShippingSplitShipmentFragment"
android:label="WooShippingSplitShipmentFragment" />
<fragment
android:id="@+id/wooShippingLabelHazmatFormFragment"
android:name="com.woocommerce.android.ui.orders.wooshippinglabels.hazmat.WooShippingLabelHazmatFormFragment"
android:label="WooShippingLabelHazmatFormFragment" />
</navigation>
4 changes: 4 additions & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4516,6 +4516,10 @@
<string name="woo_shipping_labels_customs_product_details_origin_country">Origin country</string>
<string name="woo_shipping_labels_customs_product_details_origin_country_missing">Missing country</string>
<string name="woo_shipping_labels_customs_product_details_origin_country_selection">Select a country</string>
<string name="woo_shipping_labels_hazmat_info_title">Are you shipping dangerous goods or hazardous materials?</string>
<string name="woo_shipping_labels_hazmat_info_contains_hazmat">Contains hazardous materials</string>
<string name="woo_shipping_labels_hazmat_info_full_description">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.</string>
<string name="woo_shipping_labels_hazmat_info_select_category">Select Category</string>


<string name="email_not_registered_wpcom">Hmm, we can\'t find a WordPress.com account connected to this email address.</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}