From 63c1d9543f53e6ec4ac7dee55dc316e56d9050a9 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Sep 2024 19:13:10 +0200 Subject: [PATCH 01/60] Switch to FetchProductBySKU use case in OrderCreateEditViewModel --- .../creation/OrderCreateEditViewModel.kt | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index 26d8caf3773..c7a501d2660 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.orders.creation +import com.woocommerce.android.model.Product as ModelProduct import android.os.Parcelable import android.view.View import androidx.annotation.StringRes @@ -138,7 +139,7 @@ import com.woocommerce.android.ui.products.ParameterRepository import com.woocommerce.android.ui.products.ProductRestriction import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ProductType -import com.woocommerce.android.ui.products.list.ProductListRepository +import com.woocommerce.android.ui.products.inventory.FetchProductBySKU import com.woocommerce.android.ui.products.selector.ProductSelectorViewModel.SelectedItem import com.woocommerce.android.ui.products.selector.ProductSelectorViewModel.SelectedItem.Product import com.woocommerce.android.ui.products.selector.variationIds @@ -177,13 +178,11 @@ import kotlinx.coroutines.withContext import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType -import org.wordpress.android.fluxc.store.WCProductStore import org.wordpress.android.fluxc.store.WooCommerceStore.WooPlugin.WOO_GIFT_CARDS import org.wordpress.android.fluxc.utils.putIfNotNull import java.math.BigDecimal import java.util.Date import javax.inject.Inject -import com.woocommerce.android.model.Product as ModelProduct @HiltViewModel @Suppress("LargeClass") @@ -195,7 +194,6 @@ class OrderCreateEditViewModel @Inject constructor( private val orderCreationProductMapper: OrderCreationProductMapper, private val createOrderItem: CreateOrderItem, private val tracker: AnalyticsTrackerWrapper, - private val productRepository: ProductListRepository, private val checkDigitRemoverFactory: CheckDigitRemoverFactory, private val barcodeScanningTracker: BarcodeScanningTracker, private val resourceProvider: ResourceProvider, @@ -211,6 +209,7 @@ class OrderCreateEditViewModel @Inject constructor( private val currencySymbolFinder: CurrencySymbolFinder, private val totalsHelper: OrderCreateEditTotalsHelper, private val feedbackRepository: FeedbackRepository, + private val fetchProductBySKU: FetchProductBySKU, dateUtils: DateUtils, autoSyncOrder: AutoSyncOrder, autoSyncPriceModifier: AutoSyncPriceModifier, @@ -919,12 +918,20 @@ class OrderCreateEditViewModel @Inject constructor( } }.orEmpty() viewModelScope.launch { - productRepository.searchProductList( - searchQuery = barcodeOptions.sku, - skuSearchOptions = WCProductStore.SkuSearchOptions.ExactSearch, - )?.let { products -> - handleFetchProductBySKUSuccess(products, selectedItems, source, barcodeOptions) - } ?: run { + val result = fetchProductBySKU(barcodeOptions.sku, barcodeOptions.barcodeFormat) + if (result.isSuccess) { + val product = result.getOrNull() + if (product != null) { + handleFetchProductBySKUSuccess( + product, + selectedItems, + source, + barcodeOptions + ) + } else { + handleFetchProductBySKUEmpty(barcodeOptions, source) + } + } else { handleFetchProductBySKUFailure( source, barcodeOptions, @@ -935,17 +942,13 @@ class OrderCreateEditViewModel @Inject constructor( } private fun handleFetchProductBySKUSuccess( - products: List, + product: com.woocommerce.android.model.Product, selectedItems: List, source: ScanningSource, barcodeOptions: BarcodeOptions ) { viewState = viewState.copy(isUpdatingOrderDraft = false) - products.firstOrNull()?.let { product -> - addScannedProduct(product, selectedItems, source, barcodeOptions.barcodeFormat) - } ?: run { - handleFetchProductBySKUEmpty(barcodeOptions, source) - } + addScannedProduct(product, selectedItems, source, barcodeOptions.barcodeFormat) } private fun handleFetchProductBySKUEmpty( From 53d179800e63348f69fbbfb09f2d62d11944aac2 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Sep 2024 17:23:16 +0200 Subject: [PATCH 02/60] Remove check digit processing from VM It's handled in the `FetchProductBySKU` use case --- .../creation/OrderCreateEditViewModel.kt | 41 +++---------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index c7a501d2660..ac6ec3b42ec 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -1,6 +1,5 @@ package com.woocommerce.android.ui.orders.creation -import com.woocommerce.android.model.Product as ModelProduct import android.os.Parcelable import android.view.View import androidx.annotation.StringRes @@ -183,6 +182,7 @@ import org.wordpress.android.fluxc.utils.putIfNotNull import java.math.BigDecimal import java.util.Date import javax.inject.Inject +import com.woocommerce.android.model.Product as ModelProduct @HiltViewModel @Suppress("LargeClass") @@ -194,7 +194,6 @@ class OrderCreateEditViewModel @Inject constructor( private val orderCreationProductMapper: OrderCreationProductMapper, private val createOrderItem: CreateOrderItem, private val tracker: AnalyticsTrackerWrapper, - private val checkDigitRemoverFactory: CheckDigitRemoverFactory, private val barcodeScanningTracker: BarcodeScanningTracker, private val resourceProvider: ResourceProvider, private val productRestrictions: OrderCreationProductRestrictions, @@ -955,15 +954,11 @@ class OrderCreateEditViewModel @Inject constructor( barcodeOptions: BarcodeOptions, source: ScanningSource ) { - if (shouldWeRetryProductSearchByRemovingTheCheckDigitFor(barcodeOptions)) { - fetchProductBySKURemovingCheckDigit(barcodeOptions) - } else { - handleFetchProductBySKUFailure( - source, - barcodeOptions, - "Empty data response (no product found for the SKU)" - ) - } + handleFetchProductBySKUFailure( + source, + barcodeOptions, + "Empty data response (no product found for the SKU)" + ) } private fun handleFetchProductBySKUFailure( @@ -981,30 +976,6 @@ class OrderCreateEditViewModel @Inject constructor( ) } - private fun fetchProductBySKURemovingCheckDigit(barcodeOptions: BarcodeOptions) { - viewState = viewState.copy(isUpdatingOrderDraft = true) - fetchProductBySKU( - barcodeOptions.copy( - sku = checkDigitRemoverFactory.getCheckDigitRemoverFor( - barcodeOptions.barcodeFormat - ).getSKUWithoutCheckDigit(barcodeOptions.sku), - shouldHandleCheckDigitOnFailure = false - ) - ) - } - - private fun shouldWeRetryProductSearchByRemovingTheCheckDigitFor(barcodeOptions: BarcodeOptions) = - (isBarcodeFormatUPC(barcodeOptions) || isBarcodeFormatEAN(barcodeOptions)) && - barcodeOptions.shouldHandleCheckDigitOnFailure - - private fun isBarcodeFormatUPC(barcodeOptions: BarcodeOptions) = - barcodeOptions.barcodeFormat == BarcodeFormat.FormatUPCA || - barcodeOptions.barcodeFormat == BarcodeFormat.FormatUPCE - - private fun isBarcodeFormatEAN(barcodeOptions: BarcodeOptions) = - barcodeOptions.barcodeFormat == BarcodeFormat.FormatEAN13 || - barcodeOptions.barcodeFormat == BarcodeFormat.FormatEAN8 - @Suppress("LongMethod", "ReturnCount") private fun addScannedProduct( product: ModelProduct, From 8916375ed0871b07ee301045281eb85f046b63fe Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Sep 2024 17:26:27 +0200 Subject: [PATCH 03/60] Update unit tests --- ...tionFocusedOrderCreateEditViewModelTest.kt | 76 +- .../creation/UnifiedOrderEditViewModelTest.kt | 840 ++---------------- 2 files changed, 116 insertions(+), 800 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt index bb5ad98b620..49da1035c05 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt @@ -78,7 +78,6 @@ import org.wordpress.android.fluxc.network.BaseRequest import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.CoreOrderStatus -import org.wordpress.android.fluxc.store.WCProductStore import java.math.BigDecimal import java.util.Date import java.util.function.Consumer @@ -1291,9 +1290,9 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes createSut(navArgs) - verify(productListRepository).searchProductList( + verify(fetchProductBySKU).invoke( "123", - WCProductStore.SkuSearchOptions.ExactSearch + BarcodeFormat.FormatUPCA, ) } } @@ -1348,9 +1347,9 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes createSut(navArgs) - verify(productListRepository, never()).searchProductList( - "123", - WCProductStore.SkuSearchOptions.ExactSearch + verify(fetchProductBySKU, never()).invoke( + eq("123"), + any() ) } } @@ -1374,12 +1373,12 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes ) ) whenever( - productListRepository.searchProductList( + fetchProductBySKU.invoke( "12345", - WCProductStore.SkuSearchOptions.ExactSearch + BarcodeFormat.FormatUPCA, ) ).thenReturn( - listOf( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -1419,11 +1418,13 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes ) ) whenever( - productListRepository.searchProductList( + fetchProductBySKU.invoke( "12345", - WCProductStore.SkuSearchOptions.ExactSearch + BarcodeFormat.FormatUPCA, ) - ).thenReturn(null) + ).thenReturn( + Result.failure(Exception()) + ) createSut(navArgs) @@ -1438,44 +1439,6 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes } } - @Test - fun `given scanning initiated from the order list screen, when product search via sku succeeds but contains no product, then track event with proper source`() { - testBlocking { - val navArgs = OrderCreateEditFormFragmentArgs( - Creation(), - "12345", - BarcodeFormat.FormatQRCode, - ).toSavedStateHandle() - whenever(parameterRepository.getParameters("parameters_key", navArgs)).thenReturn( - SiteParameters( - currencyCode = "", - currencySymbol = null, - currencyFormattingParameters = null, - weightUnit = null, - dimensionUnit = null, - gmtOffset = 0F - ) - ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - createSut(navArgs) - - verify(tracker).track( - AnalyticsEvent.PRODUCT_SEARCH_VIA_SKU_FAILURE, - mapOf( - AnalyticsTracker.KEY_SCANNING_SOURCE to "order_list", - KEY_SCANNING_BARCODE_FORMAT to BarcodeFormat.FormatQRCode.formatName, - KEY_SCANNING_FAILURE_REASON to "Empty data response (no product found for the SKU)" - ) - ) - } - } - @Test fun `given variable product from order list screen, when product added via scanning, then track correct source`() { testBlocking { @@ -1495,12 +1458,12 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes ) ) whenever( - productListRepository.searchProductList( + fetchProductBySKU.invoke( "12345", - WCProductStore.SkuSearchOptions.ExactSearch + BarcodeFormat.FormatUPCA, ) ).thenReturn( - listOf( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -1543,13 +1506,14 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes gmtOffset = 0F ) ) + whenever( - productListRepository.searchProductList( + fetchProductBySKU.invoke( "12345", - WCProductStore.SkuSearchOptions.ExactSearch + BarcodeFormat.FormatUPCA, ) ).thenReturn( - listOf( + Result.success( ProductTestUtils.generateProduct( productId = 10L, ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt index d465f5dba99..7b886f24709 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt @@ -46,7 +46,7 @@ import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ProductStockStatus import com.woocommerce.android.ui.products.ProductTestUtils import com.woocommerce.android.ui.products.ProductType -import com.woocommerce.android.ui.products.list.ProductListRepository +import com.woocommerce.android.ui.products.inventory.FetchProductBySKU import com.woocommerce.android.ui.products.models.SiteParameters import com.woocommerce.android.ui.products.selector.ProductSelectorViewModel import com.woocommerce.android.util.captureValues @@ -72,7 +72,6 @@ import org.mockito.kotlin.whenever import org.wordpress.android.fluxc.network.BaseRequest import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType -import org.wordpress.android.fluxc.store.WCProductStore import java.math.BigDecimal import java.util.Date import kotlin.test.assertFalse @@ -94,7 +93,6 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { protected lateinit var tracker: AnalyticsTrackerWrapper protected lateinit var resourceProvider: ResourceProvider private lateinit var barcodeScanningTracker: BarcodeScanningTracker - private lateinit var checkDigitRemoverFactory: CheckDigitRemoverFactory private lateinit var productRestrictions: OrderCreationProductRestrictions private lateinit var taxRateToAddress: GetAddressFromTaxRate private lateinit var getAutoTaxRateSetting: GetAutoTaxRateSetting @@ -102,12 +100,12 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { private lateinit var getTaxRateLabel: GetTaxRateLabel private lateinit var prefs: AppPrefs lateinit var selectedSite: SelectedSite - lateinit var productListRepository: ProductListRepository val currencySymbolFinder: CurrencySymbolFinder = mock() private lateinit var mapFeeLineToCustomAmountUiModel: MapFeeLineToCustomAmountUiModel protected lateinit var totalsHelper: OrderCreateEditTotalsHelper private lateinit var getShippingMethodsWithOtherValue: GetShippingMethodsWithOtherValue protected lateinit var feedbackRepository: FeedbackRepository + protected lateinit var fetchProductBySKU: FetchProductBySKU protected val defaultOrderValue = Order.getEmptyOrder(Date(), Date()).copy(id = 123) @@ -177,8 +175,6 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { } tracker = mock() barcodeScanningTracker = mock() - checkDigitRemoverFactory = mock() - productListRepository = mock() resourceProvider = mock { on { getString(R.string.order_creation_barcode_scanning_scanning_failed) @@ -198,6 +194,7 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { totalsHelper = mock() getShippingMethodsWithOtherValue = mock() feedbackRepository = mock() + fetchProductBySKU = mock() } protected abstract val tracksFlow: String @@ -585,13 +582,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - ProductTestUtils.generateProductList() + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(ProductTestUtils.generateProduct()) ) var isUpdatingOrderDraft: Boolean? = null sut.viewStateData.observeForever { _, viewState -> @@ -609,13 +601,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -642,13 +629,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -679,12 +661,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { productId = 10L, customStatus = ProductStatus.PENDING.name ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(createOrderItemUseCase.invoke(10L)).thenReturn( createOrderItem(10L) ) @@ -705,13 +684,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, customStatus = ProductStatus.PUBLISH.name, @@ -743,12 +717,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { customStatus = ProductStatus.PUBLISH.name, amount = "" ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(createOrderItemUseCase.invoke(10L)).thenReturn( createOrderItem(10L) ) @@ -780,12 +751,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { productId = 10L, customStatus = ProductStatus.PENDING.name ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -810,12 +778,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { productId = 10L, customStatus = ProductStatus.PENDING.name ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -844,12 +809,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { productId = 10L, customStatus = ProductStatus.PENDING.name ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -872,12 +834,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { productId = 10L, customStatus = ProductStatus.PENDING.name ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -908,12 +867,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { customStatus = ProductStatus.PUBLISH.name, amount = "" ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -943,12 +899,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { customStatus = ProductStatus.PUBLISH.name, amount = "" ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -972,12 +925,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { customStatus = ProductStatus.PUBLISH.name, amount = "" ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1002,12 +952,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { productId = 10L, customStatus = ProductStatus.PENDING.name, ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1029,12 +976,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { customStatus = ProductStatus.PUBLISH.name, amount = "" ) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(listOf(product)) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success(product) + ) whenever(productRestrictions.isProductRestricted(product)).thenReturn(true) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1056,13 +1000,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { ) createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 0L, @@ -1091,13 +1030,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { ) createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 0L, @@ -1123,13 +1057,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 0L, @@ -1154,13 +1083,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -1193,13 +1117,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { .thenReturn("You cannot add variable product directly. Please select a specific variation") createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 0L, @@ -1224,13 +1143,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { .thenReturn("You cannot add variable product directly. Please select a specific variation") createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 0L, @@ -1302,12 +1216,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { .thenReturn("Product with SKU $skuCode not found. Unable to add to the order") createSut() val scannedStatus = CodeScannerStatus.Success(skuCode, BarcodeFormat.FormatQRCode) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatQRCode)).thenReturn( + Result.failure(Exception()) + ) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1328,12 +1239,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { .thenReturn("Product with SKU $skuCode not found. Unable to add to the order") createSut() val scannedStatus = CodeScannerStatus.Success(skuCode, BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - skuCode, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(null) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.failure(Exception()) + ) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1356,12 +1264,12 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { .thenReturn("Product with SKU $skuCode not found. Unable to add to the order") createSut() val scannedStatus = CodeScannerStatus.Success(skuCode, BarcodeFormat.FormatUPCA) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.failure(Exception()) + ) whenever( - productListRepository.searchProductList( - skuCode, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(null) + fetchProductBySKU.invoke(skuCode, BarcodeFormat.FormatUPCA) + ).thenReturn(Result.failure(Exception())) sut.handleBarcodeScannedStatus(scannedStatus) (sut.event.value as OnAddingProductViaScanningFailed).retry.onClick(mock()) @@ -1375,13 +1283,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -1414,13 +1317,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, ) @@ -1537,13 +1435,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -1551,7 +1444,6 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { ) ) ) - sut.handleBarcodeScannedStatus(scannedStatus) verify(tracker).track( @@ -1566,13 +1458,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -1598,13 +1485,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(null) - + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.failure(Exception()) + ) sut.handleBarcodeScannedStatus(scannedStatus) verify(tracker).track( @@ -1619,12 +1502,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(null) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.failure(Exception()) + ) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1644,12 +1524,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(null) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.failure(Exception()) + ) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1674,13 +1551,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { ).thenReturn("You cannot add variable product directly. Please select a specific variation") createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 0L, @@ -1708,12 +1580,9 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatQRCode) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatQRCode)).thenReturn( + Result.failure(Exception()) + ) sut.handleBarcodeScannedStatus(scannedStatus) @@ -1722,7 +1591,7 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { mapOf( KEY_SCANNING_SOURCE to "order_creation", KEY_SCANNING_BARCODE_FORMAT to BarcodeFormat.FormatQRCode.formatName, - KEY_SCANNING_FAILURE_REASON to "Empty data response (no product found for the SKU)" + KEY_SCANNING_FAILURE_REASON to "Product search via SKU API call failed" ) ) } @@ -1733,13 +1602,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, parentID = 1L, @@ -1769,13 +1633,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { testBlocking { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - whenever( - productListRepository.searchProductList( - "12345", - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( + whenever(fetchProductBySKU.invoke("12345", BarcodeFormat.FormatUPCA)).thenReturn( + Result.success( ProductTestUtils.generateProduct( productId = 10L, ) @@ -1798,512 +1657,6 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { } } - @Test - fun `given UPC SKU with check digit, when product search fails, then retry product search call by removing the check digit`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockUPCCheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatUPCA) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(any()) - ).thenReturn( - mockUPCCheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(productListRepository).searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - } - } - - @Test - fun `given EAN-13 SKU with check digit, when product search fails, then retry product search call by removing the check digit`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN13CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN13) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN13) - ).thenReturn( - mockEAN13CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(productListRepository).searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - } - } - - @Test - fun `given EAN-8 SKU with check digit, when product search fails, then retry product search call by removing the check digit`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN8CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN8) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN8) - ).thenReturn( - mockEAN8CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(productListRepository).searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - } - } - - @Test - fun `given product search fails for UPC barcode format, when retrying, then show a loading indicator`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockUPCCheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatUPCA) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(any()) - ).thenReturn( - mockUPCCheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - var isUpdatingOrderDraft: Boolean? = null - sut.viewStateData.observeForever { _, viewState -> - isUpdatingOrderDraft = viewState.isUpdatingOrderDraft - } - - sut.handleBarcodeScannedStatus(scannedStatus) - - assertTrue(isUpdatingOrderDraft!!) - } - } - - @Test - fun `given product search fails for UPC barcode format, when retrying, then do not handle the check digit on failing to fetch product information second time`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockUPCCheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatUPCA) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(any()) - ).thenReturn( - mockUPCCheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(checkDigitRemoverFactory, times(1)).getCheckDigitRemoverFor(any()) - verify(productListRepository, times(1)).searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - } - } - - @Test - fun `given product search fails for EAN-13 barcode format, when retrying, then do not handle the check digit on failing to fetch product information second time`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN13CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN13) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN13) - ).thenReturn( - mockEAN13CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(checkDigitRemoverFactory, times(1)).getCheckDigitRemoverFor(BarcodeFormat.FormatEAN13) - verify(productListRepository, times(1)).searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - } - } - - @Test - fun `given product search fails for EAN-8 barcode format, when retrying, then do not handle the check digit on failing to fetch product information second time`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN8CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN8) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN8) - ).thenReturn( - mockEAN8CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(checkDigitRemoverFactory, times(1)).getCheckDigitRemoverFor(BarcodeFormat.FormatEAN8) - verify(productListRepository, times(1)).searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - } - } - - @Test - fun `given product search fails for UPC barcode format, when retrying, then do not track any failure event`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockUPCCheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatUPCA) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(any()) - ).thenReturn( - mockUPCCheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( - ProductTestUtils.generateProduct(1L) - ) - ) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(tracker, never()).track( - eq(PRODUCT_SEARCH_VIA_SKU_FAILURE), - any() - ) - } - } - - @Test - fun `given product search fails for EAN-13 barcode format, when retrying, then do not track any failure event`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN13CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN13) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN13) - ).thenReturn( - mockEAN13CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( - ProductTestUtils.generateProduct(1L) - ) - ) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(tracker, never()).track( - eq(PRODUCT_SEARCH_VIA_SKU_FAILURE), - any() - ) - } - } - - @Test - fun `given product search fails for EAN-8 barcode format, when retrying, then do not track any failure event`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN8CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN8) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN8) - ).thenReturn( - mockEAN8CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( - ProductTestUtils.generateProduct(1L) - ) - ) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(tracker, never()).track( - eq(PRODUCT_SEARCH_VIA_SKU_FAILURE), - any() - ) - } - } - - @Test - fun `given product search fails for UPC barcode format, when retrying, then do not trigger failure event`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockUPCCheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatUPCA) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(any()) - ).thenReturn( - mockUPCCheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( - ProductTestUtils.generateProduct(1L) - ) - ) - - sut.handleBarcodeScannedStatus(scannedStatus) - - assertThat(sut.event.value).isNull() - } - } - - @Test - fun `given product search fails for EAN-13 barcode format, when retrying, then do not trigger failure event`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN13CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN13) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN13) - ).thenReturn( - mockEAN13CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( - ProductTestUtils.generateProduct(1L) - ) - ) - - sut.handleBarcodeScannedStatus(scannedStatus) - - assertThat(sut.event.value).isNull() - } - } - - @Test - fun `given product search fails for EAN-8 barcode format, when retrying, then do not trigger failure event`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - val mockEAN8CheckDigitRemover = mock { - on { getSKUWithoutCheckDigit(sku) }.thenReturn(skuWithCheckDigitRemoved) - } - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatEAN8) - whenever( - checkDigitRemoverFactory.getCheckDigitRemoverFor(BarcodeFormat.FormatEAN8) - ).thenReturn( - mockEAN8CheckDigitRemover - ) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - whenever( - productListRepository.searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn( - listOf( - ProductTestUtils.generateProduct(1L) - ) - ) - - sut.handleBarcodeScannedStatus(scannedStatus) - - assertThat(sut.event.value).isNull() - } - } - - @Test - fun `given product search fails for non UPC barcode format, then do not do any checksum operation`() { - testBlocking { - val sku = "12345678901" - val skuWithCheckDigitRemoved = "1234567890" - createSut() - val scannedStatus = CodeScannerStatus.Success(sku, BarcodeFormat.FormatQRCode) - whenever( - productListRepository.searchProductList( - sku, - WCProductStore.SkuSearchOptions.ExactSearch - ) - ).thenReturn(emptyList()) - - sut.handleBarcodeScannedStatus(scannedStatus) - - verify(checkDigitRemoverFactory, never()).getCheckDigitRemoverFor(any()) - verify(productListRepository, never()).searchProductList( - skuWithCheckDigitRemoved, - WCProductStore.SkuSearchOptions.ExactSearch - ) - } - } - @Test fun `check that products are ordered by product id to prevent bundle items from switching positions`() { var productsIds: List? = null @@ -2772,8 +2125,6 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { autoSyncPriceModifier = autoSyncPriceModifier, tracker = tracker, barcodeScanningTracker = barcodeScanningTracker, - productRepository = productListRepository, - checkDigitRemoverFactory = checkDigitRemoverFactory, resourceProvider = resourceProvider, productRestrictions = productRestrictions, getTaxRatesInfoDialogState = GetTaxRatesInfoDialogViewState( @@ -2793,7 +2144,8 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { totalsHelper = totalsHelper, dateUtils = mock(), getShippingMethodsWithOtherValue = getShippingMethodsWithOtherValue, - feedbackRepository = feedbackRepository + feedbackRepository = feedbackRepository, + fetchProductBySKU = fetchProductBySKU, ) } From e99d582f00351747f3c7cc62ea58be3a686c26f2 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Sep 2024 19:07:23 +0200 Subject: [PATCH 04/60] Update draft update loading state after product fetching completes --- .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index ac6ec3b42ec..ffef03d6e10 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -897,6 +897,7 @@ class OrderCreateEditViewModel @Inject constructor( barcodeFormat = status.format ) ) + viewState = viewState.copy(isUpdatingOrderDraft = false) } CodeScannerStatus.NotFound -> { From 055b0cfe8a7a7edd7c95f7c7fb3bc92403792a88 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Sep 2024 20:21:43 +0200 Subject: [PATCH 05/60] Update test --- .../ui/orders/creation/UnifiedOrderEditViewModelTest.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt index 7b886f24709..9a4666b783b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt @@ -567,14 +567,16 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { fun `when scan succeeds, then set isUpdatingOrderDraft to true`() { createSut() val scannedStatus = CodeScannerStatus.Success("12345", BarcodeFormat.FormatUPCA) - var isUpdatingOrderDraft: Boolean? = null + var isUpdatingOrderDraft: List = emptyList() sut.viewStateData.observeForever { _, viewState -> - isUpdatingOrderDraft = viewState.isUpdatingOrderDraft + isUpdatingOrderDraft += viewState.isUpdatingOrderDraft } sut.handleBarcodeScannedStatus(scannedStatus) - assertTrue(isUpdatingOrderDraft!!) + assertFalse(isUpdatingOrderDraft[0]) + assertTrue(isUpdatingOrderDraft[1]) + assertFalse(isUpdatingOrderDraft[2]) } @Test From 1e3d3cbd523e5b917515e6fbe664d23e8eb852be Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 10:35:51 +0100 Subject: [PATCH 06/60] Show buttons to allow copying key and value --- .../editor/CustomFieldsEditorScreen.kt | 44 ++++++++++++------- .../editor/CustomFieldsEditorViewModel.kt | 8 ++++ WooCommerce/src/main/res/values/strings.xml | 2 + 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt index 4c199f8dfc7..1670091a063 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt @@ -36,6 +36,8 @@ fun CustomFieldsEditorScreen(viewModel: CustomFieldsEditorViewModel) { onValueChanged = viewModel::onValueChanged, onDoneClicked = viewModel::onDoneClicked, onDeleteClicked = viewModel::onDeleteClicked, + onCopyKeyClicked = viewModel::onCopyKeyClicked, + onCopyValueClicked = viewModel::onCopyValueClicked, onBackButtonClick = viewModel::onBackClick, ) } @@ -48,6 +50,8 @@ private fun CustomFieldsEditorScreen( onValueChanged: (String) -> Unit, onDoneClicked: () -> Unit, onDeleteClicked: () -> Unit, + onCopyKeyClicked: () -> Unit, + onCopyValueClicked: () -> Unit, onBackButtonClick: () -> Unit, ) { BackHandler { onBackButtonClick() } @@ -64,24 +68,28 @@ private fun CustomFieldsEditorScreen( text = stringResource(R.string.done) ) } - if (!state.isCreatingNewItem) { - WCOverflowMenu( - items = listOf(R.string.delete), - mapper = { stringResource(it) }, - itemColor = { - when (it) { - R.string.delete -> MaterialTheme.colors.error - else -> LocalContentColor.current - } - }, - onSelected = { resourceId -> - when (resourceId) { - R.string.delete -> onDeleteClicked() - else -> error("Unhandled menu item") - } + WCOverflowMenu( + items = listOfNotNull( + R.string.custom_fields_editor_copy_key, + R.string.custom_fields_editor_copy_value, + if (!state.isCreatingNewItem) R.string.delete else null, + ), + mapper = { stringResource(it) }, + itemColor = { + when (it) { + R.string.delete -> MaterialTheme.colors.error + else -> LocalContentColor.current } - ) - } + }, + onSelected = { resourceId -> + when (resourceId) { + R.string.delete -> onDeleteClicked() + R.string.custom_fields_editor_copy_key -> onCopyKeyClicked() + R.string.custom_fields_editor_copy_value -> onCopyValueClicked() + else -> error("Unhandled menu item") + } + } + ) } ) }, @@ -140,6 +148,8 @@ private fun CustomFieldsEditorScreenPreview() { onValueChanged = {}, onDoneClicked = {}, onDeleteClicked = {}, + onCopyKeyClicked = {}, + onCopyValueClicked = {}, onBackButtonClick = {} ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt index ee4a4651cba..c5654210f27 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt @@ -109,6 +109,14 @@ class CustomFieldsEditorViewModel @Inject constructor( ) } + fun onCopyKeyClicked() { + TODO() + } + + fun onCopyValueClicked() { + TODO() + } + fun onBackClick() { if (state.value?.hasChanges == true) { showDiscardChangesDialog.value = true diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 258ccfa49dd..ad4eb969e84 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4296,4 +4296,6 @@ Value This key is already used for another custom field.\nThe app currently does not support creating duplicate keys. Please use wp-admin to duplicate a key if needed. Invalid key: please remove the \"_\" character from the beginning. + Copy Key + Copy Value From 5749b6f2396b82434865709fe662c643af92b65b Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 10:40:17 +0100 Subject: [PATCH 07/60] Add logic to handle the copy to clipboard --- .../customfields/editor/CustomFieldsEditorFragment.kt | 9 +++++++++ .../customfields/editor/CustomFieldsEditorViewModel.kt | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorFragment.kt index 9e6f40d27e9..d871b51c30a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorFragment.kt @@ -6,12 +6,15 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.R +import com.woocommerce.android.extensions.copyToClipboard import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent import dagger.hilt.android.AndroidEntryPoint +import org.wordpress.android.util.ToastUtils @AndroidEntryPoint class CustomFieldsEditorFragment : BaseFragment() { @@ -32,9 +35,15 @@ class CustomFieldsEditorFragment : BaseFragment() { private fun handleEvents() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { + is CustomFieldsEditorViewModel.CopyContentToClipboard -> copyToClipboard(event) is MultiLiveEvent.Event.ExitWithResult<*> -> navigateBackWithResult(event.key!!, event.data) MultiLiveEvent.Event.Exit -> findNavController().navigateUp() } } } + + private fun copyToClipboard(event: CustomFieldsEditorViewModel.CopyContentToClipboard) { + requireContext().copyToClipboard(getString(event.labelResource), event.content) + ToastUtils.showToast(requireContext(), R.string.copied_to_clipboard) + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt index c5654210f27..7194bf2078a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.customfields.editor +import androidx.annotation.StringRes import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope @@ -110,11 +111,11 @@ class CustomFieldsEditorViewModel @Inject constructor( } fun onCopyKeyClicked() { - TODO() + triggerEvent(CopyContentToClipboard(R.string.custom_fields_editor_key_label, customFieldDraft.value.key)) } fun onCopyValueClicked() { - TODO() + triggerEvent(CopyContentToClipboard(R.string.custom_fields_editor_value_label, customFieldDraft.value.value)) } fun onBackClick() { @@ -152,4 +153,9 @@ class CustomFieldsEditorViewModel @Inject constructor( val onDiscard: () -> Unit, val onCancel: () -> Unit ) + + data class CopyContentToClipboard( + @StringRes val labelResource: Int, + val content: String + ) : MultiLiveEvent.Event() } From 4be88f0b2c41b47c9ea1c084e7522f75c8aa1d04 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 11:07:27 +0100 Subject: [PATCH 08/60] Add unit tests --- .../editor/CustomFieldsEditorViewModelTest.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt index 5274c0dfdcd..ed838f2a466 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt @@ -246,4 +246,36 @@ class CustomFieldsEditorViewModelTest : BaseUnitTest() { .isEqualTo(UiString.UiStringRes(R.string.custom_fields_editor_key_error_underscore)) assertThat(state.showDoneButton).isFalse() } + + @Test + fun `when tapping copy key, then copy key to clipboard`() = testBlocking { + setup(editing = true) + + val event = viewModel.event.runAndCaptureValues { + viewModel.onCopyKeyClicked() + }.last() + + assertThat(event).isEqualTo( + CustomFieldsEditorViewModel.CopyContentToClipboard( + R.string.custom_fields_editor_key_label, + CUSTOM_FIELD.key + ) + ) + } + + @Test + fun `when tapping copy value, then copy value to clipboard`() = testBlocking { + setup(editing = true) + + val event = viewModel.event.runAndCaptureValues { + viewModel.onCopyValueClicked() + }.last() + + assertThat(event).isEqualTo( + CustomFieldsEditorViewModel.CopyContentToClipboard( + R.string.custom_fields_editor_value_label, + CUSTOM_FIELD.valueAsString + ) + ) + } } From 948e8ee409a47c4b99802b291fc3d32ea903abb4 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 15:44:34 +0100 Subject: [PATCH 09/60] Fix issue of clickability of the custom fields content Before this, clicks were not registered when done on the field value text --- .../customfields/list/CustomFieldsScreen.kt | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt index 5271960f845..3ee82bff0bd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.UrlAnnotation import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.woocommerce.android.R @@ -182,29 +183,38 @@ private fun CustomFieldItem( ) Spacer(modifier = Modifier.height(4.dp)) - val text = buildAnnotatedString { - if (customField.contentType != CustomFieldContentType.TEXT) { - pushUrlAnnotation(UrlAnnotation(customField.value)) - pushStyle(SpanStyle(color = MaterialTheme.colors.primary)) - } - append(customField.valueStrippedHtml) - } - ClickableText( - text = text, - style = MaterialTheme.typography.body2.copy( - color = MaterialTheme.colors.onSurface - ), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - onClick = { offset -> - text.getUrlAnnotations( - start = offset, - end = offset - ).firstOrNull()?.let { _ -> - onValueClicked(customField) + + if (customField.contentType != CustomFieldContentType.TEXT) { + val text = buildAnnotatedString { + withStyle(SpanStyle(color = MaterialTheme.colors.primary)) { + pushUrlAnnotation(UrlAnnotation(customField.value)) + append(customField.value) } } - ) + ClickableText( + text = text, + style = MaterialTheme.typography.body2.copy( + color = MaterialTheme.colors.onSurface + ), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + onClick = { offset -> + text.getUrlAnnotations( + start = offset, + end = offset + ).firstOrNull()?.let { _ -> + onValueClicked(customField) + } + } + ) + } else { + Text( + text = customField.value, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.body2 + ) + } } Icon( From ad94ecd444e9e23c6ef5335effe266065096b0be Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 16:08:52 +0100 Subject: [PATCH 10/60] Fix condition for checking for the insertion flow --- .../ui/customfields/editor/CustomFieldsEditorViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt index 7194bf2078a..6d97d48df2d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt @@ -83,7 +83,7 @@ class CustomFieldsEditorViewModel @Inject constructor( fun onDoneClicked() { val value = requireNotNull(customFieldDraft.value) - if (storedValue == null) { + if (value.id == null) { // Check for duplicate keys before inserting the new custom field // For more context: pe5sF9-33t-p2#comment-3880 launch { From 205a6840930eddabeaaa9494f4423402c47c8af8 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 16:20:37 +0100 Subject: [PATCH 11/60] Separate keys for custom field creation and update --- .../editor/CustomFieldsEditorViewModel.kt | 27 ++++++++++--------- .../customfields/list/CustomFieldsFragment.kt | 9 +++---- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt index 6d97d48df2d..94e05219d85 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt @@ -27,6 +27,7 @@ class CustomFieldsEditorViewModel @Inject constructor( private val repository: CustomFieldsRepository ) : ScopedViewModel(savedStateHandle) { companion object { + const val CUSTOM_FIELD_CREATED_RESULT_KEY = "custom_field_created" const val CUSTOM_FIELD_UPDATED_RESULT_KEY = "custom_field_updated" const val CUSTOM_FIELD_DELETED_RESULT_KEY = "custom_field_deleted" } @@ -82,25 +83,25 @@ class CustomFieldsEditorViewModel @Inject constructor( } fun onDoneClicked() { - val value = requireNotNull(customFieldDraft.value) - if (value.id == null) { - // Check for duplicate keys before inserting the new custom field - // For more context: pe5sF9-33t-p2#comment-3880 - launch { + launch { + val value = requireNotNull(customFieldDraft.value) + if (value.id == null) { + // Check for duplicate keys before inserting the new custom field + // For more context: pe5sF9-33t-p2#comment-3880 val existingFields = repository.getDisplayableCustomFields(navArgs.parentItemId) if (existingFields.any { it.key == value.key }) { keyErrorMessage.value = UiString.UiStringRes(R.string.custom_fields_editor_key_error_duplicate) - } else { - triggerEvent( - MultiLiveEvent.Event.ExitWithResult(data = value, key = CUSTOM_FIELD_UPDATED_RESULT_KEY) - ) + return@launch } } - } else { - // When editing, we don't need to check for duplicate keys - triggerEvent( + + val event = if (storedValue == null) { + MultiLiveEvent.Event.ExitWithResult(data = value, key = CUSTOM_FIELD_CREATED_RESULT_KEY) + + } else { MultiLiveEvent.Event.ExitWithResult(data = value, key = CUSTOM_FIELD_UPDATED_RESULT_KEY) - ) + } + triggerEvent(event) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt index f3a38d521f2..fa0ca769b4f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt @@ -64,12 +64,11 @@ class CustomFieldsFragment : BaseFragment() { } private fun handleResults() { + handleResult(CustomFieldsEditorViewModel.CUSTOM_FIELD_CREATED_RESULT_KEY) { result -> + viewModel.onCustomFieldInserted(result) + } handleResult(CustomFieldsEditorViewModel.CUSTOM_FIELD_UPDATED_RESULT_KEY) { result -> - if (result.id == null) { - viewModel.onCustomFieldInserted(result) - } else { - viewModel.onCustomFieldUpdated(result) - } + viewModel.onCustomFieldUpdated(result) } handleResult(CustomFieldsEditorViewModel.CUSTOM_FIELD_DELETED_RESULT_KEY) { result -> viewModel.onCustomFieldDeleted(result) From 0c8557f2a5e07e147a6e2e1fd6c17ca494171d74 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 16:23:51 +0100 Subject: [PATCH 12/60] Make sure that editing a non-saved field doesn't insert a new separate one --- .../editor/CustomFieldsEditorViewModel.kt | 13 ++++++++++++- .../ui/customfields/list/CustomFieldsFragment.kt | 5 +++-- .../ui/customfields/list/CustomFieldsViewModel.kt | 9 +++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt index 94e05219d85..b54d9262749 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.customfields.editor +import android.os.Parcelable import androidx.annotation.StringRes import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData @@ -19,6 +20,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize import javax.inject.Inject @HiltViewModel @@ -99,7 +101,10 @@ class CustomFieldsEditorViewModel @Inject constructor( MultiLiveEvent.Event.ExitWithResult(data = value, key = CUSTOM_FIELD_CREATED_RESULT_KEY) } else { - MultiLiveEvent.Event.ExitWithResult(data = value, key = CUSTOM_FIELD_UPDATED_RESULT_KEY) + MultiLiveEvent.Event.ExitWithResult( + data = CustomFieldUpdateResult(oldKey = storedValue.key, updatedField = value), + key = CUSTOM_FIELD_UPDATED_RESULT_KEY + ) } triggerEvent(event) } @@ -159,4 +164,10 @@ class CustomFieldsEditorViewModel @Inject constructor( @StringRes val labelResource: Int, val content: String ) : MultiLiveEvent.Event() + + @Parcelize + data class CustomFieldUpdateResult( + val oldKey: String, + val updatedField: CustomFieldUiModel + ) : Parcelable } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt index fa0ca769b4f..cb52225db4f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsFragment.kt @@ -15,6 +15,7 @@ import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.customfields.CustomFieldContentType import com.woocommerce.android.ui.customfields.CustomFieldUiModel import com.woocommerce.android.ui.customfields.editor.CustomFieldsEditorViewModel +import com.woocommerce.android.ui.customfields.editor.CustomFieldsEditorViewModel.CustomFieldUpdateResult import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.ChromeCustomTabUtils @@ -67,8 +68,8 @@ class CustomFieldsFragment : BaseFragment() { handleResult(CustomFieldsEditorViewModel.CUSTOM_FIELD_CREATED_RESULT_KEY) { result -> viewModel.onCustomFieldInserted(result) } - handleResult(CustomFieldsEditorViewModel.CUSTOM_FIELD_UPDATED_RESULT_KEY) { result -> - viewModel.onCustomFieldUpdated(result) + handleResult(CustomFieldsEditorViewModel.CUSTOM_FIELD_UPDATED_RESULT_KEY) { result -> + viewModel.onCustomFieldUpdated(result.oldKey, result.updatedField) } handleResult(CustomFieldsEditorViewModel.CUSTOM_FIELD_DELETED_RESULT_KEY) { result -> viewModel.onCustomFieldDeleted(result) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModel.kt index 716832cb687..99abccc7c18 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModel.kt @@ -97,9 +97,14 @@ class CustomFieldsViewModel @Inject constructor( } } - fun onCustomFieldUpdated(result: CustomFieldUiModel) { + fun onCustomFieldUpdated(oldValueKey: String, result: CustomFieldUiModel) { pendingChanges.update { - it.copy(editedFields = it.editedFields.filterNot { field -> field.id == result.id } + result) + if (result.id == null) { + // We are updating a field that was just added and hasn't been saved yet + it.copy(insertedFields = it.insertedFields.filterNot { field -> field.key == oldValueKey } + result) + } else { + it.copy(editedFields = it.editedFields.filterNot { field -> field.id == result.id } + result) + } } } From 3385bfef67eb5caaa7964ecf4c433873717dad64 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 16:33:14 +0100 Subject: [PATCH 13/60] Add and fix unit tests --- .../editor/CustomFieldsEditorViewModelTest.kt | 27 +++++++++++++++++-- .../list/CustomFieldsViewModelTest.kt | 27 ++++++++++++++++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt index ed838f2a466..59b333a3da0 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt @@ -171,7 +171,7 @@ class CustomFieldsEditorViewModelTest : BaseUnitTest() { } @Test - fun `when done is clicked, then exit with result`() = testBlocking { + fun `given editing an existing field, when done is clicked, then exit with result`() = testBlocking { setup(editing = true) val events = viewModel.event.runAndCaptureValues { @@ -182,12 +182,35 @@ class CustomFieldsEditorViewModelTest : BaseUnitTest() { assertThat(events).isEqualTo( MultiLiveEvent.Event.ExitWithResult( - data = CustomFieldUiModel(id = CUSTOM_FIELD_ID, key = "new key", value = "new value"), + data = CustomFieldsEditorViewModel.CustomFieldUpdateResult( + CUSTOM_FIELD.key, + CustomFieldUiModel(id = CUSTOM_FIELD_ID, key = "new key", value = "new value") + ), key = CustomFieldsEditorViewModel.CUSTOM_FIELD_UPDATED_RESULT_KEY ) ) } + @Test + fun `given creating a new field, when done is clicked, then exit with result`() = testBlocking { + setup(editing = false) { + whenever(repository.getDisplayableCustomFields(PARENT_ITEM_ID)).thenReturn(emptyList()) + } + + val events = viewModel.event.runAndCaptureValues { + viewModel.onKeyChanged("key") + viewModel.onValueChanged("value") + viewModel.onDoneClicked() + }.last() + + assertThat(events).isEqualTo( + MultiLiveEvent.Event.ExitWithResult( + data = CustomFieldUiModel(key = "key", value = "value"), + key = CustomFieldsEditorViewModel.CUSTOM_FIELD_CREATED_RESULT_KEY + ) + ) + } + @Test fun `given adding a new field, when key is duplicate, then show error`() = testBlocking { setup(editing = false) { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModelTest.kt index 1c3b80850d9..6d8a5f1a120 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsViewModelTest.kt @@ -217,7 +217,7 @@ class CustomFieldsViewModelTest : BaseUnitTest() { setup() val state = viewModel.state.runAndCaptureValues { - viewModel.onCustomFieldUpdated(customField) + viewModel.onCustomFieldUpdated(CUSTOM_FIELDS.first().key, customField) advanceUntilIdle() }.last() @@ -230,8 +230,8 @@ class CustomFieldsViewModelTest : BaseUnitTest() { setup() val state = viewModel.state.runAndCaptureValues { - viewModel.onCustomFieldUpdated(customField.copy(value = "new value")) - viewModel.onCustomFieldUpdated(customField.copy(value = "new value 2")) + viewModel.onCustomFieldUpdated(CUSTOM_FIELDS.first().key, customField.copy(value = "new value")) + viewModel.onCustomFieldUpdated(CUSTOM_FIELDS.first().key, customField.copy(value = "new value 2")) advanceUntilIdle() }.last() @@ -256,6 +256,25 @@ class CustomFieldsViewModelTest : BaseUnitTest() { assertThat(state.customFields.last().value).isEqualTo(customField.value) } + @Test + fun `when adding a custom field then updating it, then confirm the field is not duplicated`() = testBlocking { + val customField = CustomFieldUiModel( + key = "new key", + value = "new value" + ) + setup() + + val state = viewModel.state.runAndCaptureValues { + viewModel.onCustomFieldInserted(customField) + viewModel.onCustomFieldUpdated(customField.key, customField.copy(value = "new value 2")) + advanceUntilIdle() + }.last() + + assertThat(state.customFields).hasSize(CUSTOM_FIELDS.size + 1) + assertThat(state.customFields.last().key).isEqualTo(customField.key) + assertThat(state.customFields.last().value).isEqualTo("new value 2") + } + @Test fun `when deleting a custom field, then custom fields are refreshed`() = testBlocking { val customField = CustomFieldUiModel(CUSTOM_FIELDS.first()) @@ -325,7 +344,7 @@ class CustomFieldsViewModelTest : BaseUnitTest() { setup() - viewModel.onCustomFieldUpdated(updatedField) + viewModel.onCustomFieldUpdated(CUSTOM_FIELDS.first().key, updatedField) viewModel.onCustomFieldInserted(insertedField) viewModel.onSaveClicked() From 7de6ad072bd5010ce7386d01a60bb4b3f8461ca5 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 11 Sep 2024 16:34:04 +0100 Subject: [PATCH 14/60] Remove blank lines --- .../ui/customfields/editor/CustomFieldsEditorViewModel.kt | 1 - .../android/ui/customfields/list/CustomFieldsScreen.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt index b54d9262749..c2172cb9d29 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt @@ -99,7 +99,6 @@ class CustomFieldsEditorViewModel @Inject constructor( val event = if (storedValue == null) { MultiLiveEvent.Event.ExitWithResult(data = value, key = CUSTOM_FIELD_CREATED_RESULT_KEY) - } else { MultiLiveEvent.Event.ExitWithResult( data = CustomFieldUpdateResult(oldKey = storedValue.key, updatedField = value), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt index 3ee82bff0bd..8bb09db20f7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/list/CustomFieldsScreen.kt @@ -183,7 +183,6 @@ private fun CustomFieldItem( ) Spacer(modifier = Modifier.height(4.dp)) - if (customField.contentType != CustomFieldContentType.TEXT) { val text = buildAnnotatedString { withStyle(SpanStyle(color = MaterialTheme.colors.primary)) { From 196bfa30c412d605d223db9fc1346ca50acbae23 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Thu, 12 Sep 2024 10:24:20 +0530 Subject: [PATCH 15/60] Delete return home icon as it is not used anymore. --- .../src/main/res/drawable/woo_pos_ic_return_home.xml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 WooCommerce/src/main/res/drawable/woo_pos_ic_return_home.xml diff --git a/WooCommerce/src/main/res/drawable/woo_pos_ic_return_home.xml b/WooCommerce/src/main/res/drawable/woo_pos_ic_return_home.xml deleted file mode 100644 index ba781e979c5..00000000000 --- a/WooCommerce/src/main/res/drawable/woo_pos_ic_return_home.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - From 6499e452550acf5487fa93d631be93b9c08b9816 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Thu, 12 Sep 2024 10:25:20 +0530 Subject: [PATCH 16/60] UI changes in payment success screen. Remove outlined button and use regular button instead Change text color inside button --- .../WooPosTotalsPaymentSuccessScreen.kt | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/success/WooPosTotalsPaymentSuccessScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/success/WooPosTotalsPaymentSuccessScreen.kt index f88b2b1e474..3cf4d0dba60 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/success/WooPosTotalsPaymentSuccessScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/success/WooPosTotalsPaymentSuccessScreen.kt @@ -3,20 +3,18 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.success import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedButton import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check @@ -29,8 +27,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -125,7 +121,7 @@ fun WooPosPaymentSuccessScreen( } ) - OutlinedButton( + Button( modifier = Modifier .constrainAs(button) { bottom.linkTo(parent.bottom) @@ -135,25 +131,16 @@ fun WooPosPaymentSuccessScreen( .height(80.dp) .width(604.dp), colors = ButtonDefaults.buttonColors( - backgroundColor = Color.Transparent, - contentColor = Color.Transparent, + backgroundColor = MaterialTheme.colors.onBackground ), - onClick = onNewTransactionClicked, - border = BorderStroke(1.dp, MaterialTheme.colors.onSurface), shape = RoundedCornerShape(8.dp), + onClick = onNewTransactionClicked, ) { - Icon( - modifier = Modifier.size(24.dp), - painter = painterResource(id = R.drawable.woo_pos_ic_return_home), - tint = MaterialTheme.colors.onSurface, - contentDescription = stringResource(id = R.string.woopos_new_order_button) - ) - Spacer(modifier = Modifier.width(12.dp.toAdaptivePadding())) Text( text = stringResource(R.string.woopos_new_order_button), style = MaterialTheme.typography.h5, fontWeight = FontWeight.SemiBold, - color = WooPosTheme.colors.paymentSuccessText, + color = MaterialTheme.colors.background, textAlign = TextAlign.Center ) } From 8cd78062b3bd19d670ca287fd99842ee05242a47 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Thu, 12 Sep 2024 10:25:59 +0530 Subject: [PATCH 17/60] Add padding on the checkout screen for cart list items when checkout button is not visible --- .../ui/woopos/home/cart/WooPosCartScreen.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt index 2a21b0cfb42..532d7136864 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt @@ -143,6 +143,7 @@ private fun WooPosCartScreen( }, items = state.body.itemsInCart, areItemsRemovable = state.areItemsRemovable, + isCheckoutButtonVisible = state.isCheckoutButtonVisible, onUIEvent = onUIEvent ) } @@ -231,6 +232,7 @@ private fun CartBodyWithItems( modifier: Modifier = Modifier, items: List, areItemsRemovable: Boolean, + isCheckoutButtonVisible: Boolean, onUIEvent: (WooPosCartUIEvent) -> Unit, ) { val listState = rememberLazyListState() @@ -259,6 +261,11 @@ private fun CartBodyWithItems( onUIEvent = onUIEvent, ) } + if (!isCheckoutButtonVisible) { + item { + Spacer(modifier = Modifier.height(182.dp)) + } + } } } @@ -586,6 +593,43 @@ fun WooPosCartScreenCheckoutPreview(modifier: Modifier = Modifier) { } } +@Composable +@WooPosPreview +fun WooPosCartScreenWithoutCheckoutButtonPreview(modifier: Modifier = Modifier) { + val itemList = mutableListOf() + + repeat(7) { index -> + val item = WooPosCartState.Body.WithItems.Item( + id = WooPosCartState.Body.WithItems.Item.Id( + productId = (index + 1).toLong(), + itemNumber = index + 1 + ), + imageUrl = "", + name = "VW California", + price = "€50,000", + isAppearanceAnimationPlayed = true + ) + itemList.add(item) + } + WooPosTheme { + WooPosCartScreen( + modifier = modifier, + state = WooPosCartState( + toolbar = WooPosCartState.Toolbar( + backIconVisible = true, + itemsCount = "8 items", + isClearAllButtonVisible = true + ), + body = WooPosCartState.Body.WithItems( + itemsInCart = itemList + ), + areItemsRemovable = false, + isCheckoutButtonVisible = false + ) + ) {} + } +} + @Composable @WooPosPreview fun WooPosCartScreenEmptyPreview(modifier: Modifier = Modifier) { From 6656e283b5a729526974aada21b9b434f0426414 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Thu, 12 Sep 2024 10:53:59 +0530 Subject: [PATCH 18/60] Remove unnecessary compose preview --- .../ui/woopos/home/cart/WooPosCartScreen.kt | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt index 532d7136864..287f3d79ccf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt @@ -593,43 +593,6 @@ fun WooPosCartScreenCheckoutPreview(modifier: Modifier = Modifier) { } } -@Composable -@WooPosPreview -fun WooPosCartScreenWithoutCheckoutButtonPreview(modifier: Modifier = Modifier) { - val itemList = mutableListOf() - - repeat(7) { index -> - val item = WooPosCartState.Body.WithItems.Item( - id = WooPosCartState.Body.WithItems.Item.Id( - productId = (index + 1).toLong(), - itemNumber = index + 1 - ), - imageUrl = "", - name = "VW California", - price = "€50,000", - isAppearanceAnimationPlayed = true - ) - itemList.add(item) - } - WooPosTheme { - WooPosCartScreen( - modifier = modifier, - state = WooPosCartState( - toolbar = WooPosCartState.Toolbar( - backIconVisible = true, - itemsCount = "8 items", - isClearAllButtonVisible = true - ), - body = WooPosCartState.Body.WithItems( - itemsInCart = itemList - ), - areItemsRemovable = false, - isCheckoutButtonVisible = false - ) - ) {} - } -} - @Composable @WooPosPreview fun WooPosCartScreenEmptyPreview(modifier: Modifier = Modifier) { From fbfee38fe79db35096a797bf0b534d7590083eb3 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 12 Sep 2024 17:04:19 +0200 Subject: [PATCH 19/60] Bump ci toolkit to 3.6.0 --- .buildkite/shared-pipeline-vars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index 279aa805e58..7d7027df32e 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -3,5 +3,5 @@ # This file is `source`'d before calling `buildkite-agent pipeline upload`, and can be used # to set up some variables that will be interpolated in the `.yml` pipeline before uploading it. - export CI_TOOLKIT="automattic/a8c-ci-toolkit#3.5.1" + export CI_TOOLKIT="automattic/a8c-ci-toolkit#3.6.0" export TEST_COLLECTOR="test-collector#v1.10.1" From 55cd291b72422ee4bd66a1c4a9af90833e8755bc Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 12 Sep 2024 17:05:12 +0200 Subject: [PATCH 20/60] Send sarif file to GitHub Only for WooCommerce module, as sending more reports from the same tool causes reports override. --- .buildkite/commands/lint.sh | 10 ++++++++++ .buildkite/pipeline.yml | 5 +---- WooCommerce/build.gradle | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100755 .buildkite/commands/lint.sh diff --git a/.buildkite/commands/lint.sh b/.buildkite/commands/lint.sh new file mode 100755 index 00000000000..a7576df3445 --- /dev/null +++ b/.buildkite/commands/lint.sh @@ -0,0 +1,10 @@ +#!/bin/bash -u + +echo "--- 🧹 Linting" +cp gradle.properties-example gradle.properties +./gradlew lintJalapenoDebug +lint_exit_code=$? + +upload_sarif_to_github 'WooCommerce/build/reports/lint-results-jalapenoDebug.sarif' 'woocommerce' 'woocommerce-android' + +exit $lint_exit_code diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 2a015a8a3bf..a6cd852fc8d 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -44,10 +44,7 @@ steps: - "**/build/reports/detekt/detekt.html" - label: "lint" - command: | - echo "--- 🧹 Linting" - cp gradle.properties-example gradle.properties - ./gradlew lintJalapenoDebug + command: .buildkite/commands/lint.sh plugins: [$CI_TOOLKIT] artifact_paths: - "**/build/reports/lint-results*.*" diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 20f1c7292de..e61e9112974 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -213,6 +213,7 @@ android { } lintOptions { + sarifReport System.getenv('CI') ? true : false disable 'InvalidPackage' } From ac57e5e0ecfc26a7fb15d00ae1d8df227e13d0c4 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 12 Sep 2024 17:11:00 +0200 Subject: [PATCH 21/60] Use `checkDependencies` in lint configuration of app module This way, we combine all lint issues of app and library modules into one lint report (wear remains separate). We do this to send sarif report file to GitHub Code Scanning API with data from all modules. --- .buildkite/commands/lint.sh | 13 +++++++++++-- WooCommerce/build.gradle | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.buildkite/commands/lint.sh b/.buildkite/commands/lint.sh index a7576df3445..af7466c73d6 100755 --- a/.buildkite/commands/lint.sh +++ b/.buildkite/commands/lint.sh @@ -2,8 +2,17 @@ echo "--- 🧹 Linting" cp gradle.properties-example gradle.properties -./gradlew lintJalapenoDebug -lint_exit_code=$? +./gradlew :WooCommerce:lintJalapenoDebug +app_lint_exit_code=$? + +./gradlew :WooCommerce-Wear:lintJalapenoDebug +wear_lint_exit_code=$? + +if [ $app_lint_exit_code -ne 0 ] || [ $wear_lint_exit_code -ne 0 ]; then + lint_exit_code=1 +else + lint_exit_code=0 +fi upload_sarif_to_github 'WooCommerce/build/reports/lint-results-jalapenoDebug.sarif' 'woocommerce' 'woocommerce-android' diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index e61e9112974..a213af6668f 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -214,6 +214,7 @@ android { lintOptions { sarifReport System.getenv('CI') ? true : false + checkDependencies true disable 'InvalidPackage' } From d3593e142d77d45dd82f59df70d09a616dae2d81 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 12 Sep 2024 18:30:41 +0200 Subject: [PATCH 22/60] Bump CI toolkit to 3.6.1 To fix issue with sending sarif files on the main branch --- .buildkite/shared-pipeline-vars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index 7d7027df32e..5aaece7d56e 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -3,5 +3,5 @@ # This file is `source`'d before calling `buildkite-agent pipeline upload`, and can be used # to set up some variables that will be interpolated in the `.yml` pipeline before uploading it. - export CI_TOOLKIT="automattic/a8c-ci-toolkit#3.6.0" + export CI_TOOLKIT="automattic/a8c-ci-toolkit#3.6.1" export TEST_COLLECTOR="test-collector#v1.10.1" From cbc5ed7fc81d717926c19e495d78f8c512a10573 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 09:24:32 +0200 Subject: [PATCH 23/60] Add new action to image picker dialog to select images for given product --- .../mediapicker/MediaPickerDialogScreen.kt | 53 +++++++++++++++++++ .../ad/BlazeCampaignCreationEditAdScreen.kt | 8 ++- .../BlazeCampaignCreationEditAdViewModel.kt | 4 ++ WooCommerce/src/main/res/values/strings.xml | 1 + 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt index 9c2fed86640..b08fad6df84 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt @@ -32,6 +32,59 @@ import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.CAMERA import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.DEVICE import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.WP_MEDIA_LIBRARY +@Composable +fun MediaPickerDialogForExistingProduct( + onDismissRequest: () -> Unit, + onMediaLibraryRequested: (DataSource) -> Unit, + onProductImagesRequested: () -> Unit +) { + Dialog(onDismissRequest = onDismissRequest) { + Card( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colors.surface, MaterialTheme.shapes.medium) + .shadow(elevation = dimensionResource(id = dimen.major_75)), + ) { + Column( + modifier = Modifier + .padding(vertical = dimensionResource(id = dimen.major_100)), + ) { + Text( + modifier = Modifier + .padding( + start = dimensionResource(id = dimen.major_100), + end = dimensionResource(id = dimen.major_100), + bottom = dimensionResource(id = dimen.minor_100) + ), + text = stringResource(id = string.media_picker_dialog_title), + style = MaterialTheme.typography.h6 + ) + + DialogButton( + image = drawable.ic_gridicons_image, + title = string.image_source_device_chooser, + onClick = { onMediaLibraryRequested(DEVICE) } + ) + DialogButton( + image = drawable.ic_gridicons_camera, + title = string.image_source_device_camera, + onClick = { onMediaLibraryRequested(CAMERA) } + ) + DialogButton( + image = drawable.ic_wordpress, + title = string.image_source_wp_media_library, + onClick = { onMediaLibraryRequested(WP_MEDIA_LIBRARY) } + ) + DialogButton( + image = drawable.ic_product, + title = string.image_source_product_images, + onClick = onProductImagesRequested + ) + } + } + } +} + @Composable fun MediaPickerDialog( onDismissRequest: () -> Unit, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt index b3fdd044dd5..03a0dc43df8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt @@ -48,6 +48,7 @@ import com.woocommerce.android.R.dimen import com.woocommerce.android.R.drawable import com.woocommerce.android.R.string import com.woocommerce.android.mediapicker.MediaPickerDialog +import com.woocommerce.android.mediapicker.MediaPickerDialogForExistingProduct import com.woocommerce.android.ui.blaze.BlazeRepository.BlazeCampaignImage import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ViewState import com.woocommerce.android.ui.compose.component.Toolbar @@ -69,6 +70,7 @@ fun BlazeCampaignCreationPreviewScreen(viewModel: BlazeCampaignCreationEditAdVie onNextSuggestionTapped = viewModel::onNextSuggestionTapped, onBackButtonTapped = viewModel::onBackButtonTapped, onMediaPickerDialogDismissed = viewModel::onMediaPickerDialogDismissed, + onProductImagesRequested = viewModel::onProductImagesRequested, onMediaLibraryRequested = viewModel::onMediaLibraryRequested, onSaveTapped = viewModel::onSaveTapped ) @@ -85,13 +87,15 @@ private fun BlazeCampaignCreationEditAdScreen( onNextSuggestionTapped: () -> Unit, onBackButtonTapped: () -> Unit, onMediaPickerDialogDismissed: () -> Unit, + onProductImagesRequested: () -> Unit, onMediaLibraryRequested: (DataSource) -> Unit, onSaveTapped: () -> Unit ) { if (viewState.isMediaPickerDialogVisible) { - MediaPickerDialog( + MediaPickerDialogForExistingProduct( onMediaPickerDialogDismissed, - onMediaLibraryRequested + onMediaLibraryRequested, + onProductImagesRequested ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt index e61e33d138e..9a83c079ce8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt @@ -170,6 +170,10 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( } } + fun onProductImagesRequested() { + TODO("Not yet implemented") + } + data class ShowMediaLibrary(val source: MediaPickerSetup.DataSource) : Event() @Parcelize diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 38de67edcaa..08b85bca35e 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -2347,6 +2347,7 @@ Choose from device Take a photo WordPress media library + Choose an existing product photo Device Media Library Select Media Source WordPress media library From 82e14f49827eaaeaa9fe5884c1ea5a611a5a36bd Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 11:09:52 +0200 Subject: [PATCH 24/60] Handle taps on product image picker --- .../creation/ad/BlazeCampaignCreationEditAdFragment.kt | 6 ++++++ .../creation/ad/BlazeCampaignCreationEditAdViewModel.kt | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt index da5c5dfb273..aa93cde4972 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.EditAdResult import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ShowMediaLibrary +import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ShowProductImagePicker import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit @@ -47,6 +48,7 @@ class BlazeCampaignCreationEditAdFragment : BaseFragment(), MediaPickerResultHan when (event) { is Exit -> findNavController().popBackStack() is ShowMediaLibrary -> mediaPickerHelper.showMediaPicker(event.source) + is ShowProductImagePicker -> showProductImagePicker() is ShowDialog -> event.showDialog() is ExitWithResult<*> -> { navigateBackWithResult(EDIT_AD_RESULT, event.data as EditAdResult) @@ -55,6 +57,10 @@ class BlazeCampaignCreationEditAdFragment : BaseFragment(), MediaPickerResultHan } } + private fun showProductImagePicker() { + TODO("Not yet implemented") + } + override fun onDeviceMediaSelected(imageUris: List, source: String) { if (imageUris.isNotEmpty()) { viewModel.onLocalImageSelected(imageUris.first().toString()) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt index 9a83c079ce8..ba55b5a20cc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt @@ -171,10 +171,11 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( } fun onProductImagesRequested() { - TODO("Not yet implemented") + triggerEvent(ShowProductImagePicker(navArgs.productId)) } data class ShowMediaLibrary(val source: MediaPickerSetup.DataSource) : Event() + data class ShowProductImagePicker(val productId: Long) : Event() @Parcelize data class ViewState( From 90095e0e55464569b0346e6741f408eb66a7e28e Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 13 Sep 2024 10:54:31 +0100 Subject: [PATCH 25/60] Make sure to clear the chart if the stats are empty --- .../woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt index 28a514da900..cdd777e9f18 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt @@ -552,6 +552,7 @@ class DashboardStatsView @JvmOverloads constructor( fadeInLabelValue(ordersValue, orders) if (chartRevenueStats.isEmpty() || revenueStatsModel?.totalSales == 0.toDouble()) { + binding.chart.clear() isRequestingStats = false return } From 40b46384d60a12e8beffa7f0fae562239b6c6917 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 13 Sep 2024 10:54:40 +0100 Subject: [PATCH 26/60] Improve logic of the function Previously, we were assigning the charts value to itself, and it was making the logic harder to follow. --- .../android/ui/dashboard/stats/DashboardStatsView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt index cdd777e9f18..4903eaaa88f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/stats/DashboardStatsView.kt @@ -649,8 +649,7 @@ class DashboardStatsView @JvmOverloads constructor( } private fun generateLineDataSet(revenueStats: Map): LineDataSet { - chartRevenueStats = revenueStats - val entries = chartRevenueStats.values.mapIndexed { index, value -> + val entries = revenueStats.values.mapIndexed { index, value -> Entry((index + 1).toFloat(), value.toFloat()) } return LineDataSet(entries, "") From d2c92adb3bd0f4b415fe280e349d8faef17dabb5 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 13:08:46 +0200 Subject: [PATCH 27/60] Display product images upon tapping in new option --- .../ad/BlazeCampaignCreationEditAdFragment.kt | 10 +- .../creation/ad/ProductImagePickerFragment.kt | 103 ++++++++++++++++++ .../ad/ProductImagePickerViewModel.kt | 56 ++++++++++ .../nav_graph_blaze_campaign_creation.xml | 11 ++ 4 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt index aa93cde4972..e8d66aaef34 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt @@ -8,6 +8,7 @@ import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.extensions.navigateBackWithResult +import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.mediapicker.MediaPickerHelper import com.woocommerce.android.mediapicker.MediaPickerHelper.MediaPickerResultHandler import com.woocommerce.android.model.Product.Image @@ -48,7 +49,7 @@ class BlazeCampaignCreationEditAdFragment : BaseFragment(), MediaPickerResultHan when (event) { is Exit -> findNavController().popBackStack() is ShowMediaLibrary -> mediaPickerHelper.showMediaPicker(event.source) - is ShowProductImagePicker -> showProductImagePicker() + is ShowProductImagePicker -> navigateToProductImagePicker(event.productId) is ShowDialog -> event.showDialog() is ExitWithResult<*> -> { navigateBackWithResult(EDIT_AD_RESULT, event.data as EditAdResult) @@ -57,8 +58,11 @@ class BlazeCampaignCreationEditAdFragment : BaseFragment(), MediaPickerResultHan } } - private fun showProductImagePicker() { - TODO("Not yet implemented") + private fun navigateToProductImagePicker(productId: Long) { + findNavController().navigateSafely( + BlazeCampaignCreationEditAdFragmentDirections + .actionBlazeCampaignCreationEditAdFragmentToProductImagePickerFragment(productId) + ) } override fun onDeviceMediaSelected(imageUris: List, source: String) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt new file mode 100644 index 00000000000..71b55d66da0 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt @@ -0,0 +1,103 @@ +package com.woocommerce.android.ui.blaze.creation.ad + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.woocommerce.android.R +import com.woocommerce.android.extensions.navigateBackWithResult +import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.blaze.creation.ad.ProductImagePickerViewModel.ImageSelectedResult +import com.woocommerce.android.ui.compose.composeView +import com.woocommerce.android.ui.main.AppBarStatus +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class ProductImagePickerFragment : BaseFragment() { + companion object { + const val ON_PRODUCT_IMAGE_SELECTED = "on_product_image_selected" + } + + override val activityAppBarStatus: AppBarStatus + get() = AppBarStatus.Hidden + + val viewModel: ProductImagePickerViewModel by viewModels() + + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return composeView { + ProductImagePickerScreen(viewModel = viewModel) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + viewModel.event.observe(viewLifecycleOwner) { event -> + when (event) { + is Exit -> findNavController().popBackStack() + is ExitWithResult<*> -> { + navigateBackWithResult(ON_PRODUCT_IMAGE_SELECTED, event.data as ImageSelectedResult) + } + } + } + } + + @Composable + fun ProductImagePickerScreen(viewModel: ProductImagePickerViewModel) { + viewModel.viewState.observeAsState().value?.let { viewState -> + ProductImagePickerScreen( + viewState = viewState, + onImageSelected = viewModel::onImageSelected + ) + } + } + + @Composable + fun ProductImagePickerScreen( + viewState: ProductImagePickerViewModel.ViewState, + onImageSelected: (String) -> Unit + ) { + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 128.dp) + ) { + items(viewState.productImageUrls) { photoUrl -> + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(photoUrl) + .crossfade(true) + .build(), + fallback = painterResource(R.drawable.blaze_campaign_product_placeholder), + placeholder = painterResource(R.drawable.blaze_campaign_product_placeholder), + error = painterResource(R.drawable.blaze_campaign_product_placeholder), + contentDescription = "", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(276.dp) + .clip(shape = RoundedCornerShape(size = 8.dp)) + .clickable { onImageSelected(photoUrl) } + ) + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt new file mode 100644 index 00000000000..a292f6b8433 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt @@ -0,0 +1,56 @@ +package com.woocommerce.android.ui.blaze.creation.ad + +import android.os.Parcelable +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.woocommerce.android.ui.products.details.ProductDetailRepository +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult +import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.getStateFlow +import com.woocommerce.android.viewmodel.navArgs +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize +import javax.inject.Inject + +@HiltViewModel +class ProductImagePickerViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val productRepository: ProductDetailRepository, +) : ScopedViewModel(savedStateHandle) { + private val navArgs: ProductImagePickerFragmentArgs by savedStateHandle.navArgs() + + private val _viewState = savedStateHandle.getStateFlow( + scope = viewModelScope, + initialValue = ViewState(emptyList()) + ) + val viewState = _viewState.asLiveData() + + init { + launch { + val product = productRepository.getProduct(navArgs.productId) + product?.let { + val productImages = product.images.map { it.source } + _viewState.update { it.copy(productImageUrls = productImages) } + } + } + } + + fun onImageSelected(selectedImageUri: String) { + triggerEvent( + ExitWithResult( + ImageSelectedResult(imageUrl = selectedImageUri) + ) + ) + } + + @Parcelize + data class ViewState( + val productImageUrls: List + ) : Parcelable + + @Parcelize + data class ImageSelectedResult(val imageUrl: String) : Parcelable +} diff --git a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml index 0f49e9552d1..dbbfad740b8 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml @@ -98,6 +98,9 @@ + + + + From a9dd531ab50730f4f2e9ecdba1c07aaaaf3d4f24 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 13:34:59 +0200 Subject: [PATCH 28/60] Update product selected image --- .../ad/BlazeCampaignCreationEditAdFragment.kt | 9 +++++++++ .../ad/BlazeCampaignCreationEditAdViewModel.kt | 1 + .../blaze/creation/ad/ProductImagePickerFragment.kt | 7 ++++--- .../creation/ad/ProductImagePickerViewModel.kt | 13 +++++++------ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt index e8d66aaef34..feafc124a8a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.mediapicker.MediaPickerHelper @@ -16,6 +17,7 @@ import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.EditAdResult import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ShowMediaLibrary import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ShowProductImagePicker +import com.woocommerce.android.ui.blaze.creation.ad.ProductImagePickerViewModel.ImageSelectedResult import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit @@ -45,6 +47,7 @@ class BlazeCampaignCreationEditAdFragment : BaseFragment(), MediaPickerResultHan } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + handleResults() viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { is Exit -> findNavController().popBackStack() @@ -65,6 +68,12 @@ class BlazeCampaignCreationEditAdFragment : BaseFragment(), MediaPickerResultHan ) } + private fun handleResults() { + handleResult(ProductImagePickerFragment.ON_PRODUCT_IMAGE_SELECTED) { + viewModel.onWPMediaSelected(it.productImage) + } + } + override fun onDeviceMediaSelected(imageUris: List, source: String) { if (imageUris.isNotEmpty()) { viewModel.onLocalImageSelected(imageUris.first().toString()) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt index ba55b5a20cc..ca7fcfe6396 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt @@ -172,6 +172,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( fun onProductImagesRequested() { triggerEvent(ShowProductImagePicker(navArgs.productId)) + setMediaPickerDialogVisibility(false) } data class ShowMediaLibrary(val source: MediaPickerSetup.DataSource) : Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt index 71b55d66da0..f00c8c131af 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt @@ -25,6 +25,7 @@ import coil.compose.AsyncImage import coil.request.ImageRequest import com.woocommerce.android.R import com.woocommerce.android.extensions.navigateBackWithResult +import com.woocommerce.android.model.Product import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.creation.ad.ProductImagePickerViewModel.ImageSelectedResult import com.woocommerce.android.ui.compose.composeView @@ -75,15 +76,15 @@ class ProductImagePickerFragment : BaseFragment() { @Composable fun ProductImagePickerScreen( viewState: ProductImagePickerViewModel.ViewState, - onImageSelected: (String) -> Unit + onImageSelected: (Product.Image) -> Unit ) { LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 128.dp) ) { - items(viewState.productImageUrls) { photoUrl -> + items(viewState.productImages) { photoUrl -> AsyncImage( model = ImageRequest.Builder(LocalContext.current) - .data(photoUrl) + .data(photoUrl.source) .crossfade(true) .build(), fallback = painterResource(R.drawable.blaze_campaign_product_placeholder), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt index a292f6b8433..955fb18a087 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt @@ -4,6 +4,8 @@ import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope +import com.woocommerce.android.model.Product +import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.products.details.ProductDetailRepository import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.ScopedViewModel @@ -32,25 +34,24 @@ class ProductImagePickerViewModel @Inject constructor( launch { val product = productRepository.getProduct(navArgs.productId) product?.let { - val productImages = product.images.map { it.source } - _viewState.update { it.copy(productImageUrls = productImages) } + _viewState.update { it.copy(productImages = product.images) } } } } - fun onImageSelected(selectedImageUri: String) { + fun onImageSelected(productImage: Product.Image) { triggerEvent( ExitWithResult( - ImageSelectedResult(imageUrl = selectedImageUri) + ImageSelectedResult(productImage = productImage) ) ) } @Parcelize data class ViewState( - val productImageUrls: List + val productImages: List ) : Parcelable @Parcelize - data class ImageSelectedResult(val imageUrl: String) : Parcelable + data class ImageSelectedResult(val productImage: Product.Image) : Parcelable } From 40e8edf5ced7121d3b1de93410506f9aed3d840c Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 13:43:22 +0200 Subject: [PATCH 29/60] Update product images UI for adaptive grid --- .../creation/ad/ProductImagePickerFragment.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt index f00c8c131af..6b5300464d1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt @@ -5,16 +5,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -79,7 +79,12 @@ class ProductImagePickerFragment : BaseFragment() { onImageSelected: (Product.Image) -> Unit ) { LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = 128.dp) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + columns = GridCells.Adaptive(minSize = 128.dp), + verticalArrangement = Arrangement.spacedBy(3.dp), + horizontalArrangement = Arrangement.spacedBy(3.dp) ) { items(viewState.productImages) { photoUrl -> AsyncImage( @@ -94,8 +99,7 @@ class ProductImagePickerFragment : BaseFragment() { contentScale = ContentScale.Crop, modifier = Modifier .fillMaxWidth() - .height(276.dp) - .clip(shape = RoundedCornerShape(size = 8.dp)) + .height(128.dp) .clickable { onImageSelected(photoUrl) } ) } From 9c190163e19c72afee00912c4e57117e6eaa0fca Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 13 Sep 2024 14:12:04 +0200 Subject: [PATCH 30/60] Update CI toolkit to 3.6.2 --- .buildkite/shared-pipeline-vars | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index 5aaece7d56e..6035b7e52f4 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -1,7 +1,8 @@ #!/bin/sh - # This file is `source`'d before calling `buildkite-agent pipeline upload`, and can be used - # to set up some variables that will be interpolated in the `.yml` pipeline before uploading it. +# This file is `source`'d before calling `buildkite-agent pipeline upload`, and can be used +# to set up some variables that will be interpolated in the `.yml` pipeline before uploading it. + +export CI_TOOLKIT="automattic/a8c-ci-toolkit#3.6.2" +export TEST_COLLECTOR="test-collector#v1.10.1" - export CI_TOOLKIT="automattic/a8c-ci-toolkit#3.6.1" - export TEST_COLLECTOR="test-collector#v1.10.1" From 769b21b5d7eb3a86ec5af66a78cbcc89343b9f36 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 13 Sep 2024 14:12:35 +0200 Subject: [PATCH 31/60] temp: add lint violation --- .../com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt index 8f1cbe68e5e..56167a4c115 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.moremenu +import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -65,6 +66,10 @@ class MoreMenuFragment : TopLevelFragment() { return } + override fun onAttach(activity: Activity) { + + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, From 00401b541a39d9b609954c47b7179baaf6b4d460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 13 Sep 2024 14:42:38 +0200 Subject: [PATCH 32/60] Do not use ignore case when renaming attributes --- .../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 9e0591f61ae..7fe53400f37 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 @@ -1664,7 +1664,7 @@ class ProductDetailViewModel @Inject constructor( fun renameAttributeInDraft(attributeId: Long, oldAttributeName: String, newAttributeName: String): Boolean { // first make sure an attribute with the new name doesn't already exist in the draft productDraftAttributes.forEach { - if (it.name.equals(newAttributeName, ignoreCase = true)) { + if (it.name.equals(newAttributeName)) { triggerEvent(ShowSnackbar(R.string.product_attribute_name_already_exists)) return false } From cbd41b6c5f299008b46ce2734dda92d34fbabc3e Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 13 Sep 2024 14:45:33 +0200 Subject: [PATCH 33/60] Revert "temp: add lint violation" This reverts commit 769b21b5d7eb3a86ec5af66a78cbcc89343b9f36. --- .../com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt index 56167a4c115..8f1cbe68e5e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt @@ -1,6 +1,5 @@ package com.woocommerce.android.ui.moremenu -import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -66,10 +65,6 @@ class MoreMenuFragment : TopLevelFragment() { return } - override fun onAttach(activity: Activity) { - - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, From d0720ba461516f48aeb3c6a1925f410ec821a31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 13 Sep 2024 16:08:13 +0200 Subject: [PATCH 34/60] Add unit test --- .../details/ProductDetailViewModelTest.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) 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 a2aac025f4a..d29d4076a31 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 @@ -8,6 +8,7 @@ import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.extensions.takeIfNotEqualTo import com.woocommerce.android.media.MediaFilesRepository import com.woocommerce.android.media.ProductImagesServiceWrapper +import com.woocommerce.android.model.ProductAttribute import com.woocommerce.android.model.ProductVariation import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.tools.SelectedSite @@ -742,6 +743,37 @@ class ProductDetailViewModelTest : BaseUnitTest() { Assertions.assertThat(draftTerms[1]).isEqualTo(firstTerm) } + @Test + fun `Re-name attribute terms is saved correctly`() = testBlocking { + viewModel.productDetailViewStateData.observeForever { _, _ -> } + val attributeName = "name" + val newName = attributeName.replaceFirstChar { it.uppercase() } + + val attributes = ArrayList() + attributes.add( + ProductAttribute( + id = 1, + name = attributeName, + isVariation = true, + isVisible = true, + terms = ArrayList().also { + it.add("one") + } + ) + ) + + val storedProduct = product.copy( + attributes = attributes + ) + doReturn(storedProduct).whenever(productRepository).getProductAsync(any()) + + viewModel.start() + viewModel.renameAttributeInDraft(1, attributeName, newName) + + val draftAttribute = viewModel.productDraftAttributes[0] + Assertions.assertThat(draftAttribute.name).isEqualTo(newName) + } + /** * Protection for a race condition bug in Variations. * From c6cd96ef38c4a36be4ec06d59418318662da65a4 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 16:09:53 +0200 Subject: [PATCH 35/60] Add toolbar and title to product photo picker --- .../creation/ad/ProductImagePickerFragment.kt | 62 -------------- .../ad/ProductImagePickerViewModel.kt | 5 ++ .../blaze/creation/ad/ProductImageScreen.kt | 84 +++++++++++++++++++ WooCommerce/src/main/res/values/strings.xml | 1 + 4 files changed, 90 insertions(+), 62 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt index 6b5300464d1..4cf6189ac41 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt @@ -4,28 +4,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.runtime.Composable -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController -import coil.compose.AsyncImage -import coil.request.ImageRequest -import com.woocommerce.android.R import com.woocommerce.android.extensions.navigateBackWithResult -import com.woocommerce.android.model.Product import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.creation.ad.ProductImagePickerViewModel.ImageSelectedResult import com.woocommerce.android.ui.compose.composeView @@ -62,47 +43,4 @@ class ProductImagePickerFragment : BaseFragment() { } } } - - @Composable - fun ProductImagePickerScreen(viewModel: ProductImagePickerViewModel) { - viewModel.viewState.observeAsState().value?.let { viewState -> - ProductImagePickerScreen( - viewState = viewState, - onImageSelected = viewModel::onImageSelected - ) - } - } - - @Composable - fun ProductImagePickerScreen( - viewState: ProductImagePickerViewModel.ViewState, - onImageSelected: (Product.Image) -> Unit - ) { - LazyVerticalGrid( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - columns = GridCells.Adaptive(minSize = 128.dp), - verticalArrangement = Arrangement.spacedBy(3.dp), - horizontalArrangement = Arrangement.spacedBy(3.dp) - ) { - items(viewState.productImages) { photoUrl -> - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(photoUrl.source) - .crossfade(true) - .build(), - fallback = painterResource(R.drawable.blaze_campaign_product_placeholder), - placeholder = painterResource(R.drawable.blaze_campaign_product_placeholder), - error = painterResource(R.drawable.blaze_campaign_product_placeholder), - contentDescription = "", - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxWidth() - .height(128.dp) - .clickable { onImageSelected(photoUrl) } - ) - } - } - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt index 955fb18a087..71a63ad10c7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import com.woocommerce.android.model.Product import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.products.details.ProductDetailRepository +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.getStateFlow @@ -47,6 +48,10 @@ class ProductImagePickerViewModel @Inject constructor( ) } + fun onBackButtonTapped() { + triggerEvent(Exit) + } + @Parcelize data class ViewState( val productImages: List diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt new file mode 100644 index 00000000000..dd860c67b24 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt @@ -0,0 +1,84 @@ +package com.woocommerce.android.ui.blaze.creation.ad + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.woocommerce.android.R +import com.woocommerce.android.model.Product +import com.woocommerce.android.ui.compose.component.Toolbar + +@Composable +fun ProductImagePickerScreen(viewModel: ProductImagePickerViewModel) { + viewModel.viewState.observeAsState().value?.let { viewState -> + ProductImagePickerScreen( + viewState = viewState, + onImageSelected = viewModel::onImageSelected, + onBackButtonTapped = viewModel::onBackButtonTapped + ) + } +} + +@Composable +fun ProductImagePickerScreen( + viewState: ProductImagePickerViewModel.ViewState, + onImageSelected: (Product.Image) -> Unit, + onBackButtonTapped: () -> Unit +) { + Scaffold( + topBar = { + Toolbar( + title = stringResource(id = R.string.blaze_campaign_product_picker_title), + onNavigationButtonClick = onBackButtonTapped, + navigationIcon = Icons.AutoMirrored.Filled.ArrowBack, + ) + }, + backgroundColor = MaterialTheme.colors.surface + ) { paddingValues -> + LazyVerticalGrid( + modifier = Modifier + .padding(paddingValues) + .fillMaxWidth() + .padding(vertical = 16.dp), + columns = GridCells.Adaptive(minSize = 128.dp), + verticalArrangement = Arrangement.spacedBy(3.dp), + horizontalArrangement = Arrangement.spacedBy(3.dp) + ) { + items(viewState.productImages) { photoUrl -> + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(photoUrl.source) + .crossfade(true) + .build(), + fallback = painterResource(R.drawable.blaze_campaign_product_placeholder), + placeholder = painterResource(R.drawable.blaze_campaign_product_placeholder), + error = painterResource(R.drawable.blaze_campaign_product_placeholder), + contentDescription = "", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(128.dp) + .clickable { onImageSelected(photoUrl) } + ) + } + } + } +} diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 08b85bca35e..56e0fdb4042 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3955,6 +3955,7 @@ Suggested by AI Invalid image Please select an image with a minimum size of 400x400 pixels + Product Photos From c332c70d10b8d7fb1c000d2d95ed3fa68c7cc973 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 16:41:43 +0200 Subject: [PATCH 39/60] Fix detekt indentation issues --- .../ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt | 1 - .../android/ui/blaze/creation/ad/ProductImagePickerFragment.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt index 03a0dc43df8..f980658800b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt @@ -47,7 +47,6 @@ import com.woocommerce.android.R.color import com.woocommerce.android.R.dimen import com.woocommerce.android.R.drawable import com.woocommerce.android.R.string -import com.woocommerce.android.mediapicker.MediaPickerDialog import com.woocommerce.android.mediapicker.MediaPickerDialogForExistingProduct import com.woocommerce.android.ui.blaze.BlazeRepository.BlazeCampaignImage import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ViewState diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt index 4cf6189ac41..fe204073a8a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerFragment.kt @@ -26,7 +26,6 @@ class ProductImagePickerFragment : BaseFragment() { val viewModel: ProductImagePickerViewModel by viewModels() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return composeView { ProductImagePickerScreen(viewModel = viewModel) From b84c63daa9ed0f26926a12a6f009b8d00c5fccd1 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 16:42:03 +0200 Subject: [PATCH 40/60] Fix title of photo picker --- .../android/ui/blaze/creation/ad/ProductImageScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt index 4ab9e81e63f..7e0014c7835 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt @@ -53,7 +53,7 @@ fun ProductImagePickerScreen( Scaffold( topBar = { Toolbar( - title = stringResource(id = R.string.blaze_campaign_product_photo_picker_empty), + title = stringResource(id = R.string.blaze_campaign_product_photo_picker_title), onNavigationButtonClick = onBackButtonTapped, navigationIcon = Icons.AutoMirrored.Filled.ArrowBack, ) From 5a5483588476b14ed935877ace95f64ef0f4c85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 13 Sep 2024 16:48:40 +0200 Subject: [PATCH 41/60] Add release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 1ecd27f90e9..5477fbe0896 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -4,6 +4,7 @@ 20.5 ----- +- [*] Fixes a bug that prevented users to rename the Product Variation Attributes to because of case insensitive checks [https://github.com/woocommerce/woocommerce-android/pull/12608] 20.4 From 7e9a5f4cd46ee82692247f7ef20bd9477df4cdc2 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 16:55:28 +0200 Subject: [PATCH 42/60] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 9f8742ca47a..0e3970bfed4 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,6 +5,7 @@ 20.4 ----- - [**] Users can now scan their tracking number when adding it to the order [https://github.com/woocommerce/woocommerce-android/pull/12533] +- [*] Users can directly pick product images when creating Blaze ads [https://github.com/woocommerce/woocommerce-android/pull/12610] 20.3 ----- From ce884db8271529115d80080f855ea93de124bcd1 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 16:58:13 +0200 Subject: [PATCH 43/60] Update release notes --- RELEASE-NOTES.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 53f573464f2..bd2c70e475e 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -4,12 +4,11 @@ 20.5 ----- - +- [*] Users can directly pick product images when creating Blaze ads [https://github.com/woocommerce/woocommerce-android/pull/12610] 20.4 ----- - [**] Users can now scan their tracking number when adding it to the order [https://github.com/woocommerce/woocommerce-android/pull/12533] -- [*] Users can directly pick product images when creating Blaze ads [https://github.com/woocommerce/woocommerce-android/pull/12610] - [*] Fixed an issue where shipping labels were incorrectly calculating the weight for packages containing multiple quantities of the same product [https://github.com/woocommerce/woocommerce-android/pull/12602] 20.3 From 31cfd61f5f3bb13797f7d716d8b8ed4cff12f212 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 13 Sep 2024 17:01:11 +0200 Subject: [PATCH 44/60] Remove compose code duplication --- .../mediapicker/MediaPickerDialogScreen.kt | 64 +++---------------- .../ad/BlazeCampaignCreationEditAdScreen.kt | 11 ++-- 2 files changed, 16 insertions(+), 59 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt index b08fad6df84..bc2a15ea884 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerDialogScreen.kt @@ -32,63 +32,12 @@ import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.CAMERA import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.DEVICE import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.WP_MEDIA_LIBRARY -@Composable -fun MediaPickerDialogForExistingProduct( - onDismissRequest: () -> Unit, - onMediaLibraryRequested: (DataSource) -> Unit, - onProductImagesRequested: () -> Unit -) { - Dialog(onDismissRequest = onDismissRequest) { - Card( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colors.surface, MaterialTheme.shapes.medium) - .shadow(elevation = dimensionResource(id = dimen.major_75)), - ) { - Column( - modifier = Modifier - .padding(vertical = dimensionResource(id = dimen.major_100)), - ) { - Text( - modifier = Modifier - .padding( - start = dimensionResource(id = dimen.major_100), - end = dimensionResource(id = dimen.major_100), - bottom = dimensionResource(id = dimen.minor_100) - ), - text = stringResource(id = string.media_picker_dialog_title), - style = MaterialTheme.typography.h6 - ) - - DialogButton( - image = drawable.ic_gridicons_image, - title = string.image_source_device_chooser, - onClick = { onMediaLibraryRequested(DEVICE) } - ) - DialogButton( - image = drawable.ic_gridicons_camera, - title = string.image_source_device_camera, - onClick = { onMediaLibraryRequested(CAMERA) } - ) - DialogButton( - image = drawable.ic_wordpress, - title = string.image_source_wp_media_library, - onClick = { onMediaLibraryRequested(WP_MEDIA_LIBRARY) } - ) - DialogButton( - image = drawable.ic_product, - title = string.image_source_product_images, - onClick = onProductImagesRequested - ) - } - } - } -} - @Composable fun MediaPickerDialog( onDismissRequest: () -> Unit, - onMediaLibraryRequested: (DataSource) -> Unit + onMediaLibraryRequested: (DataSource) -> Unit, + withProductImagePicker: Boolean = false, + onProductImagesRequested: () -> Unit = {} ) { Dialog(onDismissRequest = onDismissRequest) { Card( @@ -127,6 +76,13 @@ fun MediaPickerDialog( title = string.image_source_wp_media_library, onClick = { onMediaLibraryRequested(WP_MEDIA_LIBRARY) } ) + if (withProductImagePicker) { + DialogButton( + image = drawable.ic_product, + title = string.image_source_product_images, + onClick = onProductImagesRequested + ) + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt index f980658800b..f880be2d77a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt @@ -47,7 +47,7 @@ import com.woocommerce.android.R.color import com.woocommerce.android.R.dimen import com.woocommerce.android.R.drawable import com.woocommerce.android.R.string -import com.woocommerce.android.mediapicker.MediaPickerDialogForExistingProduct +import com.woocommerce.android.mediapicker.MediaPickerDialog import com.woocommerce.android.ui.blaze.BlazeRepository.BlazeCampaignImage import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ViewState import com.woocommerce.android.ui.compose.component.Toolbar @@ -91,10 +91,11 @@ private fun BlazeCampaignCreationEditAdScreen( onSaveTapped: () -> Unit ) { if (viewState.isMediaPickerDialogVisible) { - MediaPickerDialogForExistingProduct( - onMediaPickerDialogDismissed, - onMediaLibraryRequested, - onProductImagesRequested + MediaPickerDialog( + onDismissRequest = onMediaPickerDialogDismissed, + onMediaLibraryRequested = onMediaLibraryRequested, + withProductImagePicker = true, + onProductImagesRequested = onProductImagesRequested ) } From 28b322dc8f6ec77ec1361b3f7af0aec4b2657747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Zi=C4=99ba?= Date: Fri, 13 Sep 2024 18:56:14 +0200 Subject: [PATCH 45/60] Update .buildkite/commands/lint.sh Co-authored-by: Ian Guedes Maia --- .buildkite/commands/lint.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.buildkite/commands/lint.sh b/.buildkite/commands/lint.sh index af7466c73d6..843aba91e3c 100755 --- a/.buildkite/commands/lint.sh +++ b/.buildkite/commands/lint.sh @@ -8,10 +8,9 @@ app_lint_exit_code=$? ./gradlew :WooCommerce-Wear:lintJalapenoDebug wear_lint_exit_code=$? +lint_exit_code=0 if [ $app_lint_exit_code -ne 0 ] || [ $wear_lint_exit_code -ne 0 ]; then lint_exit_code=1 -else - lint_exit_code=0 fi upload_sarif_to_github 'WooCommerce/build/reports/lint-results-jalapenoDebug.sarif' 'woocommerce' 'woocommerce-android' From e5d17755d9a3389ce60a2cc43ec93885bbb8255e Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Fri, 13 Sep 2024 21:32:58 +0300 Subject: [PATCH 46/60] Make sitePickerViewState visible for testing --- .../woocommerce/android/ui/sitepicker/SitePickerViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt index 66aed6a5f3d..6c60dc24bdb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.sitepicker import android.os.Parcelable import androidx.annotation.StringRes +import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle @@ -78,7 +79,9 @@ class SitePickerViewModel @Inject constructor( */ @Suppress("OPT_IN_USAGE") val sitePickerViewStateData = LiveDataDelegate(savedState, SitePickerViewState()) - private var sitePickerViewState by sitePickerViewStateData + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + var sitePickerViewState by sitePickerViewStateData private val _sites = MutableLiveData>() val sites: LiveData> = _sites From a0a673da5daf4219f7a8b2def1230e71f58280fc Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Fri, 13 Sep 2024 21:33:50 +0300 Subject: [PATCH 47/60] Add unit test for the visibility of primary button in the site picker --- .../ui/sitepicker/SitePickerViewModelTest.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt index dc1a9f518de..26088227c0c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt @@ -35,8 +35,13 @@ import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Logout import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain import org.assertj.core.api.Assert import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -55,6 +60,7 @@ import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult import org.wordpress.android.fluxc.store.SiteStore.ConnectSiteInfoPayload +import kotlin.test.assertFalse @ExperimentalCoroutinesApi class SitePickerViewModelTest : BaseUnitTest() { @@ -726,4 +732,26 @@ class SitePickerViewModelTest : BaseUnitTest() { assertThat(state.noStoresLabelText).isEqualTo(resourceProvider.getString(R.string.login_simple_wpcom_site)) assertThat(state.isNoStoresBtnVisible).isFalse } + + @Test + fun `given initiated, when isPrimaryBtnVisible and loading state, then primary button view is not displayed`() = + runTest { + Dispatchers.setMain(StandardTestDispatcher()) + val expectedSites = defaultExpectedSiteList.map { it.apply { setIsJetpackCPConnected(true) } } + whenSitesAreFetched(sitesFromDb = expectedSites) + whenViewModelIsCreated() + val states = viewModel.sitePickerViewStateData.liveData.captureValues() + + // Make primary button visible after initialization. This may happen in low memory condition. + viewModel.sitePickerViewState = viewModel.sitePickerViewState.copy(isPrimaryBtnVisible = true) + + advanceUntilIdle() + + states.forEach { state -> + if (state.isSkeletonViewVisible) { + assertFalse(state.isPrimaryBtnVisible) + } + } + Dispatchers.resetMain() + } } From cbfefd22bdcaa8d4c3e38f5533c0fb1f4b0e377b Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 16 Sep 2024 11:25:47 +0530 Subject: [PATCH 48/60] Animate cart list height change --- .../android/ui/woopos/home/cart/WooPosCartScreen.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt index e7173fdcfc5..868d021fd10 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt @@ -234,6 +234,10 @@ private fun CartBodyWithItems( val listState = rememberLazyListState() ScrollToTopHandler(items, listState) + val spacerHeight by animateDpAsState( + targetValue = if (!isCheckoutButtonVisible) 182.dp else 0.dp, label = "cart list height animation" + ) + WooPosLazyColumn( modifier = modifier .padding(horizontal = 16.dp.toAdaptivePadding()), @@ -257,10 +261,8 @@ private fun CartBodyWithItems( onUIEvent = onUIEvent, ) } - if (!isCheckoutButtonVisible) { - item { - Spacer(modifier = Modifier.height(182.dp)) - } + item { + Spacer(modifier = Modifier.height(spacerHeight)) } } } From 5f747490ce12dae2de172e60a2edbb60ff05ed92 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 16 Sep 2024 11:27:15 +0530 Subject: [PATCH 49/60] Fix detekt error --- .../android/ui/woopos/home/cart/WooPosCartScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt index 868d021fd10..94a99266e39 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt @@ -235,7 +235,8 @@ private fun CartBodyWithItems( ScrollToTopHandler(items, listState) val spacerHeight by animateDpAsState( - targetValue = if (!isCheckoutButtonVisible) 182.dp else 0.dp, label = "cart list height animation" + targetValue = if (!isCheckoutButtonVisible) 182.dp else 0.dp, + label = "cart list height animation" ) WooPosLazyColumn( From b1f891d09e8d1a32d594a1a271c8bad26e00f1a0 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 16 Sep 2024 12:48:42 +0200 Subject: [PATCH 50/60] Fix how ProductImagePickerViewModel and Product.Images are imported --- .../ui/blaze/creation/ad/ProductImagePickerViewModel.kt | 3 +-- .../android/ui/blaze/creation/ad/ProductImageScreen.kt | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt index 71a63ad10c7..572a77d651a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImagePickerViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.model.Product -import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.products.details.ProductDetailRepository import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult @@ -54,7 +53,7 @@ class ProductImagePickerViewModel @Inject constructor( @Parcelize data class ViewState( - val productImages: List + val productImages: List ) : Parcelable @Parcelize diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt index 7e0014c7835..50a1488421e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt @@ -29,8 +29,6 @@ import coil.compose.AsyncImage import coil.request.ImageRequest.Builder import com.woocommerce.android.R import com.woocommerce.android.model.Product -import com.woocommerce.android.model.Product.Image -import com.woocommerce.android.ui.blaze.creation.ad.ProductImagePickerViewModel.ViewState import com.woocommerce.android.ui.compose.component.Toolbar @Composable @@ -88,8 +86,8 @@ private fun ProductPhotosEmpty() { @Composable private fun ProductImageGrid( - viewState: ViewState, - onImageSelected: (Image) -> Unit, + viewState: ProductImagePickerViewModel.ViewState, + onImageSelected: (Product.Image) -> Unit, modifier: Modifier = Modifier ) { LazyVerticalGrid( From 267d0014118ac9b7eb7df8ab9048cb032cc54464 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 16 Sep 2024 13:25:05 +0200 Subject: [PATCH 51/60] Add non empty content description to product photo --- .../android/ui/blaze/creation/ad/ProductImageScreen.kt | 4 +++- WooCommerce/src/main/res/values/strings.xml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt index 50a1488421e..cc0773cf5b0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/ProductImageScreen.kt @@ -107,7 +107,9 @@ private fun ProductImageGrid( fallback = painterResource(R.drawable.blaze_campaign_product_placeholder), placeholder = painterResource(R.drawable.blaze_campaign_product_placeholder), error = painterResource(R.drawable.blaze_campaign_product_placeholder), - contentDescription = "", + contentDescription = stringResource( + id = R.string.blaze_campaign_product_photo_picker_photo_content_description + ), contentScale = ContentScale.Crop, modifier = Modifier .fillMaxWidth() diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 2c4ec8f01e8..bf2d1b66336 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3957,6 +3957,8 @@ Please select an image with a minimum size of 400x400 pixels Product Photos No photos found + Product photo + From 1eee3466d81494372e0d2eada6e1ab3b8bcb6a92 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 16 Sep 2024 14:15:30 +0200 Subject: [PATCH 52/60] Reverted animation duration to 300 back from 1000 --- .../android/ui/woopos/home/cart/WooPosCartScreen.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt index 989515a32cf..90c954e1f4f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartScreen.kt @@ -147,8 +147,8 @@ private fun WooPosCartScreen( AnimatedVisibility( visible = state.isCheckoutButtonVisible, - enter = fadeIn(animationSpec = tween(1000)), - exit = fadeOut(animationSpec = tween(1000)), + enter = fadeIn(animationSpec = tween(300)), + exit = fadeOut(animationSpec = tween(300)), modifier = Modifier .fillMaxWidth() .padding(16.dp.toAdaptivePadding()) @@ -298,8 +298,8 @@ private fun CartToolbar( AnimatedVisibility( visible = toolbar.backIconVisible, - enter = fadeIn(animationSpec = tween(1000)) + expandHorizontally(), - exit = fadeOut(animationSpec = tween(1000)) + shrinkHorizontally() + enter = fadeIn(animationSpec = tween(300)) + expandHorizontally(), + exit = fadeOut(animationSpec = tween(300)) + shrinkHorizontally() ) { IconButton( onClick = { onBackClicked() }, From 71474a2f075578aab91a83f6cbce1d265791927b Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 16 Sep 2024 14:55:21 +0200 Subject: [PATCH 53/60] Removed redundant box --- .../ui/woopos/home/products/WooPosBanner.kt | 175 +++++++++--------- 1 file changed, 85 insertions(+), 90 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt index 14d6613cf91..f1fbafc0e1c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt @@ -64,110 +64,105 @@ fun WooPosBanner( modifier = Modifier .fillMaxWidth() ) { - Box( + ConstraintLayout( modifier = Modifier - .fillMaxWidth() .padding(32.dp.toAdaptivePadding()) + .fillMaxWidth() ) { - ConstraintLayout( + val (icon, header, description, close) = createRefs() + + Box( modifier = Modifier - .fillMaxWidth() + .size(48.dp) + .constrainAs(icon) { + top.linkTo(parent.top) + start.linkTo(parent.start) + bottom.linkTo(parent.bottom) + } ) { - val (icon, header, description, close) = createRefs() - - Box( - modifier = Modifier - .size(48.dp) - .constrainAs(icon) { - top.linkTo(parent.top) - start.linkTo(parent.start) - bottom.linkTo(parent.bottom) - } - ) { - Icon( - painterResource(id = bannerIcon), - contentDescription = stringResource( - id = R.string.woopos_banner_simple_products_info_content_description - ), - tint = MaterialTheme.colors.primary, - modifier = Modifier.align(Alignment.Center) - ) - } - - Text( - text = title, - style = MaterialTheme.typography.h5, - color = MaterialTheme.colors.onBackground.copy(alpha = 0.87f), - fontWeight = FontWeight.SemiBold, - modifier = Modifier - .padding( - start = 32.dp.toAdaptivePadding(), - bottom = 8.dp.toAdaptivePadding() - ) - .constrainAs(header) { - top.linkTo(parent.top) - start.linkTo(icon.end) - end.linkTo(close.start) - width = Dimension.fillToConstraints - } + Icon( + painterResource(id = bannerIcon), + contentDescription = stringResource( + id = R.string.woopos_banner_simple_products_info_content_description + ), + tint = MaterialTheme.colors.primary, + modifier = Modifier.align(Alignment.Center) ) + } - val annotatedText = buildAnnotatedString { - append(message) - withStyle(style = SpanStyle(color = MaterialTheme.colors.primary)) { - append(" ") - append(stringResource(id = R.string.woopos_banner_simple_products_only_message_learn_more)) + Text( + text = title, + style = MaterialTheme.typography.h5, + color = MaterialTheme.colors.onBackground.copy(alpha = 0.87f), + fontWeight = FontWeight.SemiBold, + modifier = Modifier + .padding( + start = 32.dp.toAdaptivePadding(), + bottom = 8.dp.toAdaptivePadding() + ) + .constrainAs(header) { + top.linkTo(parent.top) + start.linkTo(icon.end) + end.linkTo(close.start) + width = Dimension.fillToConstraints } + ) + + val annotatedText = buildAnnotatedString { + append(message) + withStyle(style = SpanStyle(color = MaterialTheme.colors.primary)) { + append(" ") + append(stringResource(id = R.string.woopos_banner_simple_products_only_message_learn_more)) } + } - Box( + Box( + modifier = Modifier + .constrainAs(description) { + top.linkTo(header.bottom) + start.linkTo(header.start) + end.linkTo(close.start) + width = Dimension.fillToConstraints + } + .padding( + start = 24.dp.toAdaptivePadding(), + end = 18.dp.toAdaptivePadding() + ) + ) { + Text( modifier = Modifier - .constrainAs(description) { - top.linkTo(header.bottom) - start.linkTo(header.start) - end.linkTo(close.start) - width = Dimension.fillToConstraints + .clickable { + onLearnMore() } .padding( - start = 24.dp.toAdaptivePadding(), - end = 18.dp.toAdaptivePadding() - ) - ) { - Text( - modifier = Modifier - .clickable { - onLearnMore() - } - .padding( - start = 8.dp.toAdaptivePadding(), - top = 8.dp.toAdaptivePadding(), - bottom = 8.dp.toAdaptivePadding(), - ), - text = annotatedText, - style = MaterialTheme.typography.body1, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colors.onBackground.copy(alpha = 0.87f) - ) - } - - IconButton( - modifier = Modifier - .constrainAs(close) { - top.linkTo(header.top) - bottom.linkTo(header.bottom) - end.linkTo(parent.end) - }, - onClick = { onClose() } - ) { - Icon( - modifier = Modifier.size(32.dp), - imageVector = Icons.Default.Close, - tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), - contentDescription = stringResource( - id = R.string.woopos_banner_simple_products_close_content_description + start = 8.dp.toAdaptivePadding(), + top = 8.dp.toAdaptivePadding(), + bottom = 8.dp.toAdaptivePadding(), ), - ) - } + text = annotatedText, + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colors.onBackground.copy(alpha = 0.87f) + ) + } + + IconButton( + modifier = Modifier + .constrainAs(close) { + top.linkTo(header.top) + bottom.linkTo(header.bottom) + end.linkTo(parent.end) + }, + onClick = { onClose() } + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = Icons.Default.Close, + tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), + contentDescription = stringResource( + id = R.string.woopos_banner_simple_products_close_content_description + ), + ) } } } From 1ecdac9d3f0502a9070339edd1ea78752d817e99 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 16 Sep 2024 14:58:23 +0200 Subject: [PATCH 54/60] Outer padding for the banner 24 as on the design --- .../woocommerce/android/ui/woopos/home/products/WooPosBanner.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt index f1fbafc0e1c..a8fcc00b135 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/products/WooPosBanner.kt @@ -66,7 +66,7 @@ fun WooPosBanner( ) { ConstraintLayout( modifier = Modifier - .padding(32.dp.toAdaptivePadding()) + .padding(24.dp.toAdaptivePadding()) .fillMaxWidth() ) { val (icon, header, description, close) = createRefs() From 97cac80e4d7f0c8e28d5bccd0edf4fd5c04a5325 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 16 Sep 2024 15:07:11 +0200 Subject: [PATCH 55/60] Do not apply modifier to the shadows --- .../woopos/common/composeui/component/WooPosLazyColumn.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosLazyColumn.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosLazyColumn.kt index 669e84c043b..40e50b68519 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosLazyColumn.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosLazyColumn.kt @@ -65,12 +65,12 @@ fun WooPosLazyColumn( } if (showTopShadow.value) { - Shadow(modifier) + Shadow() } if (showBottomShadow.value && withBottomShadow) { Shadow( - modifier + Modifier .align(Alignment.BottomCenter) .graphicsLayer(rotationZ = 180f) ) @@ -79,7 +79,7 @@ fun WooPosLazyColumn( } @Composable -private fun Shadow(modifier: Modifier) { +private fun Shadow(modifier: Modifier = Modifier) { WooPosCard( shape = MaterialTheme.shapes.large, backgroundColor = Color.Black.copy(alpha = 0.1f), From 4c013166ef8abb89aa3c2d60221b667915cea77a Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 16 Sep 2024 15:27:15 +0200 Subject: [PATCH 56/60] Fix typo on release publishing --- fastlane/Fastfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4a18ea71e06..71aad79b0f1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -515,7 +515,7 @@ platform :android do # lane :publish_release do |skip_confirm: false, include_wear_app: false| ensure_git_status_clean - ensure_git_branch_is_release_branch + ensure_git_branch_is_release_branch! version_number = release_version_current @@ -1336,9 +1336,7 @@ rescue StandardError => e error_message = <<-MESSAGE Error creating backmerge pull request: - ``` #{e.message} - ``` If this is not the first time you are running the release task, the backmerge PR for the version `#{version}` might have already been previously created. Please close any previous backmerge PR for `#{version}`, delete the previous merge branch, then run the release task again. From 45b5304ef08bd383e5b17ef5c5af0b75aeeb6970 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 16 Sep 2024 15:27:36 +0200 Subject: [PATCH 57/60] Disable release build retry --- .buildkite/release-builds.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.buildkite/release-builds.yml b/.buildkite/release-builds.yml index e0f72e0bbe1..a81fa31e342 100644 --- a/.buildkite/release-builds.yml +++ b/.buildkite/release-builds.yml @@ -24,6 +24,10 @@ steps: plugins: [$CI_TOOLKIT] notify: - slack: "#build-and-ship" + retry: + manual: + # If those jobs fail, one should always prefer re-triggering a new build from ReleaseV2 rather than retrying the individual job from Buildkite + allowed: false - label: "🛠 Release Build (Wear App)" command: | From 5e74c632ef6e966e1d7a9559e09ab28f999cfa06 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 16 Sep 2024 16:06:52 +0200 Subject: [PATCH 58/60] Move `isUpdatingOrderDraft` management into coroutine --- .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index f8ae89c31fd..ca65c58c4cc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -895,14 +895,12 @@ class OrderCreateEditViewModel @Inject constructor( is CodeScannerStatus.Success -> { barcodeScanningTracker.trackSuccess(ScanningSource.ORDER_CREATION) - viewState = viewState.copy(isUpdatingOrderDraft = true) fetchProductBySKU( BarcodeOptions( sku = status.code, barcodeFormat = status.format ) ) - viewState = viewState.copy(isUpdatingOrderDraft = false) } CodeScannerStatus.NotFound -> { @@ -923,6 +921,7 @@ class OrderCreateEditViewModel @Inject constructor( } }.orEmpty() viewModelScope.launch { + viewState = viewState.copy(isUpdatingOrderDraft = true) val result = fetchProductBySKU(barcodeOptions.sku, barcodeOptions.barcodeFormat) if (result.isSuccess) { val product = result.getOrNull() @@ -943,6 +942,7 @@ class OrderCreateEditViewModel @Inject constructor( "Product search via SKU API call failed" ) } + viewState = viewState.copy(isUpdatingOrderDraft = false) } } From 7aed80b23d80b490bf42b8c20f555a1d006454a5 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 17 Sep 2024 12:05:14 +0200 Subject: [PATCH 59/60] Simulate overlay for the connect reader button --- .../ui/woopos/home/toolbar/WooPosToolbar.kt | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt index 8135693e234..314c4534f1f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt @@ -21,6 +21,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable @@ -167,6 +168,7 @@ private fun Toolbar( contentDescription = labels.cardReaderStatusContentDescription }, state = cardReaderStatus, + menuCardDisabled = menuCardDisabled, ) { onUIEvent(WooPosToolbarUIEvent.OnCardReaderStatusClicked) } MenuButtonWithPopUpMenu( @@ -282,6 +284,7 @@ private fun PopUpMenuItem( private fun CardReaderStatusButton( modifier: Modifier, state: WooPosCardReaderStatus, + menuCardDisabled: Boolean, onClick: () -> Unit ) { val transition = updateTransition( @@ -334,26 +337,34 @@ private fun CardReaderStatusButton( elevation = TOOLBAR_ELEVATION, shape = RoundedCornerShape(8.dp), ) { - TextButton( - onClick = onClick, - modifier = Modifier - .padding(8.dp.toAdaptivePadding()) - .border( - width = 2.dp, - color = borderColor, - shape = RoundedCornerShape(4.dp) - ) - .height(40.dp), + Surface( + color = if (menuCardDisabled) { + MaterialTheme.colors.onSurface.copy(alpha = 0.2f) + } else { + Color.Transparent + }, ) { - Spacer(modifier = Modifier.width(16.dp.toAdaptivePadding())) - Circle(size = 12.dp, color = illustrationColor) - Spacer(modifier = Modifier.width(4.dp.toAdaptivePadding())) - ReaderStatusText( - modifier = Modifier.animateContentSize(), - title = title, - color = textColor, - ) - Spacer(modifier = Modifier.width(16.dp.toAdaptivePadding())) + TextButton( + onClick = onClick, + modifier = Modifier + .padding(8.dp.toAdaptivePadding()) + .border( + width = 2.dp, + color = borderColor, + shape = RoundedCornerShape(4.dp) + ) + .height(40.dp), + ) { + Spacer(modifier = Modifier.width(16.dp.toAdaptivePadding())) + Circle(size = 12.dp, color = illustrationColor) + Spacer(modifier = Modifier.width(4.dp.toAdaptivePadding())) + ReaderStatusText( + modifier = Modifier.animateContentSize(), + title = title, + color = textColor, + ) + Spacer(modifier = Modifier.width(16.dp.toAdaptivePadding())) + } } } } From 8a8490eec630b459037abb13f4b659e8766c629b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 17 Sep 2024 14:48:25 +0200 Subject: [PATCH 60/60] Update `isUpdatingOrderDraft` state unit test --- .../android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt index 9a4666b783b..830dbc13df1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt @@ -576,7 +576,6 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { assertFalse(isUpdatingOrderDraft[0]) assertTrue(isUpdatingOrderDraft[1]) - assertFalse(isUpdatingOrderDraft[2]) } @Test