From d4b5280a49b8ddee04d6bbac4462f9e9774a38fc Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 24 Jan 2025 20:27:54 +0700 Subject: [PATCH 01/16] Add button to view recent upload failure on product images screen --- .../src/main/res/layout/fragment_product_images.xml | 12 ++++++++++++ WooCommerce/src/main/res/values/strings.xml | 1 + 2 files changed, 13 insertions(+) diff --git a/WooCommerce/src/main/res/layout/fragment_product_images.xml b/WooCommerce/src/main/res/layout/fragment_product_images.xml index 0435dcb5b8e..421e3430b3c 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_images.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_images.xml @@ -37,6 +37,18 @@ android:layout_marginEnd="@dimen/margin_extra_large" android:text="@string/product_add_photos" /> + + Add photo Replace photo Remove photo + View recent upload failure Cover Add a product image Error removing product image From 4a23dea42f6c6bf40b1931ec8a281afcd0367c4d Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 24 Jan 2025 20:28:32 +0700 Subject: [PATCH 02/16] Observe current upload errors and hide/show button based on it. --- .../ui/products/images/ProductImagesFragment.kt | 3 +++ .../ui/products/images/ProductImagesViewModel.kt | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt index 431f4b0e592..3cf923a2288 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt @@ -194,6 +194,9 @@ class ProductImagesFragment : } } } + new.hasUploadErrors?.takeIfNotEqualTo(old?.hasUploadErrors) { hasErrors -> + binding.openUploadScreenButton.visibility = if (hasErrors) View.VISIBLE else View.GONE + } new.isDragDropDescriptionVisible?.takeIfNotEqualTo(old?.isDragDropDescriptionVisible) { isVisible -> binding.dragAndDropDescription.isVisible = isVisible } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt index fc1ad4bc2c1..d3ae68e9fcd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt @@ -219,6 +219,10 @@ class ProductImagesViewModel @Inject constructor( mediaFileUploadHandler.observeCurrentUploadErrors(remoteProductId) .filter { it.isNotEmpty() } + .map { + viewState = viewState.copy(hasUploadErrors = true) + it + } .onEach { triggerEvent( MultiLiveEvent.Event.ShowActionSnackbar( @@ -228,6 +232,11 @@ class ProductImagesViewModel @Inject constructor( ) } .launchIn(this) + + mediaFileUploadHandler.observeCurrentUploadErrors(remoteProductId) + .filter { it.isEmpty() } + .map { viewState = viewState.copy(hasUploadErrors = false) } + .launchIn(this) } fun onGalleryImageDragStarted() { @@ -270,6 +279,7 @@ class ProductImagesViewModel @Inject constructor( val images: List? = null, val chooserButtonButtonTitleRes: Int? = null, val isWarningVisible: Boolean? = null, + val hasUploadErrors: Boolean? = null, val isDragDropDescriptionVisible: Boolean? = null, val productImagesState: ProductImagesState = Browsing ) : Parcelable From 3883253c8df71a781d0cba9d7e2456f37780d0ea Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 24 Jan 2025 20:45:54 +0700 Subject: [PATCH 03/16] Simplify observe logic --- .../products/images/ProductImagesViewModel.kt | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt index d3ae68e9fcd..f3f1928c46e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt @@ -25,7 +25,6 @@ import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -218,25 +217,20 @@ class ProductImagesViewModel @Inject constructor( .launchIn(this) mediaFileUploadHandler.observeCurrentUploadErrors(remoteProductId) - .filter { it.isNotEmpty() } - .map { - viewState = viewState.copy(hasUploadErrors = true) - it - } - .onEach { - triggerEvent( - MultiLiveEvent.Event.ShowActionSnackbar( - message = resourceProvider.getMediaUploadErrorMessage(it.size), - actionText = resourceProvider.getString(R.string.details) - ) { triggerEvent(ProductNavigationTarget.ViewMediaUploadErrors(remoteProductId)) } - ) + .onEach { errorList -> + if (errorList.isEmpty()) { + viewState = viewState.copy(hasUploadErrors = false) + } else { + viewState = viewState.copy(hasUploadErrors = true) + triggerEvent( + MultiLiveEvent.Event.ShowActionSnackbar( + message = resourceProvider.getMediaUploadErrorMessage(errorList.size), + actionText = resourceProvider.getString(R.string.details) + ) { triggerEvent(ProductNavigationTarget.ViewMediaUploadErrors(remoteProductId)) } + ) + } } .launchIn(this) - - mediaFileUploadHandler.observeCurrentUploadErrors(remoteProductId) - .filter { it.isEmpty() } - .map { viewState = viewState.copy(hasUploadErrors = false) } - .launchIn(this) } fun onGalleryImageDragStarted() { From 36f54f98cd2b8a28480b0c72e4ade68ac6f81c31 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 24 Jan 2025 20:46:11 +0700 Subject: [PATCH 04/16] Make button navigate to upload screen --- .../android/ui/products/images/ProductImagesFragment.kt | 4 ++++ .../android/ui/products/images/ProductImagesViewModel.kt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt index 3cf923a2288..f7eb857e7db 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt @@ -154,6 +154,10 @@ class ProductImagesFragment : } } + binding.openUploadScreenButton.setOnClickListener { + viewModel.openUploadScreen() + } + setupTabletSecondPaneToolbar( title = getString(R.string.product_images_title), onMenuItemSelected = ::onMenuItemSelected, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt index f3f1928c46e..4cbae71536b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt @@ -260,6 +260,10 @@ class ProductImagesViewModel @Inject constructor( } } + fun openUploadScreen() { + triggerEvent(ProductNavigationTarget.ViewMediaUploadErrors(navArgs.remoteId)) + } + private fun List.updateProductCoverImageToFirstItem() = this.mapIndexed { index, image -> image.copy(isCoverImage = index == 0) } From bbd18da3dc37380c4af0182120b990573b1b7e22 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 24 Jan 2025 21:16:16 +0700 Subject: [PATCH 05/16] Add button to go to upload error screen when there's error on product details --- .../ui/products/details/ProductDetailFragment.kt | 7 +++++++ .../ui/products/details/ProductDetailViewModel.kt | 7 +++++++ .../ui/products/images/ProductImagesViewModel.kt | 2 +- .../main/res/layout/fragment_product_detail.xml | 14 ++++++++++++-- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt index 4050df62e72..59c318d3145 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt @@ -223,6 +223,10 @@ class ProductDetailFragment : } binding.cardsRecyclerView.layoutManager = layoutManager binding.cardsRecyclerView.itemAnimator = null + + binding.openUploadScreenButton.setOnClickListener { + viewModel.openUploadScreen() + } } private fun initializeViewModel() { @@ -361,6 +365,9 @@ class ProductDetailFragment : hideProgressDialog() } } + new.hasUploadErrors?.takeIfNotEqualTo(old?.hasUploadErrors) { hasErrors -> + binding.openUploadScreenButton.visibility = if (hasErrors) View.VISIBLE else View.GONE + } } viewModel.productDetailCards.observe(viewLifecycleOwner) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index 553c75c000c..363ef8f14c4 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 @@ -2134,8 +2134,10 @@ class ProductDetailViewModel @Inject constructor( mediaFileUploadHandler.observeCurrentUploadErrors(productId) .onEach { errorList -> if (errorList.isEmpty()) { + viewState = viewState.copy(hasUploadErrors = false) triggerEvent(HideImageUploadErrorSnackbar) } else { + viewState = viewState.copy(hasUploadErrors = true) triggerEvent( ShowActionSnackbar( message = resources.getMediaUploadErrorMessage(errorList.size), @@ -2651,6 +2653,10 @@ class ProductDetailViewModel @Inject constructor( } } + fun openUploadScreen() { + triggerEvent(ProductNavigationTarget.ViewMediaUploadErrors(getRemoteProductId())) + } + /** * Sealed class that handles the back navigation for the product detail screens while providing a common * interface for managing them as a single type. Currently used in all the product sub detail screens when @@ -2720,6 +2726,7 @@ class ProductDetailViewModel @Inject constructor( val productAggregateDraft: ProductAggregate? = null, val auxiliaryState: AuxiliaryState = AuxiliaryState.None, val uploadingImageUris: List? = null, + val hasUploadErrors: Boolean? = null, val isProgressDialogShown: Boolean? = null, val showBottomSheetButton: Boolean? = null, val isConfirmingTrash: Boolean = false, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt index 4cbae71536b..a64024030a2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt @@ -77,7 +77,7 @@ class ProductImagesViewModel @Inject constructor( init { if (viewState.showSourceChooser == true) { viewState = viewState.copy(showSourceChooser = false) - clearImageUploadErrors() + // clearImageUploadErrors() triggerEvent(ShowImageSourceDialog) } else if (navArgs.selectedImage != null) { triggerEvent(ShowImageDetail(navArgs.selectedImage!!)) diff --git a/WooCommerce/src/main/res/layout/fragment_product_detail.xml b/WooCommerce/src/main/res/layout/fragment_product_detail.xml index 7e5e7dddead..0c60da76f58 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_detail.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_detail.xml @@ -102,11 +102,9 @@ - - @@ -125,6 +123,18 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + Date: Thu, 30 Jan 2025 08:28:44 +0100 Subject: [PATCH 06/16] Do not clear all image errors when entering error upload image screen --- .../android/ui/media/MediaUploadErrorListViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt index 5bf759ffe4b..61fe12d4bc4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt @@ -46,9 +46,11 @@ class MediaUploadErrorListViewModel @Inject constructor( } mediaFileUploadHandler.observeCurrentUploadErrors(navArgs.remoteProductId) .filter { it.isNotEmpty() } - .onEach { errors -> + .onEach { newErrors -> val currentErrors = _viewState.value.uploadErrorList + - errors.map { ErrorUiModel(it.uploadStatus as UploadStatus.Failed, it.localUri) } + newErrors + .map { ErrorUiModel(it.uploadStatus as UploadStatus.Failed, it.localUri) } + .filter { _viewState.value.uploadErrorList.contains(it).not() } // Filter duplicates _viewState.update { _viewState.value.copy( uploadErrorList = currentErrors, @@ -58,8 +60,6 @@ class MediaUploadErrorListViewModel @Inject constructor( ) ) } - // Remove errors from mediaFileUploadHandler to avoid duplicate notifications - mediaFileUploadHandler.clearImageErrors(navArgs.remoteProductId) } .launchIn(this) } From 383ded3bfbab6e9c472d0d604be9842b001cacf3 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 08:38:29 +0100 Subject: [PATCH 07/16] Add function to clear upload error from list, upon retrying --- .../android/ui/media/MediaFileUploadHandler.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt index 96c33389c4d..3d40d7a30b5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt @@ -81,10 +81,12 @@ class MediaFileUploadHandler @Inject constructor( is Event.MediaUploadEvent.FetchSucceeded -> { enqueueMediaUpload(event) } + is Event.MediaUploadEvent.FetchFailed -> { statusList[index] = newStatus showUploadFailureNotifIfNoObserver(event.productId, statusList) } + is Event.MediaUploadEvent.UploadSucceeded -> { if (externalObservers.contains(event.productId)) { WooLog.d(WooLog.T.MEDIA, "MediaFileUploadHandler -> Upload successful, while handler is observed") @@ -94,6 +96,7 @@ class MediaFileUploadHandler @Inject constructor( statusList[index] = newStatus } } + is Event.MediaUploadEvent.UploadFailed -> { WooLog.e(WooLog.T.MEDIA, "MediaFileUploadHandler -> Upload failed", event.error) statusList[index] = newStatus @@ -125,6 +128,7 @@ class MediaFileUploadHandler @Inject constructor( when (event) { is Event.ProductUpdateEvent.ProductUpdateFailed -> notificationHandler.postUpdateFailureNotification(event.productId, event.product) + is Event.ProductUpdateEvent.ProductUpdateSucceeded -> notificationHandler.postUpdateSuccessNotification(event.productId, event.product, event.imagesCount) } @@ -179,6 +183,7 @@ class MediaFileUploadHandler @Inject constructor( uris.forEach { worker.enqueueWork(Work.FetchMedia(remoteProductId, it)) } + clearImageError(remoteProductId, uris) } fun cancelUpload(remoteProductId: Long) { @@ -196,6 +201,16 @@ class MediaFileUploadHandler @Inject constructor( notificationHandler.removeUploadFailureNotification(remoteProductId) } + private fun clearImageError(remoteProductId: Long, uris: List) { + uploadsStatus.update { list -> + list.filterNot { + it.remoteProductId == remoteProductId + && it.uploadStatus is UploadStatus.Failed + && uris.contains(it.localUri) + } + } + } + fun observeCurrentUploadErrors(remoteProductId: Long): Flow> = uploadsStatus.map { list -> list.filter { it.remoteProductId == remoteProductId && it.uploadStatus is UploadStatus.Failed } @@ -267,12 +282,14 @@ class MediaFileUploadHandler @Inject constructor( mediaErrorMessage = resourceProvider.getString(R.string.product_image_service_error_media_null), mediaErrorType = MediaStore.MediaErrorType.NULL_MEDIA_ARG ) + is Event.MediaUploadEvent.FetchSucceeded -> UploadStatus.InProgress is Event.MediaUploadEvent.UploadFailed -> UploadStatus.Failed( media = error.media, mediaErrorMessage = error.errorMessage, mediaErrorType = error.errorType ) + is Event.MediaUploadEvent.UploadSucceeded -> UploadStatus.UploadSuccess(media = media) } return ProductImageUploadData( From 1d3aceaeb0122205f5677a6b9882d3669ae062da Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 08:51:05 +0100 Subject: [PATCH 08/16] Remove unnecessary comment --- .../android/ui/products/images/ProductImagesViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt index a64024030a2..93bc07cde4f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt @@ -77,7 +77,6 @@ class ProductImagesViewModel @Inject constructor( init { if (viewState.showSourceChooser == true) { viewState = viewState.copy(showSourceChooser = false) - // clearImageUploadErrors() triggerEvent(ShowImageSourceDialog) } else if (navArgs.selectedImage != null) { triggerEvent(ShowImageDetail(navArgs.selectedImage!!)) From 5beda839e5a91ce46545e802dbbda1a2b47e3546 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 09:17:35 +0100 Subject: [PATCH 09/16] Replace CTA from upload error snackbar We now display a permanent access point to open upload errors screen --- .../android/ui/base/UIMessageResolver.kt | 17 +++++++++++++ .../products/images/ProductImagesFragment.kt | 24 ++++++------------- .../products/images/ProductImagesViewModel.kt | 13 ++++++---- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/base/UIMessageResolver.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/base/UIMessageResolver.kt index b690b0e7295..87de94cec60 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/base/UIMessageResolver.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/base/UIMessageResolver.kt @@ -234,6 +234,21 @@ interface UIMessageResolver { anchorViewId?.let { setAnchorView(it) } } + /** + * Create and return a snackbar with the provided [UiString]. + * + * @param [stringResId] The string resource id of the base message + * @param [stringArgs] Optional. One or more format argument stringArgs + */ + fun getUiStringSnack(message: UiString) = when (message) { + is UiString.UiStringRes -> + Snackbar.make(snackbarRoot, message.stringRes, BaseTransientBottomBar.LENGTH_LONG) + + is UiString.UiStringText -> Snackbar.make(snackbarRoot, message.text, BaseTransientBottomBar.LENGTH_LONG) + }.apply { + anchorViewId?.let { setAnchorView(it) } + } + /** * Display a snackbar with the provided message. * @@ -265,6 +280,7 @@ interface UIMessageResolver { val snackbar = when (message) { is UiString.UiStringRes -> Snackbar.make(snackbarRoot, message.stringRes, BaseTransientBottomBar.LENGTH_LONG) + is UiString.UiStringText -> Snackbar.make(snackbarRoot, message.text, BaseTransientBottomBar.LENGTH_LONG) }.apply { anchorViewId?.let { setAnchorView(it) } @@ -296,6 +312,7 @@ interface UIMessageResolver { snackbar.show() } } + private fun getIndefiniteSnackbarWithAction( view: View, msg: String, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt index f7eb857e7db..30b0aaf1dc9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesFragment.kt @@ -24,6 +24,7 @@ import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.extensions.takeIfNotEqualTo import com.woocommerce.android.mediapicker.MediaPickerHelper import com.woocommerce.android.model.Product +import com.woocommerce.android.model.UiString import com.woocommerce.android.ui.products.BaseProductEditorFragment import com.woocommerce.android.ui.products.ConfirmRemoveProductImageDialog import com.woocommerce.android.ui.products.ProductNavigationTarget @@ -36,14 +37,15 @@ import com.woocommerce.android.ui.products.images.ProductImagesViewModel.ShowIma import com.woocommerce.android.ui.products.images.ProductImagesViewModel.ShowStorageChooser import com.woocommerce.android.ui.products.images.ProductImagesViewModel.ShowWPMediaPicker import com.woocommerce.android.util.ChromeCustomTabUtils +import com.woocommerce.android.util.UiHelpers.getTextOfUiString import com.woocommerce.android.util.setHomeIcon import com.woocommerce.android.util.setupTabletSecondPaneToolbar import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowActionSnackbar import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowUiStringSnackbar import com.woocommerce.android.viewmodel.fixedHiltNavGraphViewModels import com.woocommerce.android.widgets.WCProductImageGalleryView import dagger.hilt.android.AndroidEntryPoint @@ -210,11 +212,7 @@ class ProductImagesFragment : when (event) { is Exit -> findNavController().navigateUp() is ShowSnackbar -> uiMessageResolver.showSnack(event.message) - is ShowActionSnackbar -> displayProductImageUploadErrorSnackBar( - event.message, - event.actionText, - event.action - ) + is ShowUiStringSnackbar -> displayProductImageUploadErrorSnackBar(event.message) is ProductNavigationTarget -> navigator.navigate(this, event) is ExitWithResult<*> -> navigateBackWithResult(KEY_IMAGES_DIALOG_RESULT, event.data) is ShowDialog -> event.showDialog() @@ -237,19 +235,11 @@ class ProductImagesFragment : ).show() } - private fun displayProductImageUploadErrorSnackBar( - message: String, - actionText: String, - actionListener: View.OnClickListener - ) { + private fun displayProductImageUploadErrorSnackBar(uiString: UiString) { if (imageUploadErrorsSnackbar == null) { - imageUploadErrorsSnackbar = uiMessageResolver.getIndefiniteActionSnack( - message = message, - actionText = actionText, - actionListener = actionListener - ) + imageUploadErrorsSnackbar = uiMessageResolver.getUiStringSnack(message = uiString) } else { - imageUploadErrorsSnackbar?.setText(message) + imageUploadErrorsSnackbar?.setText(getTextOfUiString(requireContext(), uiString)) } imageUploadErrorsSnackbar?.show() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt index 93bc07cde4f..f9550b05e4d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt @@ -11,6 +11,7 @@ import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.extensions.areSameImagesAs import com.woocommerce.android.model.Product +import com.woocommerce.android.model.UiString.UiStringText import com.woocommerce.android.model.toAppModel import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.ui.media.MediaFileUploadHandler @@ -222,10 +223,11 @@ class ProductImagesViewModel @Inject constructor( } else { viewState = viewState.copy(hasUploadErrors = true) triggerEvent( - MultiLiveEvent.Event.ShowActionSnackbar( - message = resourceProvider.getMediaUploadErrorMessage(errorList.size), - actionText = resourceProvider.getString(R.string.details) - ) { triggerEvent(ProductNavigationTarget.ViewMediaUploadErrors(remoteProductId)) } + MultiLiveEvent.Event.ShowUiStringSnackbar( + message = UiStringText( + resourceProvider.getMediaUploadErrorMessage(errorList.size) + ), + ) ) } } @@ -234,7 +236,8 @@ class ProductImagesViewModel @Inject constructor( fun onGalleryImageDragStarted() { when (viewState.productImagesState) { - is Dragging -> { /* no-op*/ } + is Dragging -> { /* no-op*/ + } Browsing -> viewState = viewState.copy( images = images.uncheckProductCoverImage(), From ca1500990edbf3ccee965ca36e5b61b5426bd37a Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 09:48:40 +0100 Subject: [PATCH 10/16] Set red text to highlight is error screen This will highlight the CTA after the user experiences some upload error --- WooCommerce/src/main/res/layout/fragment_product_detail.xml | 1 + WooCommerce/src/main/res/layout/fragment_product_images.xml | 1 + WooCommerce/src/main/res/values/strings.xml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/layout/fragment_product_detail.xml b/WooCommerce/src/main/res/layout/fragment_product_detail.xml index 0c60da76f58..536503a98f0 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_detail.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_detail.xml @@ -126,6 +126,7 @@ Add photo Replace photo Remove photo - View recent upload failure + View recent upload failures Cover Add a product image Error removing product image From b246e4aa2f2eaf933fc16a494211416a23bcf614 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 12:12:17 +0100 Subject: [PATCH 11/16] Update upload error snackbar from product detail screen as well --- .../products/details/ProductDetailFragment.kt | 28 ++++++++----------- .../details/ProductDetailViewModel.kt | 14 +++++----- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt index 59c318d3145..22a4477db80 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailFragment.kt @@ -37,6 +37,7 @@ import com.woocommerce.android.extensions.takeIfNotEqualTo import com.woocommerce.android.extensions.windowSizeClass import com.woocommerce.android.model.Product import com.woocommerce.android.model.Product.Image +import com.woocommerce.android.model.UiString import com.woocommerce.android.ui.aztec.AztecEditorFragment import com.woocommerce.android.ui.aztec.AztecEditorFragment.Companion.ARG_AZTEC_EDITOR_TEXT import com.woocommerce.android.ui.aztec.AztecEditorFragment.Companion.ARG_AZTEC_TITLE_FROM_AI_DESCRIPTION @@ -83,9 +84,10 @@ import com.woocommerce.android.ui.products.variations.VariationListViewModel.Var import com.woocommerce.android.ui.promobanner.PromoBanner import com.woocommerce.android.ui.promobanner.PromoBannerType import com.woocommerce.android.util.ChromeCustomTabUtils +import com.woocommerce.android.util.UiHelpers.getTextOfUiString import com.woocommerce.android.util.WooAnimUtils import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.LaunchUrlInChromeTab -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowActionSnackbar +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowUiStringSnackbar import com.woocommerce.android.widgets.CustomProgressDialog import com.woocommerce.android.widgets.SkeletonView import com.woocommerce.android.widgets.WCProductImageGalleryView.OnGalleryImageInteractionListener @@ -183,9 +185,11 @@ class ProductDetailFragment : ProductsCommunicationViewModel.CommunicationEvent.ProductSelected(mode.remoteProductId) ) } + is Mode.Loading, is Mode.Empty -> { findNavController().popBackStack() } + is Mode.AddNewProduct -> { } } @@ -399,11 +403,7 @@ class ProductDetailFragment : ) } - is ShowActionSnackbar -> displayProductImageUploadErrorSnackBar( - event.message, - event.actionText, - event.action - ) + is ShowUiStringSnackbar -> displayProductImageUploadErrorSnackBar(event.message) is HideImageUploadErrorSnackbar -> imageUploadErrorsSnackbar?.dismiss() is ShowLinkedProductPromoBanner -> showLinkedProductPromoBanner() @@ -424,6 +424,7 @@ class ProductDetailFragment : is ProductUpdated -> productsCommunicationViewModel.pushEvent( ProductsCommunicationViewModel.CommunicationEvent.ProductUpdated ) + is ProductDetailViewModel.ShowUpdateProductError -> showUpdateProductError(event.message) else -> event.isHandled = false } @@ -528,19 +529,11 @@ class ProductDetailFragment : } } - private fun displayProductImageUploadErrorSnackBar( - message: String, - actionText: String, - actionListener: View.OnClickListener - ) { + private fun displayProductImageUploadErrorSnackBar(uiString: UiString) { if (imageUploadErrorsSnackbar == null) { - imageUploadErrorsSnackbar = uiMessageResolver.getIndefiniteActionSnack( - message = message, - actionText = actionText, - actionListener = actionListener - ) + imageUploadErrorsSnackbar = uiMessageResolver.getUiStringSnack(message = uiString) } else { - imageUploadErrorsSnackbar?.setText(message) + imageUploadErrorsSnackbar?.setText(getTextOfUiString(requireContext(), uiString)) } imageUploadErrorsSnackbar?.show() } @@ -562,6 +555,7 @@ class ProductDetailFragment : Loading, None -> { binding.productErrorStateContainer.isVisible = false } + is Error -> { binding.productErrorStateContainer.isVisible = true binding.productDetailRoot.isVisible = false 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 363ef8f14c4..3d56f799084 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 @@ -42,6 +42,7 @@ import com.woocommerce.android.model.ProductTag import com.woocommerce.android.model.RequestResult import com.woocommerce.android.model.SubscriptionDetails import com.woocommerce.android.model.SubscriptionPeriod +import com.woocommerce.android.model.UiString.UiStringText import com.woocommerce.android.model.addTags import com.woocommerce.android.model.sortCategories import com.woocommerce.android.model.toAppModel @@ -93,9 +94,9 @@ import com.woocommerce.android.util.WooLog import com.woocommerce.android.viewmodel.LiveDataDelegate import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.LaunchUrlInChromeTab -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowActionSnackbar import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowUiStringSnackbar import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.getNullableStateFlow @@ -2139,12 +2140,11 @@ class ProductDetailViewModel @Inject constructor( } else { viewState = viewState.copy(hasUploadErrors = true) triggerEvent( - ShowActionSnackbar( - message = resources.getMediaUploadErrorMessage(errorList.size), - actionText = resources.getString(R.string.details) - ) { - triggerEvent(ProductNavigationTarget.ViewMediaUploadErrors(productId)) - } + ShowUiStringSnackbar( + message = UiStringText( + resources.getMediaUploadErrorMessage(errorList.size) + ), + ) ) } } From 12dc0e8f8a50cc9c746a48c86c25af57bab3c4e1 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 12:12:31 +0100 Subject: [PATCH 12/16] Fix detekt identation issues --- .../woocommerce/android/ui/media/MediaFileUploadHandler.kt | 6 +++--- .../android/ui/products/images/ProductImagesViewModel.kt | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt index 3d40d7a30b5..d727c9c5b0b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt @@ -204,9 +204,9 @@ class MediaFileUploadHandler @Inject constructor( private fun clearImageError(remoteProductId: Long, uris: List) { uploadsStatus.update { list -> list.filterNot { - it.remoteProductId == remoteProductId - && it.uploadStatus is UploadStatus.Failed - && uris.contains(it.localUri) + it.remoteProductId == remoteProductId && + it.uploadStatus is UploadStatus.Failed && + uris.contains(it.localUri) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt index f9550b05e4d..a8c11499a9e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/images/ProductImagesViewModel.kt @@ -236,8 +236,7 @@ class ProductImagesViewModel @Inject constructor( fun onGalleryImageDragStarted() { when (viewState.productImagesState) { - is Dragging -> { /* no-op*/ - } + is Dragging -> { /* no-op*/ } Browsing -> viewState = viewState.copy( images = images.uncheckProductCoverImage(), From f617f903bdb4eeb7c104fa2fe534729346f036a9 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 13:29:18 +0100 Subject: [PATCH 13/16] Fix and add unit tests --- .../details/ProductDetailViewModelTest.kt | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index 8f0fe0ef672..4715881d827 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 @@ -11,6 +11,7 @@ import com.woocommerce.android.media.ProductImagesServiceWrapper import com.woocommerce.android.model.ProductAggregate import com.woocommerce.android.model.ProductAttribute import com.woocommerce.android.model.ProductVariation +import com.woocommerce.android.model.UiString.UiStringText import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.blaze.IsBlazeEnabled @@ -871,11 +872,36 @@ class ProductDetailViewModelTest : BaseUnitTest() { errorEvents.emit(errors) Assertions.assertThat(viewModel.event.value).matches { - it is MultiLiveEvent.Event.ShowActionSnackbar && - it.message == errorMessage + it is MultiLiveEvent.Event.ShowUiStringSnackbar && + it.message == UiStringText(errorMessage) } } + @Test + fun `when there image upload errors, then show a cta to open upload error screen`() = testBlocking { + val errorEvents = MutableSharedFlow>() + doReturn(errorEvents).whenever(mediaFileUploadHandler).observeCurrentUploadErrors(PRODUCT_REMOTE_ID) + doReturn(productAggregate).whenever(productRepository).fetchAndGetProductAggregate(any()) + doReturn(productAggregate).whenever(productRepository).getProductAggregate(any()) + var productData: ProductDetailViewModel.ProductDetailViewState? = null + viewModel.productDetailViewStateData.observeForever { _, new -> productData = new } + + viewModel.start() + val errors = listOf( + MediaFileUploadHandler.ProductImageUploadData( + PRODUCT_REMOTE_ID, + "uri", + MediaFileUploadHandler.UploadStatus.Failed( + mediaErrorType = MediaStore.MediaErrorType.GENERIC_ERROR, + mediaErrorMessage = "error" + ) + ) + ) + errorEvents.emit(errors) + + Assertions.assertThat(productData?.hasUploadErrors).isTrue() + } + @Test fun `when image uploads gets cleared, then auto-dismiss the snackbar`() = testBlocking { val errorEvents = MutableSharedFlow>() @@ -1291,25 +1317,26 @@ class ProductDetailViewModelTest : BaseUnitTest() { } @Test - fun `When converting from simple subscription to variable subscription product, subscription data is preserved`() = testBlocking { - // GIVEN - val subscriptionProduct = productAggregate.copy( - product = productAggregate.product.copy( - type = ProductType.SUBSCRIPTION.value - ), - subscription = ProductHelper.getDefaultSubscriptionDetails() - ) - doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) - viewModel.start() + fun `When converting from simple subscription to variable subscription product, subscription data is preserved`() = + testBlocking { + // GIVEN + val subscriptionProduct = productAggregate.copy( + product = productAggregate.product.copy( + type = ProductType.SUBSCRIPTION.value + ), + subscription = ProductHelper.getDefaultSubscriptionDetails() + ) + doReturn(subscriptionProduct).whenever(productRepository).getProductAggregate(any()) + viewModel.start() - val originalSubscription = viewModel.getProduct().subscriptionDraft + val originalSubscription = viewModel.getProduct().subscriptionDraft - // WHEN - viewModel.onProductTypeChanged(ProductType.VARIABLE_SUBSCRIPTION, false) + // WHEN + viewModel.onProductTypeChanged(ProductType.VARIABLE_SUBSCRIPTION, false) - // THEN - Assertions.assertThat(viewModel.getProduct().subscriptionDraft).isEqualTo(originalSubscription) - } + // THEN + Assertions.assertThat(viewModel.getProduct().subscriptionDraft).isEqualTo(originalSubscription) + } private val productsDraft get() = viewModel.productDetailViewStateData.liveData.value?.productDraft From 1557aa5f47762dd8474bd2619601185cbe720e49 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 30 Jan 2025 14:14:35 +0100 Subject: [PATCH 14/16] Clear image upload errors when exiting product details discarding changes --- .../android/ui/products/details/ProductDetailViewModel.kt | 1 + 1 file changed, 1 insertion(+) 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 3d56f799084..7db11033da2 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 @@ -931,6 +931,7 @@ class ProductDetailViewModel @Inject constructor( val positiveAction = DialogInterface.OnClickListener { _, _ -> // Make sure to cancel any remaining image uploads mediaFileUploadHandler.cancelUpload(getRemoteProductId()) + mediaFileUploadHandler.clearImageErrors(getRemoteProductId()) triggerEvent(ProductNavigationTarget.ExitProduct) } From d7c0eef50405f76ff2a273d432f65143aa880740 Mon Sep 17 00:00:00 2001 From: Hafiz Rahman Date: Fri, 31 Jan 2025 09:31:22 +0700 Subject: [PATCH 15/16] Update button placement on product details. --- .../res/layout/fragment_product_detail.xml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/res/layout/fragment_product_detail.xml b/WooCommerce/src/main/res/layout/fragment_product_detail.xml index 536503a98f0..a3083f833b4 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_detail.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_detail.xml @@ -109,6 +109,19 @@ + - - Date: Fri, 31 Jan 2025 11:46:02 +0100 Subject: [PATCH 16/16] Extract clearing upload error to view model enqueueUpload() function should not be responsible for clearing past errors from the upload status. --- .../android/ui/media/MediaFileUploadHandler.kt | 3 +-- .../android/ui/media/MediaUploadErrorListViewModel.kt | 1 + .../ui/media/MediaUploadErrorListViewModelTest.kt | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt index d727c9c5b0b..f96edefc0b3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaFileUploadHandler.kt @@ -183,7 +183,6 @@ class MediaFileUploadHandler @Inject constructor( uris.forEach { worker.enqueueWork(Work.FetchMedia(remoteProductId, it)) } - clearImageError(remoteProductId, uris) } fun cancelUpload(remoteProductId: Long) { @@ -201,7 +200,7 @@ class MediaFileUploadHandler @Inject constructor( notificationHandler.removeUploadFailureNotification(remoteProductId) } - private fun clearImageError(remoteProductId: Long, uris: List) { + fun clearImageErrors(remoteProductId: Long, uris: List) { uploadsStatus.update { list -> list.filterNot { it.remoteProductId == remoteProductId && diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt index 61fe12d4bc4..e973f91327d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModel.kt @@ -70,6 +70,7 @@ class MediaUploadErrorListViewModel @Inject constructor( fun onRetryUploadClicked(error: ErrorUiModel) { mediaFileUploadHandler.enqueueUpload(navArgs.remoteProductId, listOf(error.localUri)) + mediaFileUploadHandler.clearImageErrors(navArgs.remoteProductId, listOf(error.localUri)) _viewState.update { _viewState.value.copy( uploadErrorList = _viewState.value.uploadErrorList - error diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModelTest.kt index 356229f1d93..962a3e4f1ac 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/media/MediaUploadErrorListViewModelTest.kt @@ -100,6 +100,16 @@ class MediaUploadErrorListViewModelTest : BaseUnitTest() { } } + @Test + fun `given some upload errors, when onRetryUploadClicked is called, then clear that upload error`() = + testBlocking { + createViewModel(SOME_UPLOAD_ERRORS) + + viewModel.onRetryUploadClicked(SOME_UI_MODEL_ERRORS.last()) + + verify(mediaFileUploadHandler).clearImageErrors(REMOTE_PRODUCT_ID, listOf("file://test2.jpg")) + } + private fun createViewModel(errors: Array? = SOME_UPLOAD_ERRORS) { viewModel = MediaUploadErrorListViewModel( resourceProvider,