diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationScreen.kt index 82808a44459..480e0e7f436 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationScreen.kt @@ -25,9 +25,13 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingL import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PageType.CARRIER import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PageType.CUSTOM import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PageType.SAVED +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.Carrier +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageGroup import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.WooShippingCarrierPackageContent import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.WooShippingCarrierPackageScreen import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.WooShippingCustomPackageCreationScreen +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.WooShippingSavedPackageContent import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.WooShippingSavedPackageScreen @Composable @@ -128,7 +132,7 @@ fun WooShippingLabelsPackageCreationScreenPreview() { ) }, createSavedPackageScreen = { - WooShippingSavedPackageScreen( + WooShippingSavedPackageContent( savedPackages = listOf( PackageData( name = "Small Flat Rate Box", @@ -157,7 +161,74 @@ fun WooShippingLabelsPackageCreationScreenPreview() { onSavedPackageSelected = { _, _ -> } ) }, - createCarrierPackageScreen = { } + createCarrierPackageScreen = { + WooShippingCarrierPackageContent( + carrierPackages = mapOf( + Carrier.DHL to listOf( + CarrierPackageGroup( + groupName = "Group 1", + packages = listOf( + PackageData( + name = "Package 1 - Carrier 1", + dimensions = "10 x 10 x 10", + weight = "10", + isSelected = false, + isLetter = false + ), + PackageData( + name = "Package 2 - Carrier 1", + dimensions = "20 x 20 x 20", + weight = "20", + isSelected = false, + isLetter = false + ) + ) + ), + CarrierPackageGroup( + groupName = "Group 2", + packages = listOf( + PackageData( + name = "Package 3 - Carrier 1", + dimensions = "30 x 30 x 30", + weight = "30", + isSelected = false, + isLetter = false + ), + PackageData( + name = "Package 4 - Carrier 1", + dimensions = "40 x 40 x 40", + weight = "40", + isSelected = false, + isLetter = false + ) + ) + ) + ), + Carrier.USPS to listOf( + CarrierPackageGroup( + groupName = "Group 2", + packages = listOf( + PackageData( + name = "Package 1 - Carrier 2", + dimensions = "10 x 10 x 10", + weight = "10", + isSelected = false, + isLetter = false + ), + PackageData( + name = "Package 2 Carrier - 2", + dimensions = "20 x 20 x 20", + weight = "20", + isSelected = false, + isLetter = false + ) + ) + ) + ) + ), + onPackageSelected = { _, _ -> } + ) + } ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationViewModel.kt index 1ec4b77b0b3..158ae224dab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationViewModel.kt @@ -11,10 +11,8 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource.W import com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking.CustomPackageCreationRequestData import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.Carrier import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageGroup -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageSelection import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CustomPackageCreationData import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.SavedPackageSelection import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel @@ -59,12 +57,9 @@ class WooShippingLabelPackageCreationViewModel @Inject constructor( init { launch { - fetchPredefinedPackages()?.let { + fetchPredefinedPackages().let { response -> _viewState.update { viewState -> - viewState.copy( - savedPackageSelection = it.savedPackageSelection, - carrierPackageSection = it.carrierPackageSelection - ) + viewState.copy(predefinedPackagesState = response) } } } @@ -72,34 +67,40 @@ class WooShippingLabelPackageCreationViewModel @Inject constructor( fun onCarrierPackageSelected(selectedPackage: PackageData, isSelected: Boolean) { _viewState.update { viewState -> - viewState.carrierPackageSection.carrierPackages - .map { updateCarrierPackagesSelection(it, selectedPackage, isSelected) } - .let { viewState.copy(carrierPackageSection = CarrierPackageSelection(it.toMap())) } + val predefinedPackages = viewState.predefinedPackagesData + predefinedPackages?.carrierPackages + ?.map { updateCarrierPackagesSelection(it, selectedPackage, isSelected) } + ?.let { + viewState.copy(predefinedPackagesState = predefinedPackages.copy(carrierPackages = it.toMap())) + } ?: _viewState.value } } fun onSavedPackageSelected(selectedPackage: PackageData, isSelected: Boolean) { _viewState.update { viewState -> - viewState.savedPackageSelection.packages - .map { it.copy(isSelected = false) } - .toMutableList() - .safelyUpdate(selectedPackage, selectedPackage.copy(isSelected = isSelected)) - .let { SavedPackageSelection(it) } - .let { viewState.copy(savedPackageSelection = it) } + val predefinedPackages = viewState.predefinedPackagesData + predefinedPackages?.savedPackages + ?.map { it.copy(isSelected = false) } + ?.toMutableList() + ?.safelyUpdate(selectedPackage, selectedPackage.copy(isSelected = isSelected)) + ?.let { viewState.copy(predefinedPackagesState = predefinedPackages.copy(savedPackages = it)) } + ?: _viewState.value } } fun onAddCarrierPackageClick() { - _viewState.value.carrierPackageSection.carrierPackages - .asSequence() - .flatMap { it.value } - .flatMap { it.packages } - .find { it.isSelected } + _viewState.value.predefinedPackagesData?.carrierPackages + ?.asSequence() + ?.flatMap { it.value } + ?.flatMap { it.packages } + ?.find { it.isSelected } ?.let { triggerEvent(PackageSelected(it)) } } fun onAddSavedPackageClick() { - _viewState.value.savedPackageSelection.packages.find { it.isSelected } + _viewState.value.predefinedPackagesData + ?.savedPackages + ?.find { it.isSelected } ?.let { triggerEvent(PackageSelected(it)) } } @@ -209,9 +210,29 @@ class WooShippingLabelPackageCreationViewModel @Inject constructor( data class ViewState( val pageTabs: List = emptyList(), val customPackageCreationData: CustomPackageCreationData = CustomPackageCreationData.EMPTY, - val savedPackageSelection: SavedPackageSelection = SavedPackageSelection(emptyList()), - val carrierPackageSection: CarrierPackageSelection = CarrierPackageSelection(emptyMap()) - ) : Parcelable + val predefinedPackagesState: PredefinedPackagesState = PredefinedPackagesState.Waiting, + ) : Parcelable { + val predefinedPackagesData + get() = (predefinedPackagesState as? PredefinedPackagesState.Data) + } + + @Parcelize + sealed class PredefinedPackagesState : Parcelable { + data object Error : PredefinedPackagesState() + data object Waiting : PredefinedPackagesState() + data class Data( + val savedPackages: List = emptyList(), + val carrierPackages: Map> = emptyMap() + ) : PredefinedPackagesState() { + val hasCarrierSelection: Boolean + get() = carrierPackages.values.flatten().find { group -> + group.packages.find { it.isSelected } != null + } != null + + val hasSavedSelection: Boolean + get() = savedPackages.find { it.isSelected } != null + } + } @Parcelize data class PageTab( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/components/WooShippingPackageListItem.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/components/PackageItemComponents.kt similarity index 53% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/components/WooShippingPackageListItem.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/components/PackageItemComponents.kt index c6ff4c75a4a..4eda4257339 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/components/WooShippingPackageListItem.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/components/PackageItemComponents.kt @@ -4,7 +4,10 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -13,13 +16,16 @@ 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.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.woocommerce.android.R +import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.component.SelectionCheck +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData @Composable -fun WooSavedPackageListItem( +fun WooShippingPackageListItem( modifier: Modifier, packageData: PackageData, onPackageSelected: (PackageData, Boolean) -> Unit @@ -60,3 +66,68 @@ fun WooSavedPackageListItem( Divider() } } + +@Composable +fun WooShippingPackageListItemSkeleton( + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .padding(top = 8.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + SkeletonView( + modifier = Modifier.size(24.dp) + ) + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + SkeletonView( + modifier = Modifier + .height(16.dp) + .fillMaxWidth(0.5f) + ) + SkeletonView( + modifier = Modifier + .height(20.dp) + .fillMaxWidth(0.7f) + ) + SkeletonView( + modifier = Modifier + .height(16.dp) + .fillMaxWidth(0.6f) + ) + } + } + Divider() + } +} + +@Preview +@Composable +fun WooSavedPackageListItemPreview() { + WooThemeWithBackground { + WooShippingPackageListItem( + modifier = Modifier, + packageData = PackageData( + name = "Small Flat Rate Box", + dimensions = "5 x 5 x 5", + weight = "1.5", + isLetter = false, + isSelected = false + ), + onPackageSelected = { _, _ -> } + ) + } +} + +@Preview +@Composable +fun WooSavedPackageListItemSkeletonPreview() { + WooThemeWithBackground { + WooShippingPackageListItemSkeleton() + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt index d61b44ef4cd..25927bf1ddf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStore.kt @@ -1,32 +1,26 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PredefinedPackagesState import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.Carrier import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageGroup -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageSelection import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.SavedPackageSelection -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.StorePredefinedPackages import javax.inject.Inject class FetchPredefinedPackagesFromStore @Inject constructor( private val selectedSite: SelectedSite, private val packageRepository: WooShippingLabelPackageRepository ) { - suspend operator fun invoke(): StorePredefinedPackages? { + suspend operator fun invoke(): PredefinedPackagesState { val storePackages = selectedSite.getOrNull() ?.let { packageRepository.fetchAllStorePackages(it) } ?.takeIf { it.isError.not() } ?.model - ?: return null + ?: return PredefinedPackagesState.Error - return StorePredefinedPackages( - savedPackageSelection = storePackages - .filterSavedData() - .let { SavedPackageSelection(it) }, - carrierPackageSelection = storePackages - .filterCarrierData() - .let { CarrierPackageSelection(it) } + return PredefinedPackagesState.Data( + savedPackages = storePackages.filterSavedData(), + carrierPackages = storePackages.filterCarrierData() ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt index 2e41ba3c487..3975be27171 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/UIModels.kt @@ -4,6 +4,7 @@ import android.os.Parcelable import com.woocommerce.android.R import com.woocommerce.android.extensions.isNotNullOrEmpty import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PackageType +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize @@ -16,6 +17,22 @@ data class PackageData( val dimensionUnit: String = "cm", val weightUnit: String = "kg" ) : Parcelable { + @IgnoredOnParcel + val length: String + + @IgnoredOnParcel + val width: String + + @IgnoredOnParcel + val height: String + + init { + val dimensionList = dimensions.split("x") + length = dimensionList.getOrNull(0).orEmpty().trim() + width = dimensionList.getOrNull(1).orEmpty().trim() + height = dimensionList.getOrNull(2).orEmpty().trim() + } + val descriptionResId: Int get() = when (isLetter) { true -> R.string.woo_shipping_labels_package_creation_envelope_type @@ -99,24 +116,6 @@ sealed class Carrier( @Parcelize data class StorePredefinedPackages( - val carrierPackageSelection: CarrierPackageSelection, - val savedPackageSelection: SavedPackageSelection + val carrierPackages: Map>, + val savedPackages: List ) : Parcelable - -@Parcelize -data class CarrierPackageSelection( - val carrierPackages: Map> -) : Parcelable { - val hasSelection: Boolean - get() = carrierPackages.values.flatten().find { group -> - group.packages.find { it.isSelected } != null - } != null -} - -@Parcelize -data class SavedPackageSelection( - val packages: List -) : Parcelable { - val hasSelection: Boolean - get() = packages.find { it.isSelected } != null -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt index 8aa8d6ae64f..d39f544cbbf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingCarrierPackageScreen.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui import androidx.annotation.DrawableRes -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -15,7 +14,9 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.Divider import androidx.compose.material.Icon @@ -40,23 +41,70 @@ import androidx.compose.ui.unit.dp import com.woocommerce.android.R import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.components.WooSavedPackageListItem +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PredefinedPackagesState +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.components.WooShippingPackageListItem +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.components.WooShippingPackageListItemSkeleton import kotlinx.coroutines.launch @Composable fun WooShippingCarrierPackageScreen(viewModel: WooShippingLabelPackageCreationViewModel) { val viewState by viewModel.viewState.observeAsState() WooShippingCarrierPackageScreen( - carrierPackages = viewState?.carrierPackageSection?.carrierPackages ?: emptyMap(), - isAddPackageEnabled = viewState?.carrierPackageSection?.hasSelection ?: false, + packageState = viewState?.predefinedPackagesState ?: PredefinedPackagesState.Waiting, + isAddPackageEnabled = viewState?.predefinedPackagesData?.hasCarrierSelection ?: false, onPackageSelected = viewModel::onCarrierPackageSelected, onAddPackageClick = viewModel::onAddCarrierPackageClick ) } -@OptIn(ExperimentalFoundationApi::class) @Composable fun WooShippingCarrierPackageScreen( + modifier: Modifier = Modifier, + packageState: PredefinedPackagesState, + onPackageSelected: (PackageData, Boolean) -> Unit, + isAddPackageEnabled: Boolean = false, + onAddPackageClick: () -> Unit = {} +) { + when (packageState) { + is PredefinedPackagesState.Data -> { + WooShippingCarrierPackageContent( + modifier = modifier, + carrierPackages = packageState.carrierPackages, + onPackageSelected = onPackageSelected, + isAddPackageEnabled = isAddPackageEnabled, + onAddPackageClick = onAddPackageClick + ) + } + + is PredefinedPackagesState.Error -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()) + ) { + Text(text = stringResource(id = R.string.woo_shipping_labels_package_creation_error)) + } + } + + is PredefinedPackagesState.Waiting -> { + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()) + ) { + WooShippingPackageListItemSkeleton() + WooShippingPackageListItemSkeleton() + WooShippingPackageListItemSkeleton() + } + } + } +} + +@Composable +fun WooShippingCarrierPackageContent( modifier: Modifier = Modifier, carrierPackages: Map>, onPackageSelected: (PackageData, Boolean) -> Unit, @@ -94,7 +142,6 @@ fun WooShippingCarrierPackageScreen( } } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun CarrierTabRow( modifier: Modifier, @@ -137,7 +184,6 @@ private fun CarrierTabRow( } } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun PackageListPager( modifier: Modifier, @@ -195,7 +241,7 @@ private fun PackageListSection( ) Divider() packages.forEach { packageData -> - WooSavedPackageListItem( + WooShippingPackageListItem( modifier = Modifier.padding(start = 16.dp), packageData = packageData, onPackageSelected = onPackageSelected @@ -223,7 +269,7 @@ private fun CarrierLogo( @Composable fun WooShippingCarrierPackageScreenPreview() { WooThemeWithBackground { - WooShippingCarrierPackageScreen( + WooShippingCarrierPackageContent( carrierPackages = mapOf( Carrier.DHL to listOf( CarrierPackageGroup( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingSavedPackageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingSavedPackageScreen.kt index 06a854e50ea..121a78d248d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingSavedPackageScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/WooShippingSavedPackageScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.material.Divider import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -18,14 +19,16 @@ import androidx.compose.ui.unit.dp import com.woocommerce.android.R import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.components.WooSavedPackageListItem +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PredefinedPackagesState +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.components.WooShippingPackageListItem +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.components.WooShippingPackageListItemSkeleton @Composable fun WooShippingSavedPackageScreen(viewModel: WooShippingLabelPackageCreationViewModel) { val viewState = viewModel.viewState.observeAsState() WooShippingSavedPackageScreen( - savedPackages = viewState.value?.savedPackageSelection?.packages.orEmpty(), - isAddPackageEnabled = viewState.value?.savedPackageSelection?.hasSelection ?: false, + packageState = viewState.value?.predefinedPackagesState ?: PredefinedPackagesState.Waiting, + isAddPackageEnabled = viewState.value?.predefinedPackagesData?.hasSavedSelection ?: false, onAddPackageClick = viewModel::onAddSavedPackageClick, onSavedPackageSelected = viewModel::onSavedPackageSelected @@ -34,6 +37,52 @@ fun WooShippingSavedPackageScreen(viewModel: WooShippingLabelPackageCreationView @Composable fun WooShippingSavedPackageScreen( + modifier: Modifier = Modifier, + packageState: PredefinedPackagesState, + isAddPackageEnabled: Boolean, + onAddPackageClick: () -> Unit, + onSavedPackageSelected: (PackageData, Boolean) -> Unit +) { + when (packageState) { + is PredefinedPackagesState.Data -> { + WooShippingSavedPackageContent( + modifier = modifier, + savedPackages = packageState.savedPackages, + isAddPackageEnabled = isAddPackageEnabled, + onAddPackageClick = onAddPackageClick, + onSavedPackageSelected = onSavedPackageSelected + ) + } + + is PredefinedPackagesState.Error -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()) + ) { + Text(text = stringResource(id = R.string.woo_shipping_labels_package_creation_error)) + } + } + + is PredefinedPackagesState.Waiting -> { + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()) + ) { + WooShippingPackageListItemSkeleton() + WooShippingPackageListItemSkeleton() + WooShippingPackageListItemSkeleton() + } + } + } +} + +@Composable +fun WooShippingSavedPackageContent( modifier: Modifier = Modifier, savedPackages: List, isAddPackageEnabled: Boolean, @@ -51,7 +100,7 @@ fun WooShippingSavedPackageScreen( .verticalScroll(rememberScrollState()) ) { savedPackages.forEach { packageData -> - WooSavedPackageListItem( + WooShippingPackageListItem( modifier, packageData, onSavedPackageSelected @@ -77,7 +126,7 @@ fun WooShippingSavedPackageScreen( @Composable fun WooShippingSavedPackageScreenPreview() { WooThemeWithBackground { - WooShippingSavedPackageScreen( + WooShippingSavedPackageContent( savedPackages = listOf( PackageData( name = "Small Flat Rate Box", @@ -107,3 +156,29 @@ fun WooShippingSavedPackageScreenPreview() { ) } } + +@Preview +@Composable +fun WooShippingSavedPackageScreenLoadingPreview() { + WooThemeWithBackground { + WooShippingSavedPackageScreen( + packageState = PredefinedPackagesState.Waiting, + isAddPackageEnabled = false, + onAddPackageClick = {}, + onSavedPackageSelected = { _, _ -> } + ) + } +} + +@Preview +@Composable +fun WooShippingSavedPackageScreenErrorPreview() { + WooThemeWithBackground { + WooShippingSavedPackageScreen( + packageState = PredefinedPackagesState.Error, + isAddPackageEnabled = false, + onAddPackageClick = {}, + onSavedPackageSelected = { _, _ -> } + ) + } +} diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 2a39bfec8a6..8103794b8ca 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4386,6 +4386,7 @@ Package Name Save this as a new package template Add Package + Failed to load the package data Box Envelope 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/packages/WooShippingLabelPackageCreationViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationViewModelTest.kt index 84edfe96e91..89f4f2073e2 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/WooShippingLabelPackageCreationViewModelTest.kt @@ -5,21 +5,20 @@ import com.woocommerce.android.R import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PackageSelected import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PackageType +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PredefinedPackagesState import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.ShowPackageTypeDialog import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.ViewState import com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource.FetchPredefinedPackagesFromStore import com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource.WooShippingLabelPackageRepository import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.Carrier import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageGroup -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageSelection import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CustomPackageCreationData import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.SavedPackageSelection -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.StorePredefinedPackages import com.woocommerce.android.viewmodel.BaseUnitTest import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -187,11 +186,9 @@ class WooShippingLabelPackageCreationViewModelTest : BaseUnitTest() { isLetter = true ) whenever(fetchPredefinedPackages()).thenReturn( - StorePredefinedPackages( - carrierPackageSelection = CarrierPackageSelection(emptyMap()), - savedPackageSelection = SavedPackageSelection( - listOf(package1, package2) - ) + PredefinedPackagesState.Data( + carrierPackages = emptyMap(), + savedPackages = listOf(package1, package2) ) ) @@ -205,7 +202,7 @@ class WooShippingLabelPackageCreationViewModelTest : BaseUnitTest() { sut.viewState.observeForever { lastViewState = it } sut.onSavedPackageSelected(package1, true) - val selectedPackages = lastViewState?.savedPackageSelection?.packages?.filter { it.isSelected } + val selectedPackages = lastViewState?.predefinedPackagesData?.savedPackages?.filter { it.isSelected } assertThat(selectedPackages).isNotNull assertThat(selectedPackages).size().isEqualTo(1) assertThat(selectedPackages?.first()).isEqualTo(package1.copy(isSelected = true)) @@ -238,11 +235,9 @@ class WooShippingLabelPackageCreationViewModelTest : BaseUnitTest() { ) ) whenever(fetchPredefinedPackages()).thenReturn( - StorePredefinedPackages( - carrierPackageSelection = CarrierPackageSelection(carrierPackages), - savedPackageSelection = SavedPackageSelection( - emptyList() - ) + PredefinedPackagesState.Data( + carrierPackages = carrierPackages, + savedPackages = emptyList() ) ) @@ -256,9 +251,14 @@ class WooShippingLabelPackageCreationViewModelTest : BaseUnitTest() { sut.viewState.observeForever { lastViewState = it } sut.onCarrierPackageSelected(package1, true) - val selectedPackages = lastViewState?.carrierPackageSection?.carrierPackages?.values?.flatten()?.flatMap { - it.packages - }?.filter { it.isSelected } + val selectedPackages = lastViewState + ?.predefinedPackagesData + ?.carrierPackages + ?.values + ?.flatten() + ?.flatMap { it.packages } + ?.filter { it.isSelected } + assertThat(selectedPackages).isNotNull assertThat(selectedPackages).size().isEqualTo(1) assertThat(selectedPackages?.first()).isEqualTo(package1.copy(isSelected = true)) @@ -313,11 +313,9 @@ class WooShippingLabelPackageCreationViewModelTest : BaseUnitTest() { ) ) whenever(fetchPredefinedPackages()).thenReturn( - StorePredefinedPackages( - carrierPackageSelection = CarrierPackageSelection(carrierPackages), - savedPackageSelection = SavedPackageSelection( - emptyList() - ) + PredefinedPackagesState.Data( + carrierPackages = carrierPackages, + savedPackages = emptyList() ) ) @@ -328,13 +326,20 @@ class WooShippingLabelPackageCreationViewModelTest : BaseUnitTest() { fetchPredefinedPackages, packageRepository ) + sut.viewState.observeForever { lastViewState = it } sut.onCarrierPackageSelected(package1, true) sut.onCarrierPackageSelected(package2, true) + advanceUntilIdle() + + val selectedPackages = lastViewState + ?.predefinedPackagesData + ?.carrierPackages + ?.values + ?.flatten() + ?.flatMap { it.packages } + ?.filter { it.isSelected } - val selectedPackages = lastViewState?.carrierPackageSection?.carrierPackages?.values?.flatten()?.flatMap { - it.packages - }?.filter { it.isSelected } assertThat(selectedPackages).isNotNull assertThat(selectedPackages).size().isEqualTo(1) assertThat(selectedPackages?.first()).isEqualTo(package2.copy(isSelected = true)) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt index 105767adf6f..3ed7d7910ca 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/FetchPredefinedPackagesFromStoreTest.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.WooShippingLabelPackageCreationViewModel.PredefinedPackagesState import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.Carrier import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.CarrierPackageGroup import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData @@ -33,9 +34,9 @@ class FetchPredefinedPackagesFromStoreTest : BaseUnitTest() { whenever(selectedSite.getOrNull()).thenReturn(site) whenever(packageRepository.fetchAllStorePackages(site)).thenReturn(WooResult(storePackages)) - val result = fetchPredefinedPackagesFromStore()!! + val result = fetchPredefinedPackagesFromStore() as PredefinedPackagesState.Data - assertThat(result.savedPackageSelection.packages).containsExactly( + assertThat(result.savedPackages).containsExactly( PackageData( name = "Saved Package 1", dimensions = "dimensions", @@ -51,7 +52,7 @@ class FetchPredefinedPackagesFromStoreTest : BaseUnitTest() { isLetter = false ) ) - assertThat(result.carrierPackageSelection.carrierPackages[Carrier.USPS]).containsExactly( + assertThat(result.carrierPackages[Carrier.USPS]).containsExactly( CarrierPackageGroup( groupName = "Group 1", packages = listOf( @@ -68,7 +69,7 @@ class FetchPredefinedPackagesFromStoreTest : BaseUnitTest() { } @Test - fun `invoke should return null StorePredefinedPackages when fetchAllStorePackages returns error`() = testBlocking { + fun `invoke should return Error StorePredefinedPackages when fetchAllStorePackages returns error`() = testBlocking { val error = WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN) val site = SiteModel().apply { id = 1 } whenever(selectedSite.getOrNull()).thenReturn(site) @@ -76,16 +77,16 @@ class FetchPredefinedPackagesFromStoreTest : BaseUnitTest() { val result = fetchPredefinedPackagesFromStore() - assertThat(result).isNull() + assertThat(result).isEqualTo(PredefinedPackagesState.Error) } @Test - fun `invoke should return null StorePredefinedPackages when site is not available`() = testBlocking { + fun `invoke should return Error StorePredefinedPackages when site is not available`() = testBlocking { whenever(selectedSite.getOrNull()).thenReturn(null) val result = fetchPredefinedPackagesFromStore() - assertThat(result).isNull() + assertThat(result).isEqualTo(PredefinedPackagesState.Error) } private fun generatePackagesData() = StorePackagesDAO( diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/PackageDataTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/PackageDataTest.kt new file mode 100644 index 00000000000..93ae1174f65 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/ui/PackageDataTest.kt @@ -0,0 +1,67 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui + +import org.assertj.core.api.Assertions.assertThat +import kotlin.test.Test + +class PackageDataTest { + + @Test + fun `length, width, and height are correctly set when dimensions are properly formatted`() { + val packageData = PackageData( + name = "Test Package", + dimensions = "10 x 20 x 30", + weight = "5", + isSelected = false, + isLetter = false + ) + + assertThat(packageData.length).isEqualTo("10") + assertThat(packageData.width).isEqualTo("20") + assertThat(packageData.height).isEqualTo("30") + } + + @Test + fun `length, width, and height are empty when dimensions are not properly formatted`() { + val packageData = PackageData( + name = "Test Package", + dimensions = "10 x 20", + weight = "5", + isSelected = false, + isLetter = false + ) + + assertThat(packageData.length).isEqualTo("10") + assertThat(packageData.width).isEqualTo("20") + assertThat(packageData.height).isEmpty() + } + + @Test + fun `length, width, and height are empty when dimensions are null`() { + val packageData = PackageData( + name = "Test Package", + dimensions = "10", + weight = "5", + isSelected = false, + isLetter = false + ) + + assertThat(packageData.length).isEqualTo("10") + assertThat(packageData.width).isEmpty() + assertThat(packageData.height).isEmpty() + } + + @Test + fun `length, width, and height are empty when dimensions are empty`() { + val packageData = PackageData( + name = "Test Package", + dimensions = "", + weight = "5", + isSelected = false, + isLetter = false + ) + + assertThat(packageData.length).isEmpty() + assertThat(packageData.width).isEmpty() + assertThat(packageData.height).isEmpty() + } +}