From e24435defa8f3f996725f6f891a3dc05d2c00e88 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 29 Nov 2024 14:59:15 +0700 Subject: [PATCH 01/37] Update Shipping card info to not show "One time shipping'" for non-subscription product. --- .../details/ProductDetailCardBuilder.kt | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt index 2f00df7c080..406141c9cc4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt @@ -471,21 +471,26 @@ class ProductDetailCardBuilder( @Suppress("LongMethod") private fun ProductAggregate.shipping(): ProductProperty? { - return if (!this.product.isVirtual && hasShipping) { + val currentProduct = this.product + return if (!currentProduct.isVirtual && hasShipping) { val weightWithUnits = product.getWeightWithUnits(parameters.weightUnit) val sizeWithUnits = product.getSizeWithUnits(parameters.dimensionUnit) - val shippingGroup = mapOf( - Pair(resources.getString(string.product_weight), weightWithUnits), - Pair(resources.getString(string.product_dimensions), sizeWithUnits), - Pair( + val shippingGroup = buildMap { + put(resources.getString(string.product_weight), weightWithUnits) + put(resources.getString(string.product_dimensions), sizeWithUnits) + put( resources.getString(string.product_shipping_class), - viewModel.getShippingClassByRemoteShippingClassId(this.product.shippingClassId) - ), - Pair( - resources.getString(string.subscription_one_time_shipping), - buildOneTimeShippingDescription(subscription) + viewModel.getShippingClassByRemoteShippingClassId(currentProduct.shippingClassId) ) - ) + + // Only add "One time shipping" info if product is subscription type + if (currentProduct.productType == SUBSCRIPTION) { + put( + resources.getString(string.subscription_one_time_shipping), + buildOneTimeShippingDescription(subscription) + ) + } + } PropertyGroup( string.product_shipping, From 0be2476f655d73d2d13fb8a7bce47cbae881acd5 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 29 Nov 2024 16:22:09 +0700 Subject: [PATCH 02/37] Add unit tests related to "One time shipping" display in subscription and simple product details. --- .../details/ProductDetailCardBuilderTest.kt | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt index fd46cdc4549..0d742db1e1d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt @@ -6,6 +6,7 @@ import com.woocommerce.android.model.ProductAggregate import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.blaze.IsBlazeEnabled import com.woocommerce.android.ui.customfields.CustomFieldsRepository +import com.woocommerce.android.ui.products.ProductHelper import com.woocommerce.android.ui.products.ProductTestUtils import com.woocommerce.android.ui.products.ProductType import com.woocommerce.android.ui.products.addons.AddonRepository @@ -37,6 +38,10 @@ class ProductDetailCardBuilderTest : BaseUnitTest() { onBlocking { hasDisplayableCustomFields(any()) } doReturn false } + private val resourceProvider: ResourceProvider = mock { + on { getString(any()) } doAnswer { it.getArgument(0).toString() } + } + @Before fun setUp() { val viewModel: ProductDetailViewModel = mock { @@ -194,4 +199,73 @@ class ProductDetailCardBuilderTest : BaseUnitTest() { } Assertions.assertThat(customFieldsCard).isNull() } + + @Test + fun `given subscription product with one time shipping enabled, when building cards, then shipping includes one-time shipping`() = testBlocking { + productStub = ProductTestUtils.generateProduct() + .copy( + isVirtual = false, + type = ProductType.SUBSCRIPTION.value, + weight = 1.5f, + length = 10f, + width = 20f, + height = 30f, + shippingClassId = 123 + ) + + val subscriptionDetails = ProductHelper.getDefaultSubscriptionDetails().copy( + oneTimeShipping = true + ) + + val cards = sut.buildPropertyCards( + ProductAggregate( + product = productStub, + subscription = subscriptionDetails + ), + "" + ) + + val shippingGroup = cards.first { it.type == ProductPropertyCard.Type.SECONDARY } + .properties + .find { + it is ProductProperty.PropertyGroup && + it.title == R.string.product_shipping + } as ProductProperty.PropertyGroup + + val propertyKeys = shippingGroup.properties.toList().map { it.first } + Assertions.assertThat(propertyKeys).hasSize(4) // Weight, Dimensions, Shipping class, One-time shipping + Assertions.assertThat(propertyKeys).contains( + resourceProvider.getString(R.string.subscription_one_time_shipping) + ) + } + + @Test + fun `given simple non-virtual product, when building cards, then shipping excludes one-time shipping`() = testBlocking { + productStub = ProductTestUtils.generateProduct() + .copy( + isVirtual = false, + type = ProductType.SIMPLE.value, + weight = 1.5f, + length = 10f, + width = 20f, + height = 30f, + shippingClassId = 123 + ) + + val cards = sut.buildPropertyCards(ProductAggregate(productStub), "") + + val shippingGroup = cards.first { it.type == ProductPropertyCard.Type.SECONDARY } + .properties + .find { + it is ProductProperty.PropertyGroup && + it.title == R.string.product_shipping + } as ProductProperty.PropertyGroup + + val propertyKeys = shippingGroup.properties.toList().map { it.first } + + Assertions.assertThat(propertyKeys).hasSize(3) // Weight, Dimensions, Shipping class + Assertions.assertThat(propertyKeys).doesNotContain( + resourceProvider.getString(R.string.subscription_one_time_shipping) + ) + } } From e348bb43a919a8a268c958ac3a9e65f4296d3c30 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 29 Nov 2024 16:37:04 +0700 Subject: [PATCH 03/37] Update release notes --- RELEASE-NOTES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 365f7acba1f..5372a30bcd7 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -4,9 +4,11 @@ 21.3 ----- - [*] "One time shipping" label in Product Subscriptions now matches its availability state correctly. [https://github.com/woocommerce/woocommerce-android/pull/13021] +- [*] "One time shipping" label should not be shown in Simple product after conversion from Subscriptions product. [https://github.com/woocommerce/woocommerce-android/pull/13032] - [Internal] Refactored IPP Payment flow to allow customizing payment collection UI in POS [https://github.com/woocommerce/woocommerce-android/pull/13014] - [*] Blaze Campaign Intro screen now offers creating a new product if the site has no products yet [https://github.com/woocommerce/woocommerce-android/pull/13001] + ----- 21.2 - [Internal] Changed a way how authenticated web view opened in the IPP flows [https://github.com/woocommerce/woocommerce-android/pull/12908] From 54aea1171112e524695fb2dd08109a077def99f1 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 29 Nov 2024 17:14:43 +0700 Subject: [PATCH 04/37] Update product detail unit test. Given the mock product is simple, it should not show "One time shipping" label. --- .../android/ui/products/details/ProductDetailViewModelTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index b846c22e2cd..1eba481f348 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -208,8 +208,7 @@ class ProductDetailViewModelTest : BaseUnitTest() { resources.getString(R.string.product_dimensions), productWithParameters.productDraft?.getSizeWithUnits(siteParams.dimensionUnit) ?: "" ), - Pair(resources.getString(R.string.product_shipping_class), ""), - Pair(resources.getString(R.string.subscription_one_time_shipping), "") + Pair(resources.getString(R.string.product_shipping_class), "") ), R.drawable.ic_gridicons_shipping, true From f62e0dff722082f6fb60641844a55cc8e5c5ebd2 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 29 Nov 2024 20:25:23 +0700 Subject: [PATCH 05/37] Add logic to remove existing subscription data when converting product type to non-subscription. --- .../ui/products/details/ProductDetailViewModel.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index daaaef496d4..c7e12e43548 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt @@ -1226,6 +1226,13 @@ class ProductDetailViewModel @Inject constructor( productType: ProductType, isVirtual: Boolean ) { + // Reset subscription data that might have existed to null when changing type to non-subscription product type. + // This avoids any Product Details card conflicts after conversion (e.g: displaying Subscription-related info + // in a non-subscription product) + if (productType != ProductType.SUBSCRIPTION) { + viewState = viewState.copy(subscriptionDraft = null) + } + updateProductDraft(type = productType.value, isVirtual = isVirtual) viewState.productAggregateDraft?.let { productAggregateDraft -> @@ -2731,7 +2738,7 @@ class ProductDetailViewModel @Inject constructor( } ) - fun copy(subscriptionDraft: SubscriptionDetails) = copy( + fun copy(subscriptionDraft: SubscriptionDetails?) = copy( productAggregateDraft = productAggregateDraft?.copy(subscription = subscriptionDraft) ) From 6c78cef7e9e4175da01c65217252bc9b3a1d4c05 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 11:28:51 +0700 Subject: [PATCH 06/37] Also show One time shipping label if type is VARIABLE_SUBSCRIPTION --- .../android/ui/products/details/ProductDetailCardBuilder.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt index 406141c9cc4..ede5d4cf90e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt @@ -483,8 +483,8 @@ class ProductDetailCardBuilder( viewModel.getShippingClassByRemoteShippingClassId(currentProduct.shippingClassId) ) - // Only add "One time shipping" info if product is subscription type - if (currentProduct.productType == SUBSCRIPTION) { + // Only add "One time shipping" info if product is Subscription types + if (currentProduct.productType == SUBSCRIPTION || currentProduct.productType == VARIABLE_SUBSCRIPTION) { put( resources.getString(string.subscription_one_time_shipping), buildOneTimeShippingDescription(subscription) From e5b5579ff81a989ec03dbfd4ad526ae4e5b8555d Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 11:31:27 +0700 Subject: [PATCH 07/37] Update unit test for VARIABLE_PRODUCT case. --- .../details/ProductDetailCardBuilderTest.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt index 0d742db1e1d..b76ec4801e6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt @@ -239,6 +239,45 @@ class ProductDetailCardBuilderTest : BaseUnitTest() { ) } + @Test + fun `given variable subscription product with one time shipping enabled, when building cards, then shipping includes one-time shipping`() = testBlocking { + productStub = ProductTestUtils.generateProduct() + .copy( + isVirtual = false, + type = ProductType.VARIABLE_SUBSCRIPTION.value, + weight = 1.5f, + length = 10f, + width = 20f, + height = 30f, + shippingClassId = 123 + ) + + val subscriptionDetails = ProductHelper.getDefaultSubscriptionDetails().copy( + oneTimeShipping = true + ) + + val cards = sut.buildPropertyCards( + ProductAggregate( + product = productStub, + subscription = subscriptionDetails + ), + "" + ) + + val shippingGroup = cards.first { it.type == ProductPropertyCard.Type.SECONDARY } + .properties + .find { + it is ProductProperty.PropertyGroup && + it.title == R.string.product_shipping + } as ProductProperty.PropertyGroup + + val propertyKeys = shippingGroup.properties.toList().map { it.first } + Assertions.assertThat(propertyKeys).hasSize(4) // Weight, Dimensions, Shipping class, One-time shipping + Assertions.assertThat(propertyKeys).contains( + resourceProvider.getString(R.string.subscription_one_time_shipping) + ) + } + @Test fun `given simple non-virtual product, when building cards, then shipping excludes one-time shipping`() = testBlocking { productStub = ProductTestUtils.generateProduct() From ba6e1c63650092b619ac0196221e65fd0d582ca8 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 12:00:25 +0700 Subject: [PATCH 08/37] Group logic with existing subscription handling and add comments to clarify. --- .../details/ProductDetailViewModel.kt | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index c7e12e43548..553c75c000c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt @@ -1226,23 +1226,25 @@ class ProductDetailViewModel @Inject constructor( productType: ProductType, isVirtual: Boolean ) { - // Reset subscription data that might have existed to null when changing type to non-subscription product type. - // This avoids any Product Details card conflicts after conversion (e.g: displaying Subscription-related info - // in a non-subscription product) - if (productType != ProductType.SUBSCRIPTION) { - viewState = viewState.copy(subscriptionDraft = null) - } - updateProductDraft(type = productType.value, isVirtual = isVirtual) viewState.productAggregateDraft?.let { productAggregateDraft -> - if (productType == ProductType.SUBSCRIPTION && productAggregateDraft.subscription == null) { - viewState = viewState.copy( - subscriptionDraft = ProductHelper.getDefaultSubscriptionDetails().copy( - price = productAggregateDraft.product.regularPrice - ) - ) - } + viewState = viewState.copy( + subscriptionDraft = when { + // If converting to subscription product, set the default subscription details + productType == ProductType.SUBSCRIPTION && productAggregateDraft.subscription == null -> + ProductHelper.getDefaultSubscriptionDetails().copy( + price = productAggregateDraft.product.regularPrice + ) + + // If converting to non-subscription products, reset subscription data that might have existed + // (e.g: if the original product is of subscription type). + // This avoids any Product Details card conflicts that can happen after conversion. + productType !in setOf(ProductType.SUBSCRIPTION, ProductType.VARIABLE_SUBSCRIPTION) -> null + + else -> viewState.subscriptionDraft + } + ) } } From 6aed151ffbad4968e1c2ee202f15eebe2850b928 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 12:09:43 +0700 Subject: [PATCH 09/37] Add unit tests related to subscription product conversions. --- .../android/ui/products/ProductTestUtils.kt | 16 ++++ .../details/ProductDetailViewModelTest.kt | 76 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt index ad18e4a54fe..cdd73bc1a45 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt @@ -5,10 +5,13 @@ import com.woocommerce.android.model.ProductAttribute import com.woocommerce.android.model.ProductCategory import com.woocommerce.android.model.ProductTag import com.woocommerce.android.model.ProductVariation +import com.woocommerce.android.model.SubscriptionDetails +import com.woocommerce.android.model.SubscriptionPeriod import com.woocommerce.android.model.toAppModel import com.woocommerce.android.ui.products.ProductStatus.DRAFT import org.wordpress.android.fluxc.model.WCProductModel import org.wordpress.android.fluxc.model.WCProductVariationModel +import java.math.BigDecimal import java.sql.Date import java.time.Instant @@ -196,4 +199,17 @@ object ProductTestUtils { return this } } + + fun generateProductSubscriptionDetails() = SubscriptionDetails( + price = BigDecimal.TEN, + period = SubscriptionPeriod.Month, + periodInterval = 1, + length = null, + signUpFee = null, + trialPeriod = null, + trialLength = null, + oneTimeShipping = true, + paymentsSyncDate = null + ) + } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index 1eba481f348..693995e2bbe 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -19,6 +19,7 @@ import com.woocommerce.android.ui.media.MediaFileUploadHandler import com.woocommerce.android.ui.products.ParameterRepository import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ProductTestUtils +import com.woocommerce.android.ui.products.ProductType import com.woocommerce.android.ui.products.addons.AddonRepository import com.woocommerce.android.ui.products.categories.ProductCategoriesRepository import com.woocommerce.android.ui.products.models.ProductProperty @@ -1243,6 +1244,81 @@ class ProductDetailViewModelTest : BaseUnitTest() { verify(productRepository, never()).fetchProductPassword(any()) } + @Test + fun `When converting from subscription to simple product, subscription data is cleared`() = testBlocking { + // GIVEN + val subscriptionProduct = productAggregate.copy( + product = productAggregate.product.copy( + type = ProductType.SUBSCRIPTION.value + ), + subscription = ProductTestUtils.generateProductSubscriptionDetails() + ) + doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) + viewModel.productDetailViewStateData.observeForever { _, _ -> } + viewModel.start() + + // Verify initial state has subscription data + Assertions.assertThat(viewModel.getProduct().subscriptionDraft).isNotNull() + + // WHEN + viewModel.onProductTypeChanged(ProductType.SIMPLE, false) + + // THEN + Assertions.assertThat(viewModel.getProduct().subscriptionDraft).isNull() + } + + @Test + fun `When converting from simple to subscription product, default subscription data is added`() = testBlocking { + // GIVEN + val simpleProduct = productAggregate.copy( + product = productAggregate.product.copy( + type = ProductType.SIMPLE.value, + regularPrice = BigDecimal("10.00") + ), + subscription = null + ) + doReturn(simpleProduct).whenever(productRepository).getProductAggregate(any()) + viewModel.productDetailViewStateData.observeForever { _, _ -> } + viewModel.start() + + // Verify initial state has no subscription data + Assertions.assertThat(viewModel.getProduct().subscriptionDraft).isNull() + + // WHEN + viewModel.onProductTypeChanged(ProductType.SUBSCRIPTION, false) + + // THEN + viewModel.getProduct().subscriptionDraft?.let { + Assertions.assertThat(it.price).isEqualTo(simpleProduct.product.regularPrice) + Assertions.assertThat(it.period) + .isEqualTo(ProductTestUtils.generateProductSubscriptionDetails().period) + Assertions.assertThat(it.periodInterval) + .isEqualTo(ProductTestUtils.generateProductSubscriptionDetails().periodInterval) + } ?: Assertions.fail("Subscription draft should not be null") + } + + @Test + fun `When converting from simple subscription to variable subscription product, subscription data is preserved`() = testBlocking { + // GIVEN + val subscriptionProduct = productAggregate.copy( + product = productAggregate.product.copy( + type = ProductType.SUBSCRIPTION.value + ), + subscription = ProductTestUtils.generateProductSubscriptionDetails() + ) + doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) + viewModel.productDetailViewStateData.observeForever { _, _ -> } + viewModel.start() + + val originalSubscription = viewModel.getProduct().subscriptionDraft + + // WHEN + viewModel.onProductTypeChanged(ProductType.VARIABLE_SUBSCRIPTION, false) + + // THEN + Assertions.assertThat(viewModel.getProduct().subscriptionDraft).isEqualTo(originalSubscription) + } + private val productsDraft get() = viewModel.productDetailViewStateData.liveData.value?.productDraft } From ae87485a0da8f2144f9d8369bfd38d9c660c3e2e Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 12:43:12 +0700 Subject: [PATCH 10/37] Revert unneeded change. --- .../android/ui/products/details/ProductDetailViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index 553c75c000c..9f3588b3345 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt @@ -2740,7 +2740,7 @@ class ProductDetailViewModel @Inject constructor( } ) - fun copy(subscriptionDraft: SubscriptionDetails?) = copy( + fun copy(subscriptionDraft: SubscriptionDetails) = copy( productAggregateDraft = productAggregateDraft?.copy(subscription = subscriptionDraft) ) From 2461e4bccbaaf0553676d3a379630920fd28de54 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 12:47:54 +0700 Subject: [PATCH 11/37] Update release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 5372a30bcd7..0c529ff9a2c 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,6 +5,7 @@ ----- - [*] "One time shipping" label in Product Subscriptions now matches its availability state correctly. [https://github.com/woocommerce/woocommerce-android/pull/13021] - [*] "One time shipping" label should not be shown in Simple product after conversion from Subscriptions product. [https://github.com/woocommerce/woocommerce-android/pull/13032] +- [*] Fixed bug related to missing Shipping card in Product details when converting from Subscriptions to Simple product. [https://github.com/woocommerce/woocommerce-android/pull/13035] - [Internal] Refactored IPP Payment flow to allow customizing payment collection UI in POS [https://github.com/woocommerce/woocommerce-android/pull/13014] - [*] Blaze Campaign Intro screen now offers creating a new product if the site has no products yet [https://github.com/woocommerce/woocommerce-android/pull/13001] From 8f3233dbb26090c1c62579b218b9efadb3a77f91 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 12:48:57 +0700 Subject: [PATCH 12/37] Detekt fix --- .../com/woocommerce/android/ui/products/ProductTestUtils.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt index cdd73bc1a45..a38386b1425 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt @@ -211,5 +211,4 @@ object ProductTestUtils { oneTimeShipping = true, paymentsSyncDate = null ) - } From 0f3e0c1b343366ab122914f993a568862f1973c4 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Mon, 2 Dec 2024 13:06:54 +0700 Subject: [PATCH 13/37] Revert "Revert unneeded change." This reverts commit ae87485a0da8f2144f9d8369bfd38d9c660c3e2e. --- .../android/ui/products/details/ProductDetailViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index 9f3588b3345..553c75c000c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt @@ -2740,7 +2740,7 @@ class ProductDetailViewModel @Inject constructor( } ) - fun copy(subscriptionDraft: SubscriptionDetails) = copy( + fun copy(subscriptionDraft: SubscriptionDetails?) = copy( productAggregateDraft = productAggregateDraft?.copy(subscription = subscriptionDraft) ) From 2c99170d5f123e979db61bcdf5377832cfc6b930 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 3 Dec 2024 15:16:54 +0100 Subject: [PATCH 14/37] Replace legacy event when selecting custom range in dashboard cards --- .../kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt | 1 - .../android/ui/dashboard/stats/DashboardStatsViewModel.kt | 2 +- .../dashboard/topperformers/DashboardTopPerformersViewModel.kt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt index 28c504ac55b..7813067728f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt @@ -178,7 +178,6 @@ enum class AnalyticsEvent(override val siteless: Boolean = false) : IAnalyticsEv STATS_UNEXPECTED_FORMAT, DASHBOARD_STATS_CUSTOM_RANGE_ADD_BUTTON_TAPPED, DASHBOARD_STATS_CUSTOM_RANGE_CONFIRMED, - DASHBOARD_STATS_CUSTOM_RANGE_TAB_SELECTED, DASHBOARD_STATS_CUSTOM_RANGE_EDIT_BUTTON_TAPPED, DASHBOARD_STATS_CUSTOM_RANGE_INTERACTED, DYNAMIC_DASHBOARD_EDIT_LAYOUT_BUTTON_TAPPED, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt index 6e9a3c5f111..664dc9c3969 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt @@ -134,7 +134,7 @@ class DashboardStatsViewModel @AssistedInject constructor( } else { appPrefsWrapper.setActiveStatsTab(SelectionType.CUSTOM.name) analyticsTrackerWrapper.track( - AnalyticsEvent.DASHBOARD_STATS_CUSTOM_RANGE_TAB_SELECTED + AnalyticsEvent.DASHBOARD_STATS_CUSTOM_RANGE_ADD_BUTTON_TAPPED ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt index 5bbafb6b035..82c2828cb59 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt @@ -150,7 +150,7 @@ class DashboardTopPerformersViewModel @AssistedInject constructor( onEditCustomRangeTapped() } else { appPrefsWrapper.setActiveTopPerformersTab(SelectionType.CUSTOM.name) - analyticsTrackerWrapper.track(AnalyticsEvent.DASHBOARD_STATS_CUSTOM_RANGE_TAB_SELECTED) + analyticsTrackerWrapper.track(AnalyticsEvent.DASHBOARD_STATS_CUSTOM_RANGE_ADD_BUTTON_TAPPED) } } } From d431e71eecf87631aece1476b0612acb7894d8c7 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 3 Dec 2024 15:17:34 +0100 Subject: [PATCH 15/37] Remove unused dashboard tracking events --- .../kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt index 7813067728f..4fe88f817f7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt @@ -167,10 +167,7 @@ enum class AnalyticsEvent(override val siteless: Boolean = false) : IAnalyticsEv DASHBOARD_SHARE_YOUR_STORE_BUTTON_TAPPED, DASHBOARD_MAIN_STATS_DATE, DASHBOARD_MAIN_STATS_LOADED, - DASHBOARD_TOP_PERFORMERS_DATE, DASHBOARD_TOP_PERFORMERS_LOADED, - DASHBOARD_NEW_STATS_REVERTED_BANNER_DISMISS_TAPPED, - DASHBOARD_NEW_STATS_REVERTED_BANNER_LEARN_MORE_TAPPED, DASHBOARD_WAITING_TIME_LOADED, DASHBOARD_SEE_MORE_ANALYTICS_TAPPED, DASHBOARD_STORE_TIMEZONE_DIFFER_FROM_DEVICE, From f4e045cf6a1686d52eb9786b01740b99ef5fcffd Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 3 Dec 2024 15:40:50 +0100 Subject: [PATCH 16/37] Rename function to remove the old concept of "tab" --- .../android/ui/dashboard/stats/DashboardStatsCard.kt | 2 +- .../ui/dashboard/stats/DashboardStatsViewModel.kt | 4 ++-- .../topperformers/DashboardTopPerformersViewModel.kt | 4 ++-- .../topperformers/DashboardTopPerformersWidgetCard.kt | 2 +- .../ui/dashboard/stats/DashboardStatsViewModelTest.kt | 10 +++++----- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsCard.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsCard.kt index 244c14dc219..561247c2419 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsCard.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsCard.kt @@ -98,7 +98,7 @@ fun DashboardStatsCard( currencyFormatter = viewModel.currencyFormatter, usageTracksEventEmitter = viewModel.usageTracksEventEmitter, onAddCustomRangeClick = viewModel::onAddCustomRangeClicked, - onTabSelected = viewModel::onTabSelected, + onTabSelected = viewModel::onRangeChanged, onChartDateSelected = viewModel::onChartDateSelected ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt index 664dc9c3969..e1cbe2a4f25 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModel.kt @@ -123,7 +123,7 @@ class DashboardStatsViewModel @AssistedInject constructor( trackLocalTimezoneDifferenceFromStore() } - fun onTabSelected(selectionType: SelectionType) { + fun onRangeChanged(selectionType: SelectionType) { parentViewModel.trackCardInteracted(DashboardWidget.Type.STATS.trackingIdentifier) usageTracksEventEmitter.interacted() if (selectionType != SelectionType.CUSTOM) { @@ -151,7 +151,7 @@ class DashboardStatsViewModel @AssistedInject constructor( viewModelScope.launch { customDateRangeDataStore.updateDateRange(range) if (dateRangeState.value?.rangeSelection?.selectionType != SelectionType.CUSTOM) { - onTabSelected(SelectionType.CUSTOM) + onRangeChanged(SelectionType.CUSTOM) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt index 82c2828cb59..f279156a4fb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersViewModel.kt @@ -140,7 +140,7 @@ class DashboardTopPerformersViewModel @AssistedInject constructor( } } - fun onTabSelected(selectionType: SelectionType) { + fun onRangeChanged(selectionType: SelectionType) { parentViewModel.trackCardInteracted(DashboardWidget.Type.POPULAR_PRODUCTS.trackingIdentifier) usageTracksEventEmitter.interacted() if (selectionType != SelectionType.CUSTOM) { @@ -287,7 +287,7 @@ class DashboardTopPerformersViewModel @AssistedInject constructor( viewModelScope.launch { customDateRangeDataStore.updateDateRange(statsTimeRange) if (selectedDateRange.value?.rangeSelection?.selectionType != SelectionType.CUSTOM) { - onTabSelected(SelectionType.CUSTOM) + onRangeChanged(SelectionType.CUSTOM) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersWidgetCard.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersWidgetCard.kt index 9a06d8d7352..1b3a2e9ed90 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersWidgetCard.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/topperformers/DashboardTopPerformersWidgetCard.kt @@ -95,7 +95,7 @@ fun DashboardTopPerformersWidgetCard( topPerformersState = topPerformersState, selectedDateRange = selectedDateRange, lastUpdateState = lastUpdateState, - onTabSelected = topPerformersViewModel::onTabSelected, + onTabSelected = topPerformersViewModel::onRangeChanged, onEditCustomRangeTapped = topPerformersViewModel::onEditCustomRangeTapped ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModelTest.kt index 6495032b0f4..27d868d8046 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsViewModelTest.kt @@ -141,7 +141,7 @@ class DashboardStatsViewModelTest : BaseUnitTest() { whenever(networkStatus.isConnected()).thenReturn(false) } - viewModel.onTabSelected(ANY_SELECTION_TYPE) + viewModel.onRangeChanged(ANY_SELECTION_TYPE) verify(getStats, never()).invoke(any(), any()) } @@ -155,7 +155,7 @@ class DashboardStatsViewModelTest : BaseUnitTest() { .thenReturn(ANY_SELECTION_TYPE.name) } - viewModel.onTabSelected(ANY_SELECTION_TYPE) + viewModel.onRangeChanged(ANY_SELECTION_TYPE) verify(getStats, times(2)).invoke( refresh = ArgumentMatchers.eq(false), @@ -196,7 +196,7 @@ class DashboardStatsViewModelTest : BaseUnitTest() { .thenReturn(ANY_SELECTION_TYPE.name) } - viewModel.onTabSelected(ANY_SELECTION_TYPE) + viewModel.onRangeChanged(ANY_SELECTION_TYPE) Assertions.assertThat(viewModel.revenueStatsState.value) .isInstanceOf(DashboardStatsViewModel.RevenueStatsViewState.Content::class.java) @@ -209,7 +209,7 @@ class DashboardStatsViewModelTest : BaseUnitTest() { testBlocking { setup() - viewModel.onTabSelected(ANY_SELECTION_TYPE) + viewModel.onRangeChanged(ANY_SELECTION_TYPE) verify(appPrefsWrapper).setActiveStatsTab(ANY_SELECTION_TYPE.name) } @@ -286,7 +286,7 @@ class DashboardStatsViewModelTest : BaseUnitTest() { val state = viewModel.dateRangeState.runAndCaptureValues { viewModel.onChartDateSelected("11") - viewModel.onTabSelected(ANY_SELECTION_TYPE) + viewModel.onRangeChanged(ANY_SELECTION_TYPE) }.last() verify(dateRangeFormatter, never()) From 8c9ad8dec116ec5dd5442cd07ef48f04464d2a8b Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Mon, 2 Dec 2024 16:02:01 -0300 Subject: [PATCH 17/37] Add a Corruption Handler to the DashboardDataStore creation --- .../woocommerce/android/datastore/DashboardDataStoreModule.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt index f6ae4cdf078..8412990dacb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.datastore import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.dataStoreFile import com.woocommerce.android.di.SiteComponent import com.woocommerce.android.di.SiteCoroutineScope @@ -29,6 +30,9 @@ object DashboardDataStoreModule { produceFile = { appContext.dataStoreFile("dashboard_configuration_${site.id}") }, + corruptionHandler = ReplaceFileCorruptionHandler { + DashboardDataModel.getDefaultInstance() + }, scope = CoroutineScope(siteCoroutineScope.coroutineContext + Dispatchers.IO), serializer = DashboardSerializer ) From d77fe117d70f982c7c0842e61fb9886c9ab3e8bb Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Mon, 2 Dec 2024 16:05:12 -0300 Subject: [PATCH 18/37] Add CorruptionHandler to the CustomDateRange DataStore --- .../com/woocommerce/android/datastore/DataStoreModule.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt index 2d8dee4c113..dd1ab34f544 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.datastore import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStoreFile @@ -102,6 +103,9 @@ class DataStoreModule { produceFile = { appContext.preferencesDataStoreFile("dashboard_coupons_custom_date_range_configuration") }, + corruptionHandler = ReplaceFileCorruptionHandler { + CustomDateRange.getDefaultInstance() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO), serializer = CustomDateRangeSerializer ) From 068cb66e70684c702aca3e750c6d5097548d2067 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 10:56:59 -0300 Subject: [PATCH 19/37] Add Preferences corruption handlers to all Preferences DataStores --- .../woocommerce/android/datastore/DataStoreModule.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt index dd1ab34f544..dd3c3986c4e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt @@ -4,8 +4,10 @@ import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler +import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.preferencesDataStoreFile import com.woocommerce.android.datastore.DataStoreType.ANALYTICS_CONFIGURATION import com.woocommerce.android.datastore.DataStoreType.ANALYTICS_UI_CACHE @@ -36,6 +38,7 @@ class DataStoreModule { produceFile = { appContext.preferencesDataStoreFile("tracker") }, + corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -49,6 +52,7 @@ class DataStoreModule { produceFile = { appContext.preferencesDataStoreFile("analytics") }, + corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -62,6 +66,7 @@ class DataStoreModule { produceFile = { appContext.preferencesDataStoreFile("analytics_configuration") }, + corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -75,6 +80,9 @@ class DataStoreModule { produceFile = { appContext.preferencesDataStoreFile("custom_date_range_configuration") }, + corruptionHandler = ReplaceFileCorruptionHandler { + CustomDateRange.getDefaultInstance() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO), serializer = CustomDateRangeSerializer ) @@ -89,6 +97,9 @@ class DataStoreModule { produceFile = { appContext.preferencesDataStoreFile("top_performers_custom_date_range_configuration") }, + corruptionHandler = ReplaceFileCorruptionHandler { + CustomDateRange.getDefaultInstance() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO), serializer = CustomDateRangeSerializer ) @@ -120,6 +131,7 @@ class DataStoreModule { produceFile = { appContext.preferencesDataStoreFile("update") }, + corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) } From 7143d5ace2ab96648ec9f6c77d3bcbef2a195cfa Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 10:57:06 -0300 Subject: [PATCH 20/37] Add Preferences corruption handlers to all Wear DataStores --- .../java/com/woocommerce/android/wear/di/DataStoreModule.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt index a7725fd11e1..ef496c008a4 100644 --- a/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt +++ b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt @@ -2,8 +2,10 @@ package com.woocommerce.android.wear.di import android.content.Context import androidx.datastore.core.DataStore +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.preferencesDataStoreFile import com.woocommerce.android.wear.datastore.DataStoreQualifier import com.woocommerce.android.wear.datastore.DataStoreType.LOGIN @@ -28,6 +30,7 @@ class DataStoreModule { @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(LOGIN.typeName) }, + corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -39,6 +42,7 @@ class DataStoreModule { @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(STATS.typeName) }, + corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -50,6 +54,7 @@ class DataStoreModule { @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(ORDERS.typeName) }, + corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) } From 8235e6008beb76e181f92e052e7537ccaae359d9 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 11:03:40 -0300 Subject: [PATCH 21/37] Remove unused imports --- .../kotlin/com/woocommerce/android/datastore/DataStoreModule.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt index dd3c3986c4e..d8a906ffbfb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt @@ -4,7 +4,6 @@ import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler -import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.emptyPreferences From ab411d0f1b495e28991664797f398990f20f0065 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 12:03:02 -0300 Subject: [PATCH 22/37] Add Event recordings for All DataStores whenever the File Corruption Handler is called --- .../android/datastore/DataStoreModule.kt | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt index d8a906ffbfb..ce8c27e25b4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt @@ -8,9 +8,12 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.preferencesDataStoreFile +import com.automattic.android.tracks.crashlogging.CrashLogging import com.woocommerce.android.datastore.DataStoreType.ANALYTICS_CONFIGURATION import com.woocommerce.android.datastore.DataStoreType.ANALYTICS_UI_CACHE +import com.woocommerce.android.datastore.DataStoreType.COUPONS import com.woocommerce.android.datastore.DataStoreType.DASHBOARD_STATS +import com.woocommerce.android.datastore.DataStoreType.LAST_UPDATE import com.woocommerce.android.datastore.DataStoreType.TOP_PERFORMER_PRODUCTS import com.woocommerce.android.datastore.DataStoreType.TRACKER import com.woocommerce.android.di.AppCoroutineScope @@ -32,12 +35,16 @@ class DataStoreModule { @DataStoreQualifier(TRACKER) fun provideTrackerDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile("tracker") }, - corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${TRACKER.name}") + emptyPreferences() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -46,12 +53,16 @@ class DataStoreModule { @DataStoreQualifier(ANALYTICS_UI_CACHE) fun provideAnalyticsDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile("analytics") }, - corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${ANALYTICS_UI_CACHE.name}") + emptyPreferences() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -60,12 +71,16 @@ class DataStoreModule { @DataStoreQualifier(ANALYTICS_CONFIGURATION) fun provideAnalyticsConfigurationDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile("analytics_configuration") }, - corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${ANALYTICS_CONFIGURATION.name}") + emptyPreferences() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -74,12 +89,14 @@ class DataStoreModule { @DataStoreQualifier(DASHBOARD_STATS) fun provideCustomDateRangeDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = DataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile("custom_date_range_configuration") }, corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${DASHBOARD_STATS.name}") CustomDateRange.getDefaultInstance() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO), @@ -91,12 +108,14 @@ class DataStoreModule { @DataStoreQualifier(TOP_PERFORMER_PRODUCTS) fun provideTopPerformersCustomDateRangeDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = DataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile("top_performers_custom_date_range_configuration") }, corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${TOP_PERFORMER_PRODUCTS.name}") CustomDateRange.getDefaultInstance() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO), @@ -105,15 +124,17 @@ class DataStoreModule { @Provides @Singleton - @DataStoreQualifier(DataStoreType.COUPONS) + @DataStoreQualifier(COUPONS) fun provideCouponsCustomDateRangeDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = DataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile("dashboard_coupons_custom_date_range_configuration") }, corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${COUPONS.name}") CustomDateRange.getDefaultInstance() }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO), @@ -122,15 +143,19 @@ class DataStoreModule { @Provides @Singleton - @DataStoreQualifier(DataStoreType.LAST_UPDATE) + @DataStoreQualifier(LAST_UPDATE) fun provideLastUpdateDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ) = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile("update") }, - corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${LAST_UPDATE.name}") + emptyPreferences() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) } From d36f98d8957398b4bae6ac76c0a7c3b33e48ac47 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 12:04:17 -0300 Subject: [PATCH 23/37] Add Event recordings for the Dashboard DataStores whenever the File Corruption Handler is called --- .../woocommerce/android/datastore/DashboardDataStoreModule.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt index 8412990dacb..326d3d7b1f8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.dataStoreFile +import com.automattic.android.tracks.crashlogging.CrashLogging import com.woocommerce.android.di.SiteComponent import com.woocommerce.android.di.SiteCoroutineScope import com.woocommerce.android.di.SiteScope @@ -24,6 +25,7 @@ object DashboardDataStoreModule { @SiteScope fun provideDashboardDataStore( appContext: Context, + crashLogging: CrashLogging, @SiteCoroutineScope siteCoroutineScope: CoroutineScope, site: SiteModel ): DataStore = DataStoreFactory.create( @@ -31,6 +33,7 @@ object DashboardDataStoreModule { appContext.dataStoreFile("dashboard_configuration_${site.id}") }, corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store: Dashboard") DashboardDataModel.getDefaultInstance() }, scope = CoroutineScope(siteCoroutineScope.coroutineContext + Dispatchers.IO), From 9b0a5d862da8f166645acedd33c6434ffbf8aa07 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 12:04:26 -0300 Subject: [PATCH 24/37] Add Event recordings for the Wear DataStores whenever the File Corruption Handler is called --- .../android/wear/di/DataStoreModule.kt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt index ef496c008a4..02fc03614a8 100644 --- a/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt +++ b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt @@ -7,6 +7,7 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.preferencesDataStoreFile +import com.automattic.android.tracks.crashlogging.CrashLogging import com.woocommerce.android.wear.datastore.DataStoreQualifier import com.woocommerce.android.wear.datastore.DataStoreType.LOGIN import com.woocommerce.android.wear.datastore.DataStoreType.ORDERS @@ -27,10 +28,14 @@ class DataStoreModule { @DataStoreQualifier(LOGIN) fun provideLoginDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(LOGIN.typeName) }, - corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store: Wear ${LOGIN.typeName}") + emptyPreferences() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -39,10 +44,14 @@ class DataStoreModule { @DataStoreQualifier(STATS) fun provideStatsDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(STATS.typeName) }, - corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store: Wear ${STATS.typeName}") + emptyPreferences() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -51,10 +60,14 @@ class DataStoreModule { @DataStoreQualifier(ORDERS) fun provideOrdersDataStore( appContext: Context, + crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(ORDERS.typeName) }, - corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store: Wear ${ORDERS.typeName}") + emptyPreferences() + }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) } From 167b36cc1b0fba6f62d737e5f623e7a5a7f52d0c Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 12:46:25 -0300 Subject: [PATCH 25/37] Leave the Wear adjustments for a later improvement --- .../android/wear/di/DataStoreModule.kt | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt index 02fc03614a8..a7725fd11e1 100644 --- a/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt +++ b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/DataStoreModule.kt @@ -2,12 +2,9 @@ package com.woocommerce.android.wear.di import android.content.Context import androidx.datastore.core.DataStore -import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.preferencesDataStoreFile -import com.automattic.android.tracks.crashlogging.CrashLogging import com.woocommerce.android.wear.datastore.DataStoreQualifier import com.woocommerce.android.wear.datastore.DataStoreType.LOGIN import com.woocommerce.android.wear.datastore.DataStoreType.ORDERS @@ -28,14 +25,9 @@ class DataStoreModule { @DataStoreQualifier(LOGIN) fun provideLoginDataStore( appContext: Context, - crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(LOGIN.typeName) }, - corruptionHandler = ReplaceFileCorruptionHandler { - crashLogging.recordEvent("Corrupted data store: Wear ${LOGIN.typeName}") - emptyPreferences() - }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -44,14 +36,9 @@ class DataStoreModule { @DataStoreQualifier(STATS) fun provideStatsDataStore( appContext: Context, - crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(STATS.typeName) }, - corruptionHandler = ReplaceFileCorruptionHandler { - crashLogging.recordEvent("Corrupted data store: Wear ${STATS.typeName}") - emptyPreferences() - }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) @@ -60,14 +47,9 @@ class DataStoreModule { @DataStoreQualifier(ORDERS) fun provideOrdersDataStore( appContext: Context, - crashLogging: CrashLogging, @AppCoroutineScope appCoroutineScope: CoroutineScope ): DataStore = PreferenceDataStoreFactory.create( produceFile = { appContext.preferencesDataStoreFile(ORDERS.typeName) }, - corruptionHandler = ReplaceFileCorruptionHandler { - crashLogging.recordEvent("Corrupted data store: Wear ${ORDERS.typeName}") - emptyPreferences() - }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) } From 5bfed0d07e59c5d79e20a284d4a5348ec634eefe Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 17:58:22 -0300 Subject: [PATCH 26/37] Update Dashboard corruption error message --- .../woocommerce/android/datastore/DashboardDataStoreModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt index 326d3d7b1f8..2fd5755091f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DashboardDataStoreModule.kt @@ -33,7 +33,7 @@ object DashboardDataStoreModule { appContext.dataStoreFile("dashboard_configuration_${site.id}") }, corruptionHandler = ReplaceFileCorruptionHandler { - crashLogging.recordEvent("Corrupted data store: Dashboard") + crashLogging.recordEvent("Corrupted data store. DataStore Type: DASHBOARD") DashboardDataModel.getDefaultInstance() }, scope = CoroutineScope(siteCoroutineScope.coroutineContext + Dispatchers.IO), From 74e9e8a3bcb1926166289a6b5bbd4625047a09d5 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 18:22:37 -0300 Subject: [PATCH 27/37] Add Serialization mapping to CustomPackageDTO and remove incorrect predefined field --- .../packages/networking/StorePackageDTOs.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/StorePackageDTOs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/StorePackageDTOs.kt index 9b868b3db22..a9b4260c925 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/StorePackageDTOs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/StorePackageDTOs.kt @@ -1,8 +1,9 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking +import com.google.gson.annotations.SerializedName + class SavedPackageInfoDTO { val custom: List? = null - val predefined: List? = null } class CustomPackageDTO { @@ -12,8 +13,14 @@ class CustomPackageDTO { val length: Double? = null val width: Double? = null val height: Double? = null + val type: String? = null + + @SerializedName("box_weight") val boxWeight: Double? = null + + @SerializedName("is_letter") val isLetter: Boolean? = null + + @SerializedName("is_user_defined") val isUserDefined: Boolean? = null - val type: String? = null } From 1461345e7433f6eddddf6821cde93eda00672e77 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 18:23:05 -0300 Subject: [PATCH 28/37] Add Serialization mapping to PackageStoreOptionsDTO --- .../packages/networking/PackageResponseDTOs.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/PackageResponseDTOs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/PackageResponseDTOs.kt index 8c8aad9853b..a73898eb5c9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/PackageResponseDTOs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/PackageResponseDTOs.kt @@ -1,14 +1,23 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking +import com.google.gson.annotations.SerializedName + class PackageResponse { val storeOptions: PackageStoreOptionsDTO? = null val packages: PackagesInfoDTO? = null } class PackageStoreOptionsDTO { + @SerializedName("currency_symbol") val currencySymbol: String? = null + + @SerializedName("dimension_unit") val dimensionUnit: String? = null + + @SerializedName("weight_unit") val weightUnit: String? = null + + @SerializedName("origin_country") val originCountry: String? = null } From 8459d4a3ea2190a4c577733b39c38afbe7ed804c Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 18:23:19 -0300 Subject: [PATCH 29/37] Add Serialization mapping to USPS and DHL Package DTOs --- .../packages/networking/CarrierPackageDTOs.kt | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt index 4b4af8a5ebb..886f61dca05 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt @@ -1,19 +1,33 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking +import com.google.gson.annotations.SerializedName + class CarrierPredefinedPackagesDTO { val usps: USPSPackageDTO? = null + + @SerializedName("dhlexpress") val dhlExpress: DHLPackageDTO? = null } class USPSPackageDTO { + @SerializedName("pri_flat_boxes") val flatBoxes: CarrierPackageGroupDTO? = null + + @SerializedName("pri_boxes") val boxes: CarrierPackageGroupDTO? = null + + @SerializedName("pri_express_boxes") val expressBoxes: CarrierPackageGroupDTO? = null + + @SerializedName("pri_envelopes") val envelopes: CarrierPackageGroupDTO? = null + + @SerializedName("pri_express_envelopes") val expressEnvelopes: CarrierPackageGroupDTO? = null } class DHLPackageDTO { + @SerializedName("domestic_and_international") val domesticAndInternationalPackages: CarrierPackageGroupDTO? = null } @@ -23,15 +37,31 @@ class CarrierPackageGroupDTO { } class PredefinedPackageDTO { + val id: String? = null + val name: String? = null + val dimensions: String? = null + + @SerializedName("inner_dimensions") val innerDimensions: String? = null + + @SerializedName("outer_dimensions") val outerDimensions: String? = null + + @SerializedName("box_weight") val boxWeight: Double? = null + + @SerializedName("is_flat_rate") val isFlatRate: Boolean? = null - val id: String? = null - val name: String? = null - val dimensions: String? = null + + @SerializedName("max_weight") val maxWeight: Double? = null + + @SerializedName("is_letter") val isLetter: Boolean? = null + + @SerializedName("group_id") val groupId: String? = null + + @SerializedName("can_ship_international") val canShipInternational: Boolean? = null } From fc6948bb49fe7c0dd398c1acf135f631c7f5db7a Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 18:28:11 -0300 Subject: [PATCH 30/37] Fix incorrect saved package mapping in WooShippingLabelPackageMapper --- .../packages/datasource/WooShippingLabelPackageMapper.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt index 3ea362d7fea..e2730b1ae50 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt @@ -4,13 +4,13 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource.C import com.woocommerce.android.ui.orders.wooshippinglabels.packages.datasource.CarrierType.USPS import com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking.CarrierPackageGroupDTO import com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking.CarrierPredefinedPackagesDTO +import com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking.CustomPackageDTO import com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking.PackageResponse -import com.woocommerce.android.ui.orders.wooshippinglabels.packages.networking.PredefinedPackageDTO import javax.inject.Inject class WooShippingLabelPackageMapper @Inject constructor() { operator fun invoke(response: PackageResponse): StorePackagesDAO { - val savedPackagesResponse = response.packages?.saved?.predefined ?: emptyList() + val savedPackagesResponse = response.packages?.saved?.custom ?: emptyList() return StorePackagesDAO( savedPackages = mapSavedPackages(savedPackagesResponse), @@ -18,7 +18,7 @@ class WooShippingLabelPackageMapper @Inject constructor() { ) } - private fun mapSavedPackages(savedResponse: List): List { + private fun mapSavedPackages(savedResponse: List): List { return savedResponse.map { PackageDAO( id = it.id.orEmpty(), From 5e38c78b2f0d957febfba93a35903d610816c9c0 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Tue, 3 Dec 2024 18:43:09 -0300 Subject: [PATCH 31/37] Remove error prone dimensions from CarrierPackageDTOs --- .../packages/datasource/WooShippingLabelPackageMapper.kt | 2 +- .../wooshippinglabels/packages/networking/CarrierPackageDTOs.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt index e2730b1ae50..71951ed2cb6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/datasource/WooShippingLabelPackageMapper.kt @@ -59,7 +59,7 @@ class WooShippingLabelPackageMapper @Inject constructor() { PackageDAO( id = it.id.orEmpty(), name = it.name.orEmpty(), - dimensions = it.dimensions.orEmpty(), + dimensions = it.outerDimensions.orEmpty(), isLetter = it.isLetter ?: false ) } ?: emptyList() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt index 886f61dca05..dcbe14bf809 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/packages/networking/CarrierPackageDTOs.kt @@ -39,7 +39,6 @@ class CarrierPackageGroupDTO { class PredefinedPackageDTO { val id: String? = null val name: String? = null - val dimensions: String? = null @SerializedName("inner_dimensions") val innerDimensions: String? = null From 9719ffe075cd916ef9f26ef91e8e02f5100252aa Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Wed, 4 Dec 2024 16:25:51 +0700 Subject: [PATCH 32/37] Update release notes wording --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 0c529ff9a2c..caf5c800c23 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,7 +5,7 @@ ----- - [*] "One time shipping" label in Product Subscriptions now matches its availability state correctly. [https://github.com/woocommerce/woocommerce-android/pull/13021] - [*] "One time shipping" label should not be shown in Simple product after conversion from Subscriptions product. [https://github.com/woocommerce/woocommerce-android/pull/13032] -- [*] Fixed bug related to missing Shipping card in Product details when converting from Subscriptions to Simple product. [https://github.com/woocommerce/woocommerce-android/pull/13035] +- [*] Fixed a bug related to incorrect Shipping settings in Product details when converting from Subscriptions to Simple product. [https://github.com/woocommerce/woocommerce-android/pull/13035] - [Internal] Refactored IPP Payment flow to allow customizing payment collection UI in POS [https://github.com/woocommerce/woocommerce-android/pull/13014] - [*] Blaze Campaign Intro screen now offers creating a new product if the site has no products yet [https://github.com/woocommerce/woocommerce-android/pull/13001] From 1170a5896ac9a5b5555fd56e417a20cc231a6db7 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Wed, 4 Dec 2024 16:48:59 +0700 Subject: [PATCH 33/37] Remove unneeded observe forever. --- .../android/ui/products/details/ProductDetailViewModelTest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index 693995e2bbe..2126a03c92a 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -1254,7 +1254,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { subscription = ProductTestUtils.generateProductSubscriptionDetails() ) doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() // Verify initial state has subscription data @@ -1278,7 +1277,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { subscription = null ) doReturn(simpleProduct).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() // Verify initial state has no subscription data @@ -1307,7 +1305,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { subscription = ProductTestUtils.generateProductSubscriptionDetails() ) doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() val originalSubscription = viewModel.getProduct().subscriptionDraft From a5c35e4d34fb7f3356ffb7d81adb2819d4da34cc Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Wed, 4 Dec 2024 16:54:51 +0700 Subject: [PATCH 34/37] Update existing unit tests that also doesn't need observeForever. --- .../ui/products/details/ProductDetailViewModelTest.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index 2126a03c92a..e7bff197d93 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -978,7 +978,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { doReturn( productAggregate.copy(product = productAggregate.product.copy(regularPrice = BigDecimal(99))) ).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() viewModel.updateProductDraft(sku = "E9999999") @@ -991,7 +990,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { doReturn( productAggregate.copy(product = productAggregate.product.copy(salePrice = BigDecimal(99))) ).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() viewModel.updateProductDraft(sku = "E9999999") @@ -1004,7 +1002,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { doReturn( productAggregate.copy(product = productAggregate.product.copy(regularPrice = BigDecimal(99))) ).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() viewModel.updateProductDraft(regularPrice = BigDecimal(0)) @@ -1017,7 +1014,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { doReturn( productAggregate.copy(product = productAggregate.product.copy(regularPrice = BigDecimal(99))) ).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() viewModel.updateProductDraft(salePrice = BigDecimal(0)) @@ -1030,7 +1026,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { doReturn( productAggregate.copy(product = productAggregate.product.copy(regularPrice = BigDecimal(99))) ).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() viewModel.updateProductDraft(regularPrice = null) @@ -1043,7 +1038,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { doReturn( productAggregate.copy(product = productAggregate.product.copy(regularPrice = BigDecimal(99))) ).whenever(productRepository).getProductAggregate(any()) - viewModel.productDetailViewStateData.observeForever { _, _ -> } viewModel.start() viewModel.updateProductDraft(salePrice = null) From a3f8d357df1664da8e6e63c688ba1929b0823fe5 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Wed, 4 Dec 2024 17:01:21 +0700 Subject: [PATCH 35/37] Use existing getDefaultSubscriptionDetails() function instead of making a new one. --- .../android/ui/products/ProductTestUtils.kt | 12 ------------ .../products/details/ProductDetailViewModelTest.kt | 9 +++++---- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt index a38386b1425..9da1d5c7631 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt @@ -199,16 +199,4 @@ object ProductTestUtils { return this } } - - fun generateProductSubscriptionDetails() = SubscriptionDetails( - price = BigDecimal.TEN, - period = SubscriptionPeriod.Month, - periodInterval = 1, - length = null, - signUpFee = null, - trialPeriod = null, - trialLength = null, - oneTimeShipping = true, - paymentsSyncDate = null - ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index e7bff197d93..8f0fe0ef672 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -17,6 +17,7 @@ import com.woocommerce.android.ui.blaze.IsBlazeEnabled import com.woocommerce.android.ui.customfields.CustomFieldsRepository import com.woocommerce.android.ui.media.MediaFileUploadHandler import com.woocommerce.android.ui.products.ParameterRepository +import com.woocommerce.android.ui.products.ProductHelper import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ProductTestUtils import com.woocommerce.android.ui.products.ProductType @@ -1245,7 +1246,7 @@ class ProductDetailViewModelTest : BaseUnitTest() { product = productAggregate.product.copy( type = ProductType.SUBSCRIPTION.value ), - subscription = ProductTestUtils.generateProductSubscriptionDetails() + subscription = ProductHelper.getDefaultSubscriptionDetails() ) doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) viewModel.start() @@ -1283,9 +1284,9 @@ class ProductDetailViewModelTest : BaseUnitTest() { viewModel.getProduct().subscriptionDraft?.let { Assertions.assertThat(it.price).isEqualTo(simpleProduct.product.regularPrice) Assertions.assertThat(it.period) - .isEqualTo(ProductTestUtils.generateProductSubscriptionDetails().period) + .isEqualTo(ProductHelper.getDefaultSubscriptionDetails().period) Assertions.assertThat(it.periodInterval) - .isEqualTo(ProductTestUtils.generateProductSubscriptionDetails().periodInterval) + .isEqualTo(ProductHelper.getDefaultSubscriptionDetails().periodInterval) } ?: Assertions.fail("Subscription draft should not be null") } @@ -1296,7 +1297,7 @@ class ProductDetailViewModelTest : BaseUnitTest() { product = productAggregate.product.copy( type = ProductType.SUBSCRIPTION.value ), - subscription = ProductTestUtils.generateProductSubscriptionDetails() + subscription = ProductHelper.getDefaultSubscriptionDetails() ) doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) viewModel.start() From 7f37411d983501485d84bd2dfa222ac8cef9df4d Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Wed, 4 Dec 2024 17:09:47 +0700 Subject: [PATCH 36/37] Detekt fix --- .../com/woocommerce/android/ui/products/ProductTestUtils.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt index 9da1d5c7631..ad18e4a54fe 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt @@ -5,13 +5,10 @@ import com.woocommerce.android.model.ProductAttribute import com.woocommerce.android.model.ProductCategory import com.woocommerce.android.model.ProductTag import com.woocommerce.android.model.ProductVariation -import com.woocommerce.android.model.SubscriptionDetails -import com.woocommerce.android.model.SubscriptionPeriod import com.woocommerce.android.model.toAppModel import com.woocommerce.android.ui.products.ProductStatus.DRAFT import org.wordpress.android.fluxc.model.WCProductModel import org.wordpress.android.fluxc.model.WCProductVariationModel -import java.math.BigDecimal import java.sql.Date import java.time.Instant From 79b82c6e1cba1706407290d0f9327ff54f012ca2 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Wed, 4 Dec 2024 05:55:54 -0500 Subject: [PATCH 37/37] Bump version number --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index 5dbfc506144..28df1322e87 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -versionName=21.2-rc-2 -versionCode=629 \ No newline at end of file +versionName=21.2-rc-3 +versionCode=630 \ No newline at end of file