diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index e601470d2ae..c276f37ecbb 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -7,6 +7,7 @@ 17.3 ----- - [*] [Internal] Enhanced user experience in shipping label creation with automatic scrolling to the first invalid field upon form submission failure [https://github.com/woocommerce/woocommerce-android/pull/10657] +- [*] [Internal] Enhanced product variation delete confirmation dialog visibility and functionality across device rotations [https://github.com/woocommerce/woocommerce-android/pull/10664] - [*] [Internal] Added a text along with the receipts file when it is shared [https://github.com/woocommerce/woocommerce-android/pull/10681] 17.2 diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt new file mode 100644 index 00000000000..e484ec323b0 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt @@ -0,0 +1,15 @@ +package com.woocommerce.android.ui.dialog + +import android.os.Parcelable +import androidx.annotation.StringRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DialogParams( + @StringRes val titleId: Int? = null, + @StringRes val messageId: Int? = null, + @StringRes val positiveButtonId: Int? = null, + @StringRes val negativeButtonId: Int? = null, + @StringRes val neutralButtonId: Int? = null, + val cancelable: Boolean = true +) : Parcelable diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt new file mode 100644 index 00000000000..83dd8d2c247 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt @@ -0,0 +1,60 @@ +package com.woocommerce.android.ui.dialog + +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class WooDialogFragment : DialogFragment() { + + private var dialogInteractionListener: DialogInteractionListener? = null + + fun setDialogInteractionListener(listener: DialogInteractionListener) { + dialogInteractionListener = listener + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + @Suppress("DEPRECATION") val params = requireArguments().getParcelable(ARG_DIALOG_PARAMS)!! + + val builder = MaterialAlertDialogBuilder(requireContext()) + .setCancelable(params.cancelable) + + params.titleId?.let { builder.setTitle(it) } + params.messageId?.let { builder.setMessage(it) } + params.positiveButtonId?.let { posId -> + builder.setPositiveButton(posId) { _, _ -> + dialogInteractionListener?.onPositiveButtonClicked() + } + } + params.negativeButtonId?.let { negId -> + builder.setNegativeButton(negId) { _, _ -> + dialogInteractionListener?.onNegativeButtonClicked() + } + } + params.neutralButtonId?.let { neutId -> + builder.setNeutralButton(neutId) { _, _ -> + dialogInteractionListener?.onNeutralButtonClicked() + } + } + + return builder.create() + } + + interface DialogInteractionListener { + fun onPositiveButtonClicked() + fun onNegativeButtonClicked() + fun onNeutralButtonClicked() + } + + companion object { + const val ARG_DIALOG_PARAMS = "dialog_params" + const val TAG = "WooDialogFragment" + fun newInstance(params: DialogParams): WooDialogFragment { + return WooDialogFragment().apply { + arguments = Bundle().apply { + putParcelable(ARG_DIALOG_PARAMS, params) + } + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt index 20f3f4a9d24..ccf61aacef7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt @@ -31,6 +31,8 @@ import com.woocommerce.android.model.VariantOption import com.woocommerce.android.ui.aztec.AztecEditorFragment import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver +import com.woocommerce.android.ui.dialog.WooDialogFragment +import com.woocommerce.android.ui.dialog.WooDialogFragment.DialogInteractionListener import com.woocommerce.android.ui.main.MainActivity.Companion.BackPressListener import com.woocommerce.android.ui.products.BaseProductEditorFragment import com.woocommerce.android.ui.products.ProductInventoryViewModel.InventoryData @@ -48,6 +50,7 @@ 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.ShowDialogFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.widgets.CustomProgressDialog import com.woocommerce.android.widgets.SkeletonView @@ -61,7 +64,8 @@ class VariationDetailFragment : BaseFragment(R.layout.fragment_variation_detail), BackPressListener, OnGalleryImageInteractionListener, - MenuProvider { + MenuProvider, + DialogInteractionListener { companion object { private const val LIST_STATE_KEY = "list_state" const val KEY_VARIATION_DETAILS_RESULT = "key_variation_details_result" @@ -95,11 +99,18 @@ class VariationDetailFragment : _binding = FragmentVariationDetailBinding.bind(view) + reattachDialogInteractionListener() + requireActivity().addMenuProvider(this, viewLifecycleOwner) initializeViews(savedInstanceState) initializeViewModel() } + private fun reattachDialogInteractionListener() { + val dialogFragment = parentFragmentManager.findFragmentByTag(WooDialogFragment.TAG) as? WooDialogFragment + dialogFragment?.setDialogInteractionListener(this) + } + override fun onDestroyView() { skeletonView.hide() imageUploadErrorsSnackbar?.dismiss() @@ -276,12 +287,25 @@ class VariationDetailFragment : is ExitWithResult<*> -> navigateBackWithResult(KEY_VARIATION_DETAILS_RESULT, event.data) is ShowDialog -> event.showDialog() + is ShowDialogFragment -> event.showIn(parentFragmentManager, this) is Exit -> requireActivity().onBackPressedDispatcher.onBackPressed() else -> event.isHandled = false } } } + override fun onPositiveButtonClicked() { + viewModel.onDeleteVariationConfirmed() + } + + override fun onNegativeButtonClicked() { + viewModel.onDeleteVariationCancelled() + } + + override fun onNeutralButtonClicked() { + // no-op + } + private fun showVariationDetails(variation: ProductVariation) { if (variation.image == null && !viewModel.isUploadingImages()) { binding.imageGallery.hide() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt index f3d5941718f..8b502da15bd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt @@ -131,18 +131,7 @@ class VariationDetailViewModel @Inject constructor( fun onDeleteVariationClicked() { triggerEvent( - Event.ShowDialog( - positiveBtnAction = { _, _ -> - AnalyticsTracker.track( - AnalyticsEvent.PRODUCT_VARIATION_REMOVE_BUTTON_TAPPED, - mapOf(KEY_PRODUCT_ID to viewState.parentProduct?.remoteId) - ) - viewState = viewState.copy(isConfirmingDeletion = false) - deleteVariation() - }, - negativeBtnAction = { _, _ -> - viewState = viewState.copy(isConfirmingDeletion = false) - }, + Event.ShowDialogFragment( messageId = string.variation_confirm_delete, positiveButtonId = string.delete, negativeButtonId = string.cancel @@ -150,6 +139,19 @@ class VariationDetailViewModel @Inject constructor( ) } + fun onDeleteVariationConfirmed() { + AnalyticsTracker.track( + AnalyticsEvent.PRODUCT_VARIATION_REMOVE_BUTTON_TAPPED, + mapOf(KEY_PRODUCT_ID to viewState.parentProduct?.remoteId) + ) + viewState = viewState.copy(isConfirmingDeletion = false) + deleteVariation() + } + + fun onDeleteVariationCancelled() { + viewState = viewState.copy(isConfirmingDeletion = false) + } + fun onExit() { when { isUploadingImages() -> { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt index 2c8dbcca8d8..88a8e23f765 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt @@ -12,7 +12,9 @@ import com.google.android.material.snackbar.Snackbar import com.woocommerce.android.R.string import com.woocommerce.android.model.UiString import com.woocommerce.android.support.help.HelpOrigin +import com.woocommerce.android.ui.dialog.DialogParams import com.woocommerce.android.ui.dialog.WooDialog +import com.woocommerce.android.ui.dialog.WooDialogFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import java.util.concurrent.atomic.AtomicBoolean @@ -129,6 +131,33 @@ open class MultiLiveEvent : MutableLiveData() { data class LaunchUrlInChromeTab(val url: String) : Event() + data class ShowDialogFragment( + @StringRes val titleId: Int? = null, + @StringRes val messageId: Int? = null, + @StringRes val positiveButtonId: Int? = null, + @StringRes val negativeButtonId: Int? = null, + @StringRes val neutralButtonId: Int? = null, + val cancelable: Boolean = true + ) : Event() { + fun showIn( + fragmentManager: androidx.fragment.app.FragmentManager, + listener: WooDialogFragment.DialogInteractionListener + ) { + val dialogParams = DialogParams( + titleId = titleId, + messageId = messageId, + positiveButtonId = positiveButtonId, + negativeButtonId = negativeButtonId, + neutralButtonId = neutralButtonId, + cancelable = cancelable + ) + val dialogFragment = WooDialogFragment.newInstance(dialogParams).apply { + setDialogInteractionListener(listener) + } + dialogFragment.show(fragmentManager, WooDialogFragment.TAG) + } + } + data class ShowDialog( @StringRes val titleId: Int? = null, @StringRes val messageId: Int? = null,