From 3b7d2d601e7d62c0a7cc912acbc9a207f513254b Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 15:29:31 +0200 Subject: [PATCH 01/48] Removed unused flag --- .../main/kotlin/com/woocommerce/android/AppPrefs.kt | 11 ----------- .../kotlin/com/woocommerce/android/AppPrefsWrapper.kt | 2 -- 2 files changed, 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index 4abf337fc9f..8e07d5a77ef 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -118,7 +118,6 @@ object AppPrefs { NUMBER_OF_TIMES_AI_DESCRIPTION_TOOLTIP_SHOWN, STORE_CREATION_PROFILER_ANSWERS, AI_CONTENT_GENERATION_TONE, - AI_PRODUCT_CREATION_IS_FIRST_ATTEMPT, BLAZE_FIRST_TIME_WITHOUT_CAMPAIGN, BLAZE_CAMPAIGN_CREATED, BLAZE_CELEBRATION_SCREEN_SHOWN, @@ -1013,16 +1012,6 @@ object AppPrefs { value = value.slug ) - var aiProductCreationIsFirstAttempt: Boolean - get() = getBoolean( - key = DeletablePrefKey.AI_PRODUCT_CREATION_IS_FIRST_ATTEMPT, - default = true - ) - set(value) = setBoolean( - key = DeletablePrefKey.AI_PRODUCT_CREATION_IS_FIRST_ATTEMPT, - value = value - ) - var isBlazeCelebrationScreenShown: Boolean get() = getBoolean( key = DeletablePrefKey.BLAZE_CELEBRATION_SCREEN_SHOWN, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt index e4d61019ff5..3befd5d5447 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -20,8 +20,6 @@ class AppPrefsWrapper @Inject constructor() { var aiContentGenerationTone by AppPrefs::aiContentGenerationTone - var aiProductCreationIsFirstAttempt by AppPrefs::aiProductCreationIsFirstAttempt - var isBlazeCelebrationScreenShown by AppPrefs::isBlazeCelebrationScreenShown var blazeFirstTimeWithoutCampaign by AppPrefs::blazeFirstTimeWithoutCampaign From 987d45c863ca8046db3f025dd579b3a51ad8eb63 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 15:35:36 +0200 Subject: [PATCH 02/48] Moved the AI Product creation banner to the products/ai package --- .../ai/banner/AIProductBannerDialogFragment.kt} | 10 +++++----- .../ai/banner/AIProductBannerDialogScreen.kt} | 6 +++--- .../ai/banner/AIProductBannerDialogViewModel.kt} | 4 ++-- WooCommerce/src/main/res/navigation/nav_graph_main.xml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/{dashboard/AIProductDescriptionDialogFragment.kt => products/ai/banner/AIProductBannerDialogFragment.kt} (86%) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/{dashboard/AIProductDescriptionDialog.kt => products/ai/banner/AIProductBannerDialogScreen.kt} (95%) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/{dashboard/AIProductDescriptionDialogViewModel.kt => products/ai/banner/AIProductBannerDialogViewModel.kt} (84%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt similarity index 86% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogFragment.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt index 4302a25f2d8..6f54378bc2c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.dashboard +package com.woocommerce.android.ui.products.ai.banner import android.os.Bundle import android.view.LayoutInflater @@ -13,19 +13,19 @@ import com.woocommerce.android.NavGraphMainDirections import com.woocommerce.android.R.style import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground -import com.woocommerce.android.ui.dashboard.AIProductDescriptionDialogViewModel.TryAIProductDescriptionGeneration +import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogViewModel.TryAIProductDescriptionGeneration import com.woocommerce.android.ui.products.details.ProductDetailFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.util.DisplayUtils @AndroidEntryPoint -class AIProductDescriptionDialogFragment : DialogFragment() { +class AIProductBannerDialogFragment : DialogFragment() { companion object { private const val TABLET_LANDSCAPE_WIDTH_RATIO = 0.35f } - private val viewModel: AIProductDescriptionDialogViewModel by viewModels() + private val viewModel: AIProductBannerDialogViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -42,7 +42,7 @@ class AIProductDescriptionDialogFragment : DialogFragment() { setContent { WooThemeWithBackground { - AIProductDescriptionDialog(viewModel::onTryNowButtonClicked, viewModel::onDismissButtonClicked) + AIProductDescriptionDialogScreen(viewModel::onTryNowButtonClicked, viewModel::onDismissButtonClicked) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialog.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogScreen.kt similarity index 95% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialog.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogScreen.kt index 54e2171882e..73c002f38fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialog.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogScreen.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.dashboard +package com.woocommerce.android.ui.products.ai.banner import android.content.res.Configuration import androidx.compose.foundation.Image @@ -24,7 +24,7 @@ import com.woocommerce.android.ui.compose.component.WCOutlinedButton import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground @Composable -fun AIProductDescriptionDialog( +fun AIProductDescriptionDialogScreen( onTryNowButtonClick: () -> Unit, onMaybeLaterButtonClick: () -> Unit ) { @@ -79,6 +79,6 @@ fun AIProductDescriptionDialog( @Composable fun PreviewAIProductDescriptionDialog() { WooThemeWithBackground { - AIProductDescriptionDialog({}, {}) + AIProductDescriptionDialogScreen({}, {}) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt similarity index 84% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogViewModel.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt index 3eeeca490e7..1b099eb2c9c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.dashboard +package com.woocommerce.android.ui.products.ai.banner import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -8,7 +8,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel -class AIProductDescriptionDialogViewModel @Inject constructor( +class AIProductBannerDialogViewModel @Inject constructor( savedState: SavedStateHandle ) : ScopedViewModel(savedState) { fun onTryNowButtonClicked() { diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index d2e7483e45f..c0c57bf4064 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -750,7 +750,7 @@ app:destination="@id/privacyBannerFragment" /> Date: Thu, 26 Sep 2024 15:47:35 +0200 Subject: [PATCH 03/48] Removed showing the AI banner on the dashboard --- .../android/ui/dashboard/DashboardFragment.kt | 6 ------ .../android/ui/dashboard/DashboardViewModel.kt | 13 +------------ .../banner/AIProductBannerDialogShouldBeShown.kt | 16 ++++++++++++++++ .../ai/banner/AIProductBannerDialogViewModel.kt | 11 ++++++++++- .../src/main/res/navigation/nav_graph_main.xml | 7 ++++--- 5 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt index f66d90f2c27..e9eb982ea13 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFragment.kt @@ -52,7 +52,6 @@ import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.Fe import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.OpenEditWidgets import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.OpenRangePicker import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ShareStore -import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ShowAIProductDescriptionDialog import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ShowPrivacyBanner import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUiModel import com.woocommerce.android.ui.google.webview.GoogleAdsWebViewFragment @@ -173,11 +172,6 @@ class DashboardFragment : is ShareStore -> ActivityUtils.shareStoreUrl(requireActivity(), event.storeUrl) - is ShowAIProductDescriptionDialog -> - findNavController().navigateSafely( - DashboardFragmentDirections.actionDashboardToAIProductDescriptionDialogFragment() - ) - is OpenEditWidgets -> { findNavController().navigateSafely( DashboardFragmentDirections.actionDashboardToEditWidgetsFragment() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt index e1fdcc99f6c..c6fdb3e0c33 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardViewModel.kt @@ -14,7 +14,6 @@ import com.woocommerce.android.analytics.AnalyticsEvent.DYNAMIC_DASHBOARD_CARD_I import com.woocommerce.android.analytics.AnalyticsEvent.FEATURE_JETPACK_BENEFITS_BANNER import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTrackerWrapper -import com.woocommerce.android.extensions.isEligibleForAI import com.woocommerce.android.extensions.isSitePublic import com.woocommerce.android.model.DashboardWidget import com.woocommerce.android.model.UiString @@ -30,7 +29,6 @@ import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardWidgetUi import com.woocommerce.android.ui.dashboard.data.DashboardRepository import com.woocommerce.android.ui.prefs.privacy.banner.domain.ShouldShowPrivacyBanner import com.woocommerce.android.util.PackageUtils -import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel @@ -131,13 +129,6 @@ class DashboardViewModel @Inject constructor( } } - if (selectedSite.getOrNull()?.isEligibleForAI == true && - !appPrefsWrapper.wasAIProductDescriptionPromoDialogShown - ) { - triggerEvent(DashboardEvent.ShowAIProductDescriptionDialog) - appPrefsWrapper.wasAIProductDescriptionPromoDialogShown = true - } - updateShareStoreButtonVisibility() } @@ -332,12 +323,10 @@ class DashboardViewModel @Inject constructor( val showShareStoreButton: Boolean = false, ) - sealed class DashboardEvent : MultiLiveEvent.Event() { + sealed class DashboardEvent : Event() { data object ShowPrivacyBanner : DashboardEvent() - data object ShowAIProductDescriptionDialog : DashboardEvent() - data class ShareStore(val storeUrl: String) : DashboardEvent() data object OpenEditWidgets : DashboardEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt new file mode 100644 index 00000000000..593438e947c --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt @@ -0,0 +1,16 @@ +package com.woocommerce.android.ui.products.ai.banner + +import com.woocommerce.android.AppPrefsWrapper +import com.woocommerce.android.extensions.isEligibleForAI +import com.woocommerce.android.tools.SelectedSite +import javax.inject.Inject + +class AIProductBannerDialogShouldBeShown @Inject constructor( + private val selectedSite: SelectedSite, + private val appPrefsWrapper: AppPrefsWrapper, +) { + operator fun invoke(): Boolean { + return selectedSite.getOrNull()?.isEligibleForAI == true && + !appPrefsWrapper.wasAIProductDescriptionPromoDialogShown + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt index 1b099eb2c9c..adad8c49487 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt @@ -1,16 +1,25 @@ package com.woocommerce.android.ui.products.ai.banner import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.ScopedViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class AIProductBannerDialogViewModel @Inject constructor( - savedState: SavedStateHandle + savedState: SavedStateHandle, + appPrefsWrapper: AppPrefsWrapper ) : ScopedViewModel(savedState) { + init { + launch { + appPrefsWrapper.wasAIProductDescriptionPromoDialogShown = true + } + } + fun onTryNowButtonClicked() { triggerEvent(TryAIProductDescriptionGeneration) } diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index c0c57bf4064..0996fcf3816 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -72,9 +72,6 @@ - @@ -223,6 +220,10 @@ app:exitAnim="@null" app:popEnterAnim="@null" app:popExitAnim="@anim/activity_fade_out" /> + + Date: Thu, 26 Sep 2024 15:53:05 +0200 Subject: [PATCH 04/48] Move events from the VM --- .../ui/products/list/ProductListEvent.kt | 38 ++++++++++++++ .../ui/products/list/ProductListFragment.kt | 20 +++---- .../ui/products/list/ProductListViewModel.kt | 52 +++++-------------- .../products/list/ProductListViewModelTest.kt | 6 +-- 4 files changed, 63 insertions(+), 53 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt new file mode 100644 index 00000000000..3f1a157976c --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt @@ -0,0 +1,38 @@ +package com.woocommerce.android.ui.products.list + +import android.view.View +import com.woocommerce.android.viewmodel.MultiLiveEvent + +sealed class ProductListEvent : MultiLiveEvent.Event() { + data object ScrollToTop : ProductListEvent() + data object ShowAddProductBottomSheet : ProductListEvent() + data object ShowProductSortingBottomSheet : ProductListEvent() + data class ShowProductFilterScreen( + val stockStatusFilter: String?, + val productTypeFilter: String?, + val productStatusFilter: String?, + val productCategoryFilter: String?, + val selectedCategoryName: String? + ) : ProductListEvent() + data class ShowProductUpdateStockStatusScreen(val productsIds: List) : ProductListEvent() + sealed class ShowUpdateDialog : ProductListEvent() { + abstract val productsIds: List + + data class Price(override val productsIds: List) : ShowUpdateDialog() + data class Status(override val productsIds: List) : ShowUpdateDialog() + } + data class ShowDiscardProductChangesConfirmationDialog( + val productId: Long, + val productName: String, + ) : ProductListEvent() + data class OpenProduct( + val productId: Long, + val oldPosition: Int, + val newPosition: Int, + val sharedView: View? + ) : ProductListEvent() + + data object OpenEmptyProduct : ProductListEvent() + + data class SelectProducts(val productsIds: List) : ProductListEvent() +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt index 752b3116b11..3e3c32bca50 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt @@ -52,16 +52,16 @@ import com.woocommerce.android.ui.products.UpdateProductStockStatusViewModel.Upd import com.woocommerce.android.ui.products.details.ProductDetailFragment import com.woocommerce.android.ui.products.details.ProductDetailFragmentArgs import com.woocommerce.android.ui.products.filter.ProductFilterResult -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.OpenEmptyProduct -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.OpenProduct -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ScrollToTop -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.SelectProducts -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowAddProductBottomSheet -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowDiscardProductChangesConfirmationDialog -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowProductFilterScreen -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowProductSortingBottomSheet -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowProductUpdateStockStatusScreen -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowUpdateDialog +import com.woocommerce.android.ui.products.list.ProductListEvent.OpenEmptyProduct +import com.woocommerce.android.ui.products.list.ProductListEvent.OpenProduct +import com.woocommerce.android.ui.products.list.ProductListEvent.ScrollToTop +import com.woocommerce.android.ui.products.list.ProductListEvent.SelectProducts +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAddProductBottomSheet +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowDiscardProductChangesConfirmationDialog +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductFilterScreen +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductSortingBottomSheet +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductUpdateStockStatusScreen +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowUpdateDialog import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.StringUtils import com.woocommerce.android.util.TabletLayoutSetupHelper diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt index f9974ec37bc..e8e11a93be2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt @@ -21,12 +21,13 @@ import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.media.MediaFileUploadHandler import com.woocommerce.android.ui.products.ProductStatus -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ScrollToTop -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.SelectProducts -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowAddProductBottomSheet -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowProductFilterScreen -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowProductSortingBottomSheet -import com.woocommerce.android.ui.products.list.ProductListViewModel.ProductListEvent.ShowUpdateDialog +import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogShouldBeShown +import com.woocommerce.android.ui.products.list.ProductListEvent.ScrollToTop +import com.woocommerce.android.ui.products.list.ProductListEvent.SelectProducts +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAddProductBottomSheet +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductFilterScreen +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductSortingBottomSheet +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowUpdateDialog import com.woocommerce.android.util.IsWindowClassLargeThanCompact import com.woocommerce.android.util.WooLog import com.woocommerce.android.viewmodel.LiveDataDelegate @@ -59,6 +60,7 @@ class ProductListViewModel @Inject constructor( private val selectedSite: SelectedSite, private val wooCommerceStore: WooCommerceStore, private val isWindowClassLargeThanCompact: IsWindowClassLargeThanCompact, + private val aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown ) : ScopedViewModel(savedState) { companion object { private const val KEY_PRODUCT_FILTER_OPTIONS = "key_product_filter_options" @@ -109,6 +111,10 @@ class ProductListViewModel @Inject constructor( mediaFileUploadHandler.observeProductImageChanges() .onEach { loadProducts() } .launchIn(this) + + if (aiProductBannerDialogShouldBeShown()) { + triggerEvent(MultiLiveEvent.Event.ShowAIProductBannerDialog) + } } override fun onCleared() { @@ -765,39 +771,5 @@ class ProductListViewModel @Inject constructor( val isFilteringActive = filterCount != null && filterCount > 0 } - sealed class ProductListEvent : MultiLiveEvent.Event() { - data object ScrollToTop : ProductListEvent() - data object ShowAddProductBottomSheet : ProductListEvent() - data object ShowProductSortingBottomSheet : ProductListEvent() - data class ShowProductFilterScreen( - val stockStatusFilter: String?, - val productTypeFilter: String?, - val productStatusFilter: String?, - val productCategoryFilter: String?, - val selectedCategoryName: String? - ) : ProductListEvent() - data class ShowProductUpdateStockStatusScreen(val productsIds: List) : ProductListEvent() - sealed class ShowUpdateDialog : ProductListEvent() { - abstract val productsIds: List - - data class Price(override val productsIds: List) : ShowUpdateDialog() - data class Status(override val productsIds: List) : ShowUpdateDialog() - } - data class ShowDiscardProductChangesConfirmationDialog( - val productId: Long, - val productName: String, - ) : ProductListEvent() - data class OpenProduct( - val productId: Long, - val oldPosition: Int, - val newPosition: Int, - val sharedView: View? - ) : ProductListEvent() - - data object OpenEmptyProduct : ProductListEvent() - - data class SelectProducts(val productsIds: List) : ProductListEvent() - } - enum class ProductListState { Selecting, Browsing } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt index d07d55990f8..33843234325 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt @@ -315,7 +315,7 @@ class ProductListViewModelTest : BaseUnitTest() { viewModel.onFiltersButtonTapped() - assertThat(events.count { it is ProductListViewModel.ProductListEvent.ShowProductFilterScreen }).isEqualTo(1) + assertThat(events.count { it is ProductListEvent.ShowProductFilterScreen }).isEqualTo(1) } @Test @@ -336,7 +336,7 @@ class ProductListViewModelTest : BaseUnitTest() { viewModel.onFiltersButtonTapped() - val event = events.first() as ProductListViewModel.ProductListEvent.ShowProductFilterScreen + val event = events.first() as ProductListEvent.ShowProductFilterScreen assertThat(event.productStatusFilter).isEqualTo(status) assertThat(event.productTypeFilter).isEqualTo(type) assertThat(event.stockStatusFilter).isEqualTo(stockStatus) @@ -355,7 +355,7 @@ class ProductListViewModelTest : BaseUnitTest() { viewModel.onSortButtonTapped() - assertThat(events.count { it is ProductListViewModel.ProductListEvent.ShowProductSortingBottomSheet }) + assertThat(events.count { it is ProductListEvent.ShowProductSortingBottomSheet }) .isEqualTo(1) } From c68f390cd77121b247ed80ec18d7100a9442016e Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 15:54:16 +0200 Subject: [PATCH 05/48] Trigger ShowAIProductBannerDialog if banner shoulw be shown --- .../woocommerce/android/ui/products/list/ProductListEvent.kt | 2 ++ .../android/ui/products/list/ProductListViewModel.kt | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt index 3f1a157976c..01931ecc469 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt @@ -35,4 +35,6 @@ sealed class ProductListEvent : MultiLiveEvent.Event() { data object OpenEmptyProduct : ProductListEvent() data class SelectProducts(val productsIds: List) : ProductListEvent() + + data object ShowAIProductBannerDialog: ProductListEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt index e8e11a93be2..76ca9354dbf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt @@ -24,6 +24,7 @@ import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogShouldBeShown import com.woocommerce.android.ui.products.list.ProductListEvent.ScrollToTop import com.woocommerce.android.ui.products.list.ProductListEvent.SelectProducts +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAIProductBannerDialog import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAddProductBottomSheet import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductFilterScreen import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductSortingBottomSheet @@ -60,7 +61,7 @@ class ProductListViewModel @Inject constructor( private val selectedSite: SelectedSite, private val wooCommerceStore: WooCommerceStore, private val isWindowClassLargeThanCompact: IsWindowClassLargeThanCompact, - private val aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown + aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown ) : ScopedViewModel(savedState) { companion object { private const val KEY_PRODUCT_FILTER_OPTIONS = "key_product_filter_options" @@ -113,7 +114,7 @@ class ProductListViewModel @Inject constructor( .launchIn(this) if (aiProductBannerDialogShouldBeShown()) { - triggerEvent(MultiLiveEvent.Event.ShowAIProductBannerDialog) + triggerEvent(ShowAIProductBannerDialog) } } From 9e30b7c7e5b251fcea0b4a024ef3d1aa0d193ce9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 15:54:54 +0200 Subject: [PATCH 06/48] ProductListViewState to a separate class --- .../ui/products/list/ProductListViewModel.kt | 30 +------------------ .../ui/products/list/ProductListViewState.kt | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 29 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt index 76ca9354dbf..48d5cbb93c1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt @@ -1,6 +1,5 @@ package com.woocommerce.android.ui.products.list -import android.os.Parcelable import android.view.View import androidx.annotation.StringRes import androidx.lifecycle.LiveData @@ -42,8 +41,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import kotlinx.parcelize.IgnoredOnParcel -import kotlinx.parcelize.Parcelize import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -82,7 +79,7 @@ class ProductListViewModel @Inject constructor( * with @OptIn(LiveDelegateSavedStateAPI::class). */ @Suppress("OPT_IN_USAGE") - val viewStateLiveData = LiveDataDelegate(savedState, ViewState()) + val viewStateLiveData = LiveDataDelegate(savedState, ProductListViewState()) private var viewState by viewStateLiveData private val productFilterOptions: MutableMap by lazy { @@ -747,30 +744,5 @@ class ProductListViewModel @Inject constructor( object OnProductSortingChanged - @Parcelize - data class ViewState( - val isSkeletonShown: Boolean? = null, - val isLoading: Boolean? = null, - val isLoadingMore: Boolean? = null, - val canLoadMore: Boolean? = null, - val isRefreshing: Boolean? = null, - val query: String? = null, - val isSkuSearch: Boolean = false, - val filterCount: Int? = null, - val isSearchActive: Boolean? = null, - val isEmptyViewVisible: Boolean? = null, - val sortingTitleResource: Int? = null, - val displaySortAndFilterCard: Boolean? = null, - val isAddProductButtonVisible: Boolean? = null, - val productListState: ProductListState? = null, - val selectionCount: Int? = null - ) : Parcelable { - @IgnoredOnParcel - val isBottomNavBarVisible = isSearchActive != true && productListState != ProductListState.Selecting - - @IgnoredOnParcel - val isFilteringActive = filterCount != null && filterCount > 0 - } - enum class ProductListState { Selecting, Browsing } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt new file mode 100644 index 00000000000..bb117ddc155 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt @@ -0,0 +1,30 @@ +package com.woocommerce.android.ui.products.list + +import android.os.Parcelable +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize + +@Parcelize +data class ProductListViewState( + val isSkeletonShown: Boolean? = null, + val isLoading: Boolean? = null, + val isLoadingMore: Boolean? = null, + val canLoadMore: Boolean? = null, + val isRefreshing: Boolean? = null, + val query: String? = null, + val isSkuSearch: Boolean = false, + val filterCount: Int? = null, + val isSearchActive: Boolean? = null, + val isEmptyViewVisible: Boolean? = null, + val sortingTitleResource: Int? = null, + val displaySortAndFilterCard: Boolean? = null, + val isAddProductButtonVisible: Boolean? = null, + val productListState: ProductListViewModel.ProductListState? = null, + val selectionCount: Int? = null +) : Parcelable { + @IgnoredOnParcel + val isBottomNavBarVisible = isSearchActive != true && productListState != ProductListViewModel.ProductListState.Selecting + + @IgnoredOnParcel + val isFilteringActive = filterCount != null && filterCount > 0 +} From e89a0ec544055d2f6dfa0c6f95067042e2bd31f1 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 16:05:15 +0200 Subject: [PATCH 07/48] Show AI product creation banner with delay --- .../android/ui/products/list/ProductListFragment.kt | 13 ++++++++++--- .../ui/products/list/ProductListViewModel.kt | 12 +++++++----- .../ui/products/list/ProductListViewState.kt | 6 ++++-- .../src/main/res/navigation/nav_graph_main.xml | 8 ++++---- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt index 3e3c32bca50..7cc9ccd4f31 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt @@ -56,6 +56,7 @@ import com.woocommerce.android.ui.products.list.ProductListEvent.OpenEmptyProduc import com.woocommerce.android.ui.products.list.ProductListEvent.OpenProduct import com.woocommerce.android.ui.products.list.ProductListEvent.ScrollToTop import com.woocommerce.android.ui.products.list.ProductListEvent.SelectProducts +import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAIProductBannerDialog import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAddProductBottomSheet import com.woocommerce.android.ui.products.list.ProductListEvent.ShowDiscardProductChangesConfirmationDialog import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductFilterScreen @@ -403,6 +404,12 @@ class ProductListFragment : ) } + is ShowAIProductBannerDialog -> { + findNavController().navigateSafely( + ProductListFragmentDirections.actionProductListFragmentToAIProductBannerDialogFragment() + ) + } + else -> event.isHandled = false } } @@ -503,9 +510,9 @@ class ProductListFragment : .show() } - private fun handleListState(productListState: ProductListViewModel.ProductListState) { + private fun handleListState(productListState: ProductListViewState.ProductListState) { when (productListState) { - ProductListViewModel.ProductListState.Selecting -> { + ProductListViewState.ProductListState.Selecting -> { actionMode = (requireActivity() as AppCompatActivity) .startSupportActionMode(this@ProductListFragment) delayMultiSelection() @@ -513,7 +520,7 @@ class ProductListFragment : enableProductSortAndFiltersCard(false) } - ProductListViewModel.ProductListState.Browsing -> { + ProductListViewState.ProductListState.Browsing -> { actionMode?.finish() enableProductsRefresh(true) enableProductSortAndFiltersCard(true) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt index 48d5cbb93c1..91b5d82554e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt @@ -111,7 +111,10 @@ class ProductListViewModel @Inject constructor( .launchIn(this) if (aiProductBannerDialogShouldBeShown()) { - triggerEvent(ShowAIProductBannerDialog) + triggerEventWithDelay( + ShowAIProductBannerDialog, + delay = 500 + ) } } @@ -123,7 +126,7 @@ class ProductListViewModel @Inject constructor( fun isSearching() = viewState.isSearchActive == true - fun isSelecting() = viewState.productListState == ProductListState.Selecting + fun isSelecting() = viewState.productListState == ProductListViewState.ProductListState.Selecting fun isSkuSearch() = isSearching() && viewState.isSkuSearch @@ -504,7 +507,7 @@ class ProductListViewModel @Inject constructor( private fun enterSelectionMode(count: Int) { viewState = viewState.copy( - productListState = ProductListState.Selecting, + productListState = ProductListViewState.ProductListState.Selecting, isAddProductButtonVisible = false, selectionCount = count ) @@ -512,7 +515,7 @@ class ProductListViewModel @Inject constructor( fun exitSelectionMode() { viewState = viewState.copy( - productListState = ProductListState.Browsing, + productListState = ProductListViewState.ProductListState.Browsing, isAddProductButtonVisible = true, selectionCount = null ) @@ -744,5 +747,4 @@ class ProductListViewModel @Inject constructor( object OnProductSortingChanged - enum class ProductListState { Selecting, Browsing } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt index bb117ddc155..df85685301c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt @@ -19,12 +19,14 @@ data class ProductListViewState( val sortingTitleResource: Int? = null, val displaySortAndFilterCard: Boolean? = null, val isAddProductButtonVisible: Boolean? = null, - val productListState: ProductListViewModel.ProductListState? = null, + val productListState: ProductListState? = null, val selectionCount: Int? = null ) : Parcelable { @IgnoredOnParcel - val isBottomNavBarVisible = isSearchActive != true && productListState != ProductListViewModel.ProductListState.Selecting + val isBottomNavBarVisible = isSearchActive != true && productListState != ProductListState.Selecting @IgnoredOnParcel val isFilteringActive = filterCount != null && filterCount > 0 + + enum class ProductListState { Selecting, Browsing } } diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index 0996fcf3816..2b52b8140ea 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -222,8 +222,8 @@ app:popExitAnim="@anim/activity_fade_out" /> + android:id="@+id/action_productListFragment_to_AIProductBannerDialogFragment" + app:destination="@id/AIProductBannerDialogFragment" /> + android:label="AIProductBannerDialogFragment" /> Date: Thu, 26 Sep 2024 16:07:18 +0200 Subject: [PATCH 08/48] Fixed formatting --- .../ui/products/ai/banner/AIProductBannerDialogFragment.kt | 5 ++++- .../woocommerce/android/ui/products/list/ProductListEvent.kt | 5 ++++- .../android/ui/products/list/ProductListViewModel.kt | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt index 6f54378bc2c..90955e679b3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt @@ -42,7 +42,10 @@ class AIProductBannerDialogFragment : DialogFragment() { setContent { WooThemeWithBackground { - AIProductDescriptionDialogScreen(viewModel::onTryNowButtonClicked, viewModel::onDismissButtonClicked) + AIProductDescriptionDialogScreen( + viewModel::onTryNowButtonClicked, + viewModel::onDismissButtonClicked + ) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt index 01931ecc469..c7b3a6363e6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt @@ -14,6 +14,7 @@ sealed class ProductListEvent : MultiLiveEvent.Event() { val productCategoryFilter: String?, val selectedCategoryName: String? ) : ProductListEvent() + data class ShowProductUpdateStockStatusScreen(val productsIds: List) : ProductListEvent() sealed class ShowUpdateDialog : ProductListEvent() { abstract val productsIds: List @@ -21,10 +22,12 @@ sealed class ProductListEvent : MultiLiveEvent.Event() { data class Price(override val productsIds: List) : ShowUpdateDialog() data class Status(override val productsIds: List) : ShowUpdateDialog() } + data class ShowDiscardProductChangesConfirmationDialog( val productId: Long, val productName: String, ) : ProductListEvent() + data class OpenProduct( val productId: Long, val oldPosition: Int, @@ -36,5 +39,5 @@ sealed class ProductListEvent : MultiLiveEvent.Event() { data class SelectProducts(val productsIds: List) : ProductListEvent() - data object ShowAIProductBannerDialog: ProductListEvent() + data object ShowAIProductBannerDialog : ProductListEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt index 91b5d82554e..7836573a044 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt @@ -691,6 +691,7 @@ class ProductListViewModel @Inject constructor( delay = EXPAND_COLLAPSE_ANIMATION_DURATION_MILLIS ) } + else -> { exitSelectionMode() onFailure() @@ -746,5 +747,4 @@ class ProductListViewModel @Inject constructor( } object OnProductSortingChanged - } From 9a2d975b3747c7a866420e17b006b2d0051ad1da Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 26 Sep 2024 16:12:23 +0200 Subject: [PATCH 09/48] Add new objective field to campaign details --- .../com/woocommerce/android/ui/blaze/BlazeRepository.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt index 2d94bf3da21..9e3bb89bfa6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt @@ -191,7 +191,8 @@ class BlazeRepository @Inject constructor( destinationParameters = DestinationParameters( targetUrl = product.permalink, parameters = emptyMap() - ) + ), + objectiveId = "" ) } @@ -318,7 +319,8 @@ class BlazeRepository @Inject constructor( topics = it.interests.map { interest -> interest.id } ) }, - isEndlessCampaign = campaignDetails.budget.isEndlessCampaign + isEndlessCampaign = campaignDetails.budget.isEndlessCampaign, + objectiveId = campaignDetails.objectiveId ) ) @@ -384,6 +386,7 @@ class BlazeRepository @Inject constructor( val budget: Budget, val targetingParameters: TargetingParameters, val destinationParameters: DestinationParameters, + val objectiveId: String ) : Parcelable sealed interface BlazeCampaignImage : Parcelable { From f59d3fcf0e98c90c0196a554531a636549ae27eb Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 26 Sep 2024 16:18:05 +0200 Subject: [PATCH 10/48] Update FluxC changeset --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 774a74ac196..877a2437171 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-9a51a9dfcfdb247e15dc14ad025ddee9e3285ff8' + fluxCVersion = '3103-6a3c656de5377ca7677548baa0a259edfc9c9ffe' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 0cf55cedc0dcbf86de31b7aebe20d2fca96ee229 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 16:27:57 +0200 Subject: [PATCH 11/48] Added a test on the changes in the products list --- .../products/list/ProductListViewModelTest.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt index 33843234325..5f271b3bfe2 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt @@ -13,6 +13,7 @@ import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.media.MediaFileUploadHandler import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ProductTestUtils +import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogShouldBeShown import com.woocommerce.android.util.IsWindowClassLargeThanCompact import com.woocommerce.android.viewmodel.BaseUnitTest import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -47,6 +48,7 @@ class ProductListViewModelTest : BaseUnitTest() { private val wooCommerceStore: WooCommerceStore = mock() private val selectedSite: SelectedSite = mock() private val isWindowClassLargeThanCompact: IsWindowClassLargeThanCompact = mock() + private val aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown = mock() private val productList = ProductTestUtils.generateProductList() private lateinit var viewModel: ProductListViewModel @@ -68,6 +70,7 @@ class ProductListViewModelTest : BaseUnitTest() { selectedSite, wooCommerceStore, isWindowClassLargeThanCompact, + aiProductBannerDialogShouldBeShown, ) ) } @@ -697,4 +700,21 @@ class ProductListViewModelTest : BaseUnitTest() { mapOf("horizontal_size_class" to "compact") ) } + + @Test + fun `given ai product banner should be shown, when vm init, then ShowAIProductBannerDialog event emitted`() = testBlocking { + // given + whenever(aiProductBannerDialogShouldBeShown()).thenReturn(true) + + createViewModel() + + val events = mutableListOf() + viewModel.event.observeForever { + events.add(it) + } + advanceUntilIdle() + + // then + assertThat(events.count { it is ProductListEvent.ShowAIProductBannerDialog }).isEqualTo(1) + } } From 5cfad239b30776b2fc860c8a7e732636fb44e2b7 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 16:36:18 +0200 Subject: [PATCH 12/48] Tests on AIProductBannerDialogShouldBeShown --- .../AIProductBannerDialogShouldBeShownTest.kt | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt new file mode 100644 index 00000000000..70138b7c52a --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt @@ -0,0 +1,99 @@ +import com.woocommerce.android.AppPrefsWrapper +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogShouldBeShown +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.SiteModel + +class AIProductBannerDialogShouldBeShownTest { + private val selectedSite: SelectedSite = mock() + private val appPrefsWrapper: AppPrefsWrapper = mock() + + private lateinit var aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown + + @Before + fun setup() { + aiProductBannerDialogShouldBeShown = AIProductBannerDialogShouldBeShown( + selectedSite, + appPrefsWrapper + ) + } + + @Test + fun `given site is WPComAtomic, when AI promo dialog was not shown, then should return true`() { + // given + val site = mock() + whenever(site.isWPComAtomic).thenReturn(true) + whenever(site.planActiveFeatures).thenReturn("") + whenever(selectedSite.getOrNull()).thenReturn(site) + whenever(appPrefsWrapper.wasAIProductDescriptionPromoDialogShown).thenReturn(false) + + // when + val result = aiProductBannerDialogShouldBeShown() + + // then + assertThat(result).isTrue + } + + @Test + fun `given site has AI assistant in plan features, when AI promo dialog was not shown, then should return true`() { + // given + val site = mock() + whenever(site.isWPComAtomic).thenReturn(false) + whenever(site.planActiveFeatures).thenReturn("ai-assistant") + whenever(selectedSite.getOrNull()).thenReturn(site) + whenever(appPrefsWrapper.wasAIProductDescriptionPromoDialogShown).thenReturn(false) + + // when + val result = aiProductBannerDialogShouldBeShown() + + // then + assertThat(result).isTrue + } + + @Test + fun `given site is not eligible for AI, when checking if dialog should be shown, then should return false`() { + // given + val site = mock() + whenever(site.isWPComAtomic).thenReturn(false) + whenever(site.planActiveFeatures).thenReturn("") + whenever(selectedSite.getOrNull()).thenReturn(site) + + // when + val result = aiProductBannerDialogShouldBeShown() + + // then + assertThat(result).isFalse + } + + @Test + fun `given AI promo dialog was already shown, when site is eligible for AI, then should return false`() { + // given + val site = mock() + whenever(site.isWPComAtomic).thenReturn(true) + whenever(site.planActiveFeatures).thenReturn("") + whenever(selectedSite.getOrNull()).thenReturn(site) + whenever(appPrefsWrapper.wasAIProductDescriptionPromoDialogShown).thenReturn(true) + + // when + val result = aiProductBannerDialogShouldBeShown() + + // then + assertThat(result).isFalse + } + + @Test + fun `given no site is selected, when checking if dialog should be shown, then should return false`() { + // given + whenever(selectedSite.getOrNull()).thenReturn(null) + + // when + val result = aiProductBannerDialogShouldBeShown() + + // then + assertThat(result).isFalse + } +} From e9a09dc8e99dc6629a24cee1d4a8551e26239dee Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 26 Sep 2024 16:42:26 +0200 Subject: [PATCH 13/48] Updated release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 19536d66eeb..abd42a5c1e8 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -4,6 +4,7 @@ 20.7 ----- - [**] Improve barcode scanner reading accuracy [https://github.com/woocommerce/woocommerce-android/pull/12673] +- [Internal] AI product creation banner is in products now [https://github.com/woocommerce/woocommerce-android/pull/12705] 20.6 ----- From fa4e0d7e1a4e937b221e08d879ceaaf55536feac Mon Sep 17 00:00:00 2001 From: Rooney Date: Thu, 26 Sep 2024 17:07:14 -0400 Subject: [PATCH 14/48] Update the string --- WooCommerce/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index beb06df356e..668ba106aa7 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -1566,7 +1566,7 @@ 1. Create an order 2. Tap “Collect Payment” and choose “Tap to Pay”. 3. Present your phone to the customer. - 4. Your customer holds their card horizontally at the top of your phone, over the contactless symbol. + 4. Your customer taps their card on the back of your phone, over the contactless symbol. 5. After you see the “Done” checkmark, your store will process the payment, and the transaction will be complete. The Contactless Symbol is a trademark owned by and used with permission of EMVCo, LLC. From 10b8ef1692c421a77c1959ba5259c5513c9fe3a6 Mon Sep 17 00:00:00 2001 From: Rooney Date: Thu, 26 Sep 2024 17:07:47 -0400 Subject: [PATCH 15/48] Make more clear --- WooCommerce/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 668ba106aa7..a3c71a3a07e 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -1566,7 +1566,7 @@ 1. Create an order 2. Tap “Collect Payment” and choose “Tap to Pay”. 3. Present your phone to the customer. - 4. Your customer taps their card on the back of your phone, over the contactless symbol. + 4. Your customer taps their card on the back of your phone. 5. After you see the “Done” checkmark, your store will process the payment, and the transaction will be complete. The Contactless Symbol is a trademark owned by and used with permission of EMVCo, LLC. From f5f29eaf9980bebb9c868a7a677165b603fc5f60 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 25 Sep 2024 18:10:33 +0100 Subject: [PATCH 16/48] Bump fluxc --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 774a74ac196..508012d3f68 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-9a51a9dfcfdb247e15dc14ad025ddee9e3285ff8' + fluxCVersion = '3102-bd6d5b76934f3662d55106b701530c451739cdd2' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 0b8c68ca0dc9b73ba544e14cc384db6b9def0104 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 26 Sep 2024 15:32:10 +0100 Subject: [PATCH 17/48] Expose whether a custom field is json or not in the UI model --- .../ui/customfields/CustomFieldModels.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/CustomFieldModels.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/CustomFieldModels.kt index 4316a081c48..bfaa31e788a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/CustomFieldModels.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/CustomFieldModels.kt @@ -16,8 +16,14 @@ data class CustomFieldUiModel( val key: String, val value: String, val id: Long? = null, + val isJson: Boolean = false ) : Parcelable { - constructor(customField: CustomField) : this(customField.key, customField.valueAsString, customField.id) + constructor(customField: CustomField) : this( + key = customField.key, + value = customField.valueAsString, + id = customField.id, + isJson = customField.isJson + ) val valueStrippedHtml: String get() = HtmlUtils.fastStripHtml(value) @@ -25,11 +31,17 @@ data class CustomFieldUiModel( @IgnoredOnParcel val contentType: CustomFieldContentType = CustomFieldContentType.fromMetadataValue(value) - fun toDomainModel() = CustomField( - id = id ?: 0, // Use 0 for new custom fields - key = key, - value = WCMetaDataValue.StringValue(value) // Treat all updates as string values - ) + fun toDomainModel(): CustomField { + require(!isJson) { + "Editing JSON custom fields is not supported, this shouldn't be called for JSON custom fields" + } + + return CustomField( + id = id ?: 0, // Use 0 for new custom fields + key = key, + value = WCMetaDataValue.StringValue(value) // Treat all updates as string values + ) + } } enum class CustomFieldContentType { From cb89d47bd79b79721cd605f01694f817ac5ef509 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 26 Sep 2024 15:34:20 +0100 Subject: [PATCH 18/48] Add logic for showing a custom field on a popup --- .../list/CustomFieldsViewModel.kt | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) 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 c9dc9e8653f..e9e344e2f1d 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 @@ -6,19 +6,22 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.R -import com.woocommerce.android.extensions.combine import com.woocommerce.android.ui.customfields.CustomFieldUiModel import com.woocommerce.android.ui.customfields.CustomFieldsRepository import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.getNullableStateFlow import com.woocommerce.android.viewmodel.getStateFlow import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -37,28 +40,40 @@ class CustomFieldsViewModel @Inject constructor( private val isRefreshing = MutableStateFlow(false) private val isSaving = MutableStateFlow(false) + private val customFields = repository.observeDisplayableCustomFields(args.parentItemId) + .shareIn(viewModelScope, started = SharingStarted.Lazily) + private val showDiscardChangesDialog = savedStateHandle.getStateFlow( scope = viewModelScope, initialValue = false, key = "showDiscardChangesDialog" ) - private val customFields = repository.observeDisplayableCustomFields(args.parentItemId) private val pendingChanges = savedStateHandle.getStateFlow(viewModelScope, PendingChanges()) + private val overlayedFieldId = savedStateHandle.getNullableStateFlow( + viewModelScope, null, Long::class.java, "overlayedFieldId" + ) + private val bannerDismissed = appPrefs.observePrefs() .onStart { emit(Unit) } .map { appPrefs.isCustomFieldsTopBannerDismissed } .distinctUntilChanged() - val state = combine( + private val customFieldsWithChanges = combine( customFields, - pendingChanges, + pendingChanges + ) { customFields, pendingChanges -> + Pair(customFields.map { CustomFieldUiModel(it) }.combineWithChanges(pendingChanges), pendingChanges) + } + + val state = combine( + customFieldsWithChanges, isRefreshing, isSaving, showDiscardChangesDialog, bannerDismissed - ) { customFields, pendingChanges, isLoading, isSaving, isShowingDiscardDialog, bannerDismissed -> + ) { (customFields, pendingChanges), isLoading, isSaving, isShowingDiscardDialog, bannerDismissed -> UiState( - customFields = customFields.map { CustomFieldUiModel(it) }.combineWithChanges(pendingChanges), + customFields = customFields, isRefreshing = isLoading, isSaving = isSaving, hasChanges = pendingChanges.hasChanges, @@ -76,6 +91,13 @@ class CustomFieldsViewModel @Inject constructor( ) }.asLiveData() + val overlayedField = combine( + customFieldsWithChanges, + overlayedFieldId + ) { (customFields, _), fieldId -> + customFields.find { it.id == fieldId } + }.asLiveData() + fun onBackClick() { if (pendingChanges.value.hasChanges) { showDiscardChangesDialog.value = true @@ -95,7 +117,15 @@ class CustomFieldsViewModel @Inject constructor( } fun onCustomFieldClicked(field: CustomFieldUiModel) { - triggerEvent(OpenCustomFieldEditor(field)) + if (field.isJson) { + overlayedFieldId.value = field.id + } else { + triggerEvent(OpenCustomFieldEditor(field)) + } + } + + fun onOverlayedFieldDismissed() { + overlayedFieldId.value = null } fun onCustomFieldValueClicked(field: CustomFieldUiModel) { From 341eb4742607af9f2bb7e5da9355863f3ae1d82c Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 26 Sep 2024 17:29:15 +0100 Subject: [PATCH 19/48] Update SavedStateFlow implementation to allow primitive types --- .../com/woocommerce/android/viewmodel/SavedStateFlow.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt index 5f9c6368e17..bb4a1df7506 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt @@ -27,8 +27,8 @@ fun SavedStateHandle.getStateFlow( initialValue: T, key: String = initialValue.javaClass.name ): MutableStateFlow { - if (initialValue !is Parcelable && initialValue !is Serializable) { - error("getStateFlow supports only types that are either Parcelable or Serializable") + if (initialValue !is Parcelable && initialValue !is Serializable && !initialValue.javaClass.isPrimitive) { + error("getStateFlow supports only types that are either Parcelable or Serializable or primitives") } return getStateFlowInternal(scope, initialValue, key) @@ -42,8 +42,9 @@ fun SavedStateHandle.getNullableStateFlow( ): MutableStateFlow { if (!Parcelable::class.java.isAssignableFrom(clazz) && !Serializable::class.java.isAssignableFrom(clazz) + && !clazz.isPrimitive ) { - error("getStateFlow supports only types that are either Parcelable or Serializable") + error("getStateFlow supports only types that are either Parcelable or Serializable or primitives") } return getStateFlowInternal(scope, initialValue, key) From f98c8aa2829c774ff41fd50ee45f71b07d0c5af1 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 26 Sep 2024 18:06:40 +0100 Subject: [PATCH 20/48] Add UI for the popup --- .../customfields/list/CustomFieldsScreen.kt | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) 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 afc35841ae0..229040ffd90 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 @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.customfields.list import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -20,9 +21,11 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField import androidx.compose.material.Scaffold import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowForwardIos @@ -46,17 +49,21 @@ 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 androidx.compose.ui.window.Dialog import com.woocommerce.android.R import com.woocommerce.android.ui.compose.component.DiscardChangesDialog import com.woocommerce.android.ui.compose.component.ExpandableTopBanner import com.woocommerce.android.ui.compose.component.ProgressDialog import com.woocommerce.android.ui.compose.component.Toolbar +import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.component.WCTextButton import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.customfields.CustomField import com.woocommerce.android.ui.customfields.CustomFieldContentType import com.woocommerce.android.ui.customfields.CustomFieldUiModel +import org.json.JSONArray +import org.json.JSONObject @Composable fun CustomFieldsScreen( @@ -75,6 +82,13 @@ fun CustomFieldsScreen( snackbarHostState = snackbarHostState ) } + + viewModel.overlayedField.observeAsState().value?.let { overlayedField -> + JsonCustomFieldViewer( + customField = overlayedField, + onDismiss = viewModel::onOverlayedFieldDismissed + ) + } } @OptIn(ExperimentalMaterialApi::class) @@ -243,6 +257,54 @@ private fun CustomFieldItem( } } +@Composable +private fun JsonCustomFieldViewer( + customField: CustomFieldUiModel, + onDismiss: () -> Unit +) { + Dialog(onDismissRequest = onDismiss) { + Surface( + shape = MaterialTheme.shapes.medium, + ) { + val jsonFormatted = remember(customField.value) { + if (customField.value.trimStart().startsWith("[")) { + JSONArray(customField.value).toString(4) + } else { + JSONObject(customField.value).toString(4) + } + } + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(16.dp) + ) { + OutlinedTextField( + value = customField.key, + onValueChange = {}, + label = { Text(text = stringResource(id = R.string.custom_fields_editor_key_label)) }, + readOnly = true, + modifier = Modifier + ) + + OutlinedTextField( + value = jsonFormatted, + onValueChange = {}, + label = { Text(text = stringResource(id = R.string.custom_fields_editor_value_label)) }, + readOnly = true, + modifier = Modifier.weight(1f, fill = false) + ) + + WCColoredButton( + onClick = onDismiss, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Text(text = stringResource(id = R.string.close)) + } + } + } + } +} + @LightDarkThemePreviews @Preview @Composable @@ -274,3 +336,24 @@ private fun CustomFieldsScreenPreview() { ) } } + +@LightDarkThemePreviews +@Preview +@Composable +private fun JsonCustomFieldViewerPreview() { + WooThemeWithBackground { + JsonCustomFieldViewer( + customField = CustomFieldUiModel( + CustomField( + id = 0, + key = "key1", + value = + """|{ + | "Key": "Value" + |}""".trimMargin() + ) + ), + onDismiss = {} + ) + } +} From ed234c167eb5fa6d51eb8611c307ca973a609de4 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 26 Sep 2024 18:08:13 +0100 Subject: [PATCH 21/48] Fix detekt issues --- .../android/ui/customfields/list/CustomFieldsScreen.kt | 3 ++- .../android/ui/customfields/list/CustomFieldsViewModel.kt | 5 ++++- .../com/woocommerce/android/viewmodel/SavedStateFlow.kt | 4 ++-- 3 files changed, 8 insertions(+), 4 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 229040ffd90..df9f5a8d750 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 @@ -350,7 +350,8 @@ private fun JsonCustomFieldViewerPreview() { value = """|{ | "Key": "Value" - |}""".trimMargin() + |} + |""".trimMargin() ) ), onDismiss = {} 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 e9e344e2f1d..c4c786b2234 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 @@ -50,7 +50,10 @@ class CustomFieldsViewModel @Inject constructor( ) private val pendingChanges = savedStateHandle.getStateFlow(viewModelScope, PendingChanges()) private val overlayedFieldId = savedStateHandle.getNullableStateFlow( - viewModelScope, null, Long::class.java, "overlayedFieldId" + scope = viewModelScope, + initialValue = null, + clazz = Long::class.java, + key = "overlayedFieldId" ) private val bannerDismissed = appPrefs.observePrefs() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt index bb4a1df7506..ee5da816aff 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/SavedStateFlow.kt @@ -41,8 +41,8 @@ fun SavedStateHandle.getNullableStateFlow( key: String = clazz.name ): MutableStateFlow { if (!Parcelable::class.java.isAssignableFrom(clazz) && - !Serializable::class.java.isAssignableFrom(clazz) - && !clazz.isPrimitive + !Serializable::class.java.isAssignableFrom(clazz) && + !clazz.isPrimitive ) { error("getStateFlow supports only types that are either Parcelable or Serializable or primitives") } From 1563ecaab9f705b4db54083fafe443ec2e82caec Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 26 Sep 2024 18:12:39 +0100 Subject: [PATCH 22/48] Add unit tests --- .../list/CustomFieldsViewModelTest.kt | 66 +++++++++++++++---- 1 file changed, 54 insertions(+), 12 deletions(-) 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 505ae6a5236..77a6ace0b69 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 @@ -28,6 +28,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.wordpress.android.fluxc.model.metadata.MetaDataParentItemType +import org.wordpress.android.fluxc.model.metadata.WCMetaDataValue @OptIn(ExperimentalCoroutinesApi::class) class CustomFieldsViewModelTest : BaseUnitTest() { @@ -396,24 +397,26 @@ class CustomFieldsViewModelTest : BaseUnitTest() { } @Test - fun `given custom fields top banner is dismissed, when screen is opened, then banner is not shown`() = testBlocking { - appPrefs.isCustomFieldsTopBannerDismissed = true - setup() + fun `given custom fields top banner is dismissed, when screen is opened, then banner is not shown`() = + testBlocking { + appPrefs.isCustomFieldsTopBannerDismissed = true + setup() - val state = viewModel.state.getOrAwaitValue() + val state = viewModel.state.getOrAwaitValue() - assertThat(state.topBannerState).isNull() - } + assertThat(state.topBannerState).isNull() + } @Test - fun `given custom fields top banner is not dismissed, when screen is opened, then banner is shown`() = testBlocking { - appPrefs.isCustomFieldsTopBannerDismissed = false - setup() + fun `given custom fields top banner is not dismissed, when screen is opened, then banner is shown`() = + testBlocking { + appPrefs.isCustomFieldsTopBannerDismissed = false + setup() - val state = viewModel.state.getOrAwaitValue() + val state = viewModel.state.getOrAwaitValue() - assertThat(state.topBannerState).isNotNull - } + assertThat(state.topBannerState).isNotNull + } @Test fun `given custom fields top banner is shown, when banner is dismissed, then banner is not shown`() = testBlocking { @@ -427,4 +430,43 @@ class CustomFieldsViewModelTest : BaseUnitTest() { assertThat(state.topBannerState).isNull() } + + @Test + fun `when a json custom field is clicked, then show it in an overlay`() = testBlocking { + val customField = CustomField( + id = 1, + key = "key", + value = WCMetaDataValue.fromRawString("{\"key\": \"value\"}") + ) + setup { + whenever(repository.observeDisplayableCustomFields(PARENT_ITEM_ID)).thenReturn(flowOf(listOf(customField))) + } + + val uiModel = CustomFieldUiModel(customField) + val overlayedField = viewModel.overlayedField.runAndCaptureValues { + viewModel.onCustomFieldClicked(uiModel) + }.last() + + assertThat(overlayedField).isEqualTo(uiModel) + } + + @Test + fun `when overlayed field is dismissed, then overlay is removed`() = testBlocking { + val customField = CustomField( + id = 1, + key = "key", + value = WCMetaDataValue.fromRawString("{\"key\": \"value\"}") + ) + setup { + whenever(repository.observeDisplayableCustomFields(PARENT_ITEM_ID)).thenReturn(flowOf(listOf(customField))) + } + + val uiModel = CustomFieldUiModel(customField) + val overlayedField = viewModel.overlayedField.runAndCaptureValues { + viewModel.onCustomFieldClicked(uiModel) + viewModel.onOverlayedFieldDismissed() + }.last() + + assertThat(overlayedField).isNull() + } } From b585623a30f538c3c5212641323189854e7f7c32 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 27 Sep 2024 09:50:41 +0100 Subject: [PATCH 23/48] Catch any parsing errors when formatting the value --- .../ui/customfields/list/CustomFieldsScreen.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 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 df9f5a8d750..2cc989fc2ae 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 @@ -267,11 +267,13 @@ private fun JsonCustomFieldViewer( shape = MaterialTheme.shapes.medium, ) { val jsonFormatted = remember(customField.value) { - if (customField.value.trimStart().startsWith("[")) { - JSONArray(customField.value).toString(4) - } else { - JSONObject(customField.value).toString(4) - } + runCatching { + if (customField.value.trimStart().startsWith("[")) { + JSONArray(customField.value).toString(4) + } else { + JSONObject(customField.value).toString(4) + } + }.getOrDefault(customField.value) } Column( @@ -347,11 +349,7 @@ private fun JsonCustomFieldViewerPreview() { CustomField( id = 0, key = "key1", - value = - """|{ - | "Key": "Value" - |} - |""".trimMargin() + value = "[{\"key\": \"value\"}]" ) ), onDismiss = {} From 50a8d16061661177970427ad159c22d2b78f0528 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Fri, 27 Sep 2024 19:09:58 +0200 Subject: [PATCH 24/48] Update FluxC changeset to include fix --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 774a74ac196..898d6c92feb 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-9a51a9dfcfdb247e15dc14ad025ddee9e3285ff8' + fluxCVersion = '3104-6b80a18db518435c8b5a9cd1623b2ff7b0bcb16a' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 408675ffcb458ac1931c49df2318e0d538651212 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 11:49:10 +0200 Subject: [PATCH 25/48] Fetch objectives for Blaze preview screen --- .../com/woocommerce/android/AppPrefs.kt | 5 +++ .../woocommerce/android/AppPrefsWrapper.kt | 2 + .../BlazeCampaignCreationPreviewScreen.kt | 7 +++- .../BlazeCampaignCreationPreviewViewModel.kt | 37 ++++++++++++++++--- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index 4abf337fc9f..af6b06176c6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -129,6 +129,7 @@ object AppPrefs { TIMES_AI_PRODUCT_CREATION_SURVEY_DISPLAYED, AI_PRODUCT_CREATION_SURVEY_DISMISSED, CUSTOM_FIELDS_TOP_BANNER_DISMISSED, + BLAZE_CAMPAIGN_SELECTED_OBJECTIVE } /** @@ -274,6 +275,10 @@ object AppPrefs { get() = getBoolean(DeletablePrefKey.CUSTOM_FIELDS_TOP_BANNER_DISMISSED, false) set(value) = setBoolean(DeletablePrefKey.CUSTOM_FIELDS_TOP_BANNER_DISMISSED, value) + var blazeCampaignSelectedObjective: String + get() = getString(DeletablePrefKey.BLAZE_CAMPAIGN_SELECTED_OBJECTIVE, "") + set(value) = setString(DeletablePrefKey.BLAZE_CAMPAIGN_SELECTED_OBJECTIVE, value) + fun getProductSortingChoice(currentSiteId: Int) = getString(getProductSortingKey(currentSiteId)).orNullIfEmpty() fun setProductSortingChoice(currentSiteId: Int, value: String) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt index e4d61019ff5..f423366b165 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -40,6 +40,8 @@ class AppPrefsWrapper @Inject constructor() { var isCustomFieldsTopBannerDismissed by AppPrefs::isCustomFieldsTopBannerDismissed + var blazeCampaignSelectedObjective by AppPrefs::blazeCampaignSelectedObjective + fun getAppInstallationDate() = AppPrefs.installationDate fun getReceiptUrl(localSiteId: Int, remoteSiteId: Long, selfHostedSiteId: Long, orderId: Long) = diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt index 9865dff0cbd..34b19d9b9ba 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt @@ -50,6 +50,7 @@ import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPr import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignDetailItemUi import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignDetailsUi import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignPreviewUiState +import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.SelectedObjectiveUi import com.woocommerce.android.ui.compose.Render import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.component.ToolbarWithHelpButton @@ -449,7 +450,11 @@ fun CampaignScreenPreview() { displayValue = "https://www.myer.com.au/p/white-t-shirt-797334760-797334760", onItemSelected = {}, maxLinesValue = 1, - ) + ), + selectedObjective = SelectedObjectiveUi( + id = "sales", + displayTitle = "Sales", + ), ) ), onBackPressed = { }, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt index 25c770092c3..4706dfebc16 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.blaze.creation.preview import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_CREATION_CONFIRM_DETAILS_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_CREATION_EDIT_AD_TAPPED @@ -15,6 +16,7 @@ import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.AiSuggestionForAd import com.woocommerce.android.ui.blaze.BlazeRepository.CampaignDetails +import com.woocommerce.android.ui.blaze.BlazeRepository.Objective import com.woocommerce.android.ui.blaze.Location import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.DEVICE @@ -44,6 +46,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val resourceProvider: ResourceProvider, private val currencyFormatter: CurrencyFormatter, private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, + private val appPrefsWrapper: AppPrefsWrapper ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationPreviewFragmentArgs by savedStateHandle.navArgs() private val campaignDetails = savedStateHandle.getNullableStateFlow( @@ -60,8 +63,10 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val viewState = combine( campaignDetails.filterNotNull(), adDetailsState, - dialogState - ) { campaignDetails, adDetailsState, dialogState -> + dialogState, + blazeRepository.observeObjectives() + ) { campaignDetails, adDetailsState, dialogState, objectives -> + val selectedObjective = getSelectedObjective(objectives) CampaignPreviewUiState( adDetails = when (adDetailsState) { AdDetailsUiState.LOADING -> AdDetailsUi.Loading @@ -73,11 +78,20 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( isContentSuggestedByAi = isAdContentGeneratedByAi(campaignDetails) ) }, - campaignDetails = campaignDetails.toCampaignDetailsUi(), + campaignDetails = campaignDetails.toCampaignDetailsUi(selectedObjective), dialogState = dialogState ) }.asLiveData() + private fun getSelectedObjective(objectives: List) = + if (objectives.isNotEmpty() + && appPrefsWrapper.blazeCampaignSelectedObjective.isNotEmpty() + ) { + objectives + .find { it.id == appPrefsWrapper.blazeCampaignSelectedObjective } + ?.toSelectedObjectiveUi() + } else null + init { loadData() analyticsTrackerWrapper.track( @@ -234,10 +248,11 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) } } + blazeRepository.fetchObjectives() } } - private fun CampaignDetails.toCampaignDetailsUi() = CampaignDetailsUi( + private fun CampaignDetails.toCampaignDetailsUi(selectedObjective: SelectedObjectiveUi?) = CampaignDetailsUi( budget = getBudgetDetails(), targetDetails = listOf( getTargetLanguagesDetails(), @@ -245,7 +260,13 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( getTargetLocationsDetails(), getTargetInterestsDetails(), ), - destinationUrl = getTargetDestinationDetails() + destinationUrl = getTargetDestinationDetails(), + selectedObjective = selectedObjective + ) + + private fun Objective.toSelectedObjectiveUi() = SelectedObjectiveUi( + id = id, + displayTitle = title ) private fun CampaignDetails.getBudgetDetails() = @@ -365,6 +386,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val budget: CampaignDetailItemUi, val targetDetails: List, val destinationUrl: CampaignDetailItemUi, + val selectedObjective: SelectedObjectiveUi? ) data class CampaignDetailItemUi( @@ -404,4 +426,9 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( data class NavigateToPaymentSummary( val campaignDetails: CampaignDetails ) : MultiLiveEvent.Event() + + data class SelectedObjectiveUi( + val id: String, + val displayTitle: String + ) : MultiLiveEvent.Event() } From a2163033dfe4173fa8acd6690b0cc739c0f90ba9 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 12:44:51 +0200 Subject: [PATCH 26/48] Display selected objective value --- .../BlazeCampaignCreationPreviewScreen.kt | 10 ++--- .../BlazeCampaignCreationPreviewViewModel.kt | 40 ++++++++----------- WooCommerce/src/main/res/values/strings.xml | 2 + 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt index 34b19d9b9ba..4f1e4df3051 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt @@ -50,7 +50,6 @@ import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPr import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignDetailItemUi import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignDetailsUi import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignPreviewUiState -import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.SelectedObjectiveUi import com.woocommerce.android.ui.compose.Render import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.component.ToolbarWithHelpButton @@ -320,7 +319,7 @@ fun CampaignDetails( style = MaterialTheme.typography.body2 ) // Budget - CampaignPropertyGroupItem(items = listOf(campaignDetails.budget)) + CampaignPropertyGroupItem(items = listOf(campaignDetails.selectedObjective, campaignDetails.budget)) Spacer(modifier = Modifier.height(16.dp)) // Ad Audience @@ -451,9 +450,10 @@ fun CampaignScreenPreview() { onItemSelected = {}, maxLinesValue = 1, ), - selectedObjective = SelectedObjectiveUi( - id = "sales", - displayTitle = "Sales", + selectedObjective = CampaignDetailItemUi( + displayTitle = stringResource(R.string.blaze_campaign_preview_details_objective), + displayValue = "Sales", + onItemSelected = {}, ), ) ), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt index 4706dfebc16..c4eaa4e3ccc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt @@ -56,6 +56,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( clazz = CampaignDetails::class.java ) private var aiSuggestions: List = emptyList() + private var campaignObjectives: List = emptyList() private val adDetailsState = savedStateHandle.getStateFlow(viewModelScope, AdDetailsUiState.LOADING) private val dialogState = MutableStateFlow(null) @@ -66,7 +67,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( dialogState, blazeRepository.observeObjectives() ) { campaignDetails, adDetailsState, dialogState, objectives -> - val selectedObjective = getSelectedObjective(objectives) + campaignObjectives = objectives CampaignPreviewUiState( adDetails = when (adDetailsState) { AdDetailsUiState.LOADING -> AdDetailsUi.Loading @@ -78,20 +79,11 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( isContentSuggestedByAi = isAdContentGeneratedByAi(campaignDetails) ) }, - campaignDetails = campaignDetails.toCampaignDetailsUi(selectedObjective), + campaignDetails = campaignDetails.toCampaignDetailsUi(), dialogState = dialogState ) }.asLiveData() - private fun getSelectedObjective(objectives: List) = - if (objectives.isNotEmpty() - && appPrefsWrapper.blazeCampaignSelectedObjective.isNotEmpty() - ) { - objectives - .find { it.id == appPrefsWrapper.blazeCampaignSelectedObjective } - ?.toSelectedObjectiveUi() - } else null - init { loadData() analyticsTrackerWrapper.track( @@ -252,7 +244,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } } - private fun CampaignDetails.toCampaignDetailsUi(selectedObjective: SelectedObjectiveUi?) = CampaignDetailsUi( + private fun CampaignDetails.toCampaignDetailsUi() = CampaignDetailsUi( budget = getBudgetDetails(), targetDetails = listOf( getTargetLanguagesDetails(), @@ -261,13 +253,20 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( getTargetInterestsDetails(), ), destinationUrl = getTargetDestinationDetails(), - selectedObjective = selectedObjective + selectedObjective = getSelectedObjective(campaignObjectives) ) - private fun Objective.toSelectedObjectiveUi() = SelectedObjectiveUi( - id = id, - displayTitle = title - ) + private fun getSelectedObjective(objectives: List): CampaignDetailItemUi { + val selectedObjectiveDisplayValue = objectives + .find { it.id == appPrefsWrapper.blazeCampaignSelectedObjective } + ?.title + ?: resourceProvider.getString(R.string.blaze_campaign_preview_details_choose_objective) + return CampaignDetailItemUi( + displayTitle = resourceProvider.getString(R.string.blaze_campaign_preview_details_objective), + displayValue = selectedObjectiveDisplayValue, + onItemSelected = {} // TODO Implement navigation to objective selection screen + ) + } private fun CampaignDetails.getBudgetDetails() = CampaignDetailItemUi( @@ -386,7 +385,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val budget: CampaignDetailItemUi, val targetDetails: List, val destinationUrl: CampaignDetailItemUi, - val selectedObjective: SelectedObjectiveUi? + val selectedObjective: CampaignDetailItemUi ) data class CampaignDetailItemUi( @@ -426,9 +425,4 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( data class NavigateToPaymentSummary( val campaignDetails: CampaignDetails ) : MultiLiveEvent.Event() - - data class SelectedObjectiveUi( - val id: String, - val displayTitle: String - ) : MultiLiveEvent.Event() } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index db805f6d5fa..97dff7affbe 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3899,6 +3899,8 @@ Details Audience Budget + Campaign objective + Choose campaign objective Language Devices Location From 358a57e50a47ab05cfbbbd005f7dea003a9f0d78 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 13:14:24 +0200 Subject: [PATCH 27/48] Navigate to select objective screen --- .../BlazeCampaignObjectiveFragment.kt | 35 +++++++++++++++++++ .../BlazeCampaignCreationPreviewFragment.kt | 7 ++++ .../BlazeCampaignCreationPreviewViewModel.kt | 4 ++- .../nav_graph_blaze_campaign_creation.xml | 7 ++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt new file mode 100644 index 00000000000..fa905e6208e --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt @@ -0,0 +1,35 @@ +package com.woocommerce.android.ui.blaze.creation.objective + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.material.Text +import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.compose.composeView +import com.woocommerce.android.ui.main.AppBarStatus +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class BlazeCampaignObjectiveFragment : BaseFragment() { + override val activityAppBarStatus: AppBarStatus + get() = AppBarStatus.Hidden + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return composeView { + Text(text = "Select Campaign Objective") + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + handleEvents() + } + + private fun handleEvents() { +// viewModel.event.observe(viewLifecycleOwner) { event -> +// when (event) { +// is MultiLiveEvent.Event.Exit -> findNavController().navigateUp() +// } +// } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt index 1554effcb12..2ce7f29ade2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt @@ -19,6 +19,7 @@ import com.woocommerce.android.ui.blaze.creation.destination.BlazeCampaignCreati import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToAdDestinationScreen import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToBudgetScreen import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToEditAdScreen +import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToObjectiveSelectionScreen import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToPaymentSummary import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToTargetLocationSelectionScreen import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToTargetSelectionScreen @@ -99,12 +100,18 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { event.destinationParameters ) ) + is NavigateToPaymentSummary -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignPaymentSummaryFragment( event.campaignDetails ) ) + + is NavigateToObjectiveSelectionScreen -> findNavController().navigateSafely( + BlazeCampaignCreationPreviewFragmentDirections + .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignObjectiveFragment() + ) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt index c4eaa4e3ccc..6794239e5a4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt @@ -264,7 +264,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( return CampaignDetailItemUi( displayTitle = resourceProvider.getString(R.string.blaze_campaign_preview_details_objective), displayValue = selectedObjectiveDisplayValue, - onItemSelected = {} // TODO Implement navigation to objective selection screen + onItemSelected = { triggerEvent(NavigateToObjectiveSelectionScreen) } ) } @@ -425,4 +425,6 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( data class NavigateToPaymentSummary( val campaignDetails: CampaignDetails ) : MultiLiveEvent.Event() + + data object NavigateToObjectiveSelectionScreen : MultiLiveEvent.Event() } 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 dbbfad740b8..b89e972fc52 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 @@ -76,6 +76,9 @@ + + From cf7e73549f0eded0e53353a3b7534eb79230ae81 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 30 Sep 2024 14:57:43 +0200 Subject: [PATCH 28/48] Fixed release notes --- RELEASE-NOTES.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 5b5acf703b9..bd112d5884f 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,14 +1,14 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] *** Use [*****] to indicate smoke tests of all critical flows should be run on the final APK before release (e.g. major library or targetSdk updates). *** For entries which are touching the Android Wear app's, start entry with `[WEAR]` too. -20.7 +20.8 ----- 20.7 ----- - [**] Improve barcode scanner reading accuracy [https://github.com/woocommerce/woocommerce-android/pull/12673] -- [Internal] AI product creation banner is in products now [https://github.com/woocommerce/woocommerce-android/pull/12705] +- [Internal] AI product creation banner is removed [https://github.com/woocommerce/woocommerce-android/pull/12705] - [**] Fixed bug with coupons disappearing from the order creation screen unexpectedly [https://github.com/woocommerce/woocommerce-android/pull/12724] 20.6 From 6bdfa76c60681b6789835542f08486235de2c363 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 30 Sep 2024 15:27:10 +0200 Subject: [PATCH 29/48] Removed AI Product Banner dialog --- .../banner/AIProductBannerDialogFragment.kt | 83 --------------- .../ai/banner/AIProductBannerDialogScreen.kt | 84 --------------- .../AIProductBannerDialogShouldBeShown.kt | 16 --- .../banner/AIProductBannerDialogViewModel.kt | 32 ------ .../ui/products/list/ProductListEvent.kt | 2 - .../ui/products/list/ProductListFragment.kt | 7 -- .../ui/products/list/ProductListViewModel.kt | 10 -- .../main/res/drawable-hdpi/img_ai_dialog.webp | Bin 2446 -> 0 bytes .../main/res/drawable-mdpi/img_ai_dialog.webp | Bin 1558 -> 0 bytes .../drawable-night-hdpi/img_ai_dialog.webp | Bin 2180 -> 0 bytes .../drawable-night-mdpi/img_ai_dialog.webp | Bin 1406 -> 0 bytes .../drawable-night-xhdpi/img_ai_dialog.webp | Bin 2924 -> 0 bytes .../drawable-night-xxhdpi/img_ai_dialog.webp | Bin 4868 -> 0 bytes .../drawable-night-xxxhdpi/img_ai_dialog.webp | Bin 6352 -> 0 bytes .../res/drawable-xhdpi/img_ai_dialog.webp | Bin 3266 -> 0 bytes .../res/drawable-xxhdpi/img_ai_dialog.webp | Bin 5482 -> 0 bytes .../res/drawable-xxxhdpi/img_ai_dialog.webp | Bin 6824 -> 0 bytes .../main/res/navigation/nav_graph_main.xml | 8 -- .../src/main/res/values-ar/strings.xml | 2 - .../src/main/res/values-de/strings.xml | 2 - .../src/main/res/values-es/strings.xml | 2 - .../src/main/res/values-fr/strings.xml | 2 - .../src/main/res/values-he/strings.xml | 2 - .../src/main/res/values-id/strings.xml | 2 - .../src/main/res/values-it/strings.xml | 2 - .../src/main/res/values-ja/strings.xml | 2 - .../src/main/res/values-ko/strings.xml | 2 - .../src/main/res/values-nl/strings.xml | 2 - .../src/main/res/values-pt-rBR/strings.xml | 2 - .../src/main/res/values-ru/strings.xml | 2 - .../src/main/res/values-sv/strings.xml | 2 - .../src/main/res/values-tr/strings.xml | 2 - .../src/main/res/values-zh-rCN/strings.xml | 2 - .../src/main/res/values-zh-rTW/strings.xml | 2 - WooCommerce/src/main/res/values/strings.xml | 2 - .../AIProductBannerDialogShouldBeShownTest.kt | 99 ------------------ .../products/list/ProductListViewModelTest.kt | 20 ---- 37 files changed, 395 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogScreen.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt delete mode 100644 WooCommerce/src/main/res/drawable-hdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-mdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-night-hdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-night-mdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-night-xhdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-night-xxhdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-night-xxxhdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-xhdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-xxhdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/main/res/drawable-xxxhdpi/img_ai_dialog.webp delete mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt deleted file mode 100644 index 90955e679b3..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogFragment.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.woocommerce.android.ui.products.ai.banner - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController -import com.woocommerce.android.NavGraphMainDirections -import com.woocommerce.android.R.style -import com.woocommerce.android.extensions.navigateSafely -import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground -import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogViewModel.TryAIProductDescriptionGeneration -import com.woocommerce.android.ui.products.details.ProductDetailFragment -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit -import dagger.hilt.android.AndroidEntryPoint -import org.wordpress.android.util.DisplayUtils - -@AndroidEntryPoint -class AIProductBannerDialogFragment : DialogFragment() { - companion object { - private const val TABLET_LANDSCAPE_WIDTH_RATIO = 0.35f - } - - private val viewModel: AIProductBannerDialogViewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setStyle(STYLE_NO_TITLE, style.Theme_Woo_Dialog_RoundedCorners) - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - // Specify transition animations - dialog?.window?.attributes?.windowAnimations = style.Woo_Animations_Dialog - - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - - setContent { - WooThemeWithBackground { - AIProductDescriptionDialogScreen( - viewModel::onTryNowButtonClicked, - viewModel::onDismissButtonClicked - ) - } - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - viewModel.event.observe(viewLifecycleOwner) { event -> - when (event) { - is TryAIProductDescriptionGeneration -> openBlankProduct() - is Exit -> findNavController().navigateUp() - } - } - } - - private fun openBlankProduct() { - findNavController().navigateSafely( - NavGraphMainDirections.actionGlobalProductDetailFragment( - mode = ProductDetailFragment.Mode.AddNewProduct, - ) - ) - } - - override fun onStart() { - super.onStart() - if (isTabletLandscape()) { - requireDialog().window!!.setLayout( - (DisplayUtils.getWindowPixelWidth(requireContext()) * TABLET_LANDSCAPE_WIDTH_RATIO).toInt(), - ViewGroup.LayoutParams.WRAP_CONTENT - ) - } - } - - private fun isTabletLandscape() = (DisplayUtils.isTablet(context) || DisplayUtils.isXLargeTablet(context)) && - DisplayUtils.isLandscape(context) -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogScreen.kt deleted file mode 100644 index 73c002f38fa..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogScreen.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.woocommerce.android.ui.products.ai.banner - -import android.content.res.Configuration -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import com.woocommerce.android.R -import com.woocommerce.android.ui.compose.component.WCColoredButton -import com.woocommerce.android.ui.compose.component.WCOutlinedButton -import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground - -@Composable -fun AIProductDescriptionDialogScreen( - onTryNowButtonClick: () -> Unit, - onMaybeLaterButtonClick: () -> Unit -) { - Box( - modifier = Modifier - .background( - color = colorResource(id = R.color.color_surface) - ) - .padding(dimensionResource(id = R.dimen.major_100)) - ) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - modifier = Modifier.fillMaxWidth(), - painter = painterResource(R.drawable.img_ai_dialog), - contentDescription = "Generate AI Description", - ) - Text( - modifier = Modifier.padding(top = dimensionResource(id = R.dimen.major_100)), - text = stringResource(id = R.string.ai_product_description_dialog_title), - style = MaterialTheme.typography.h5, - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier.padding(top = dimensionResource(id = R.dimen.major_100)), - text = stringResource(id = R.string.ai_product_description_dialog_message), - style = MaterialTheme.typography.body1, - textAlign = TextAlign.Center - ) - WCColoredButton( - modifier = Modifier - .padding(top = dimensionResource(id = R.dimen.major_100)) - .fillMaxWidth(), - onClick = onTryNowButtonClick - ) { - Text(stringResource(id = R.string.try_it_now)) - } - WCOutlinedButton( - modifier = Modifier.fillMaxWidth(), - onClick = onMaybeLaterButtonClick - ) { - Text(stringResource(id = R.string.maybe_later)) - } - } - } -} - -@Preview(name = "Light Mode") -@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) -@Composable -fun PreviewAIProductDescriptionDialog() { - WooThemeWithBackground { - AIProductDescriptionDialogScreen({}, {}) - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt deleted file mode 100644 index 593438e947c..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShown.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.woocommerce.android.ui.products.ai.banner - -import com.woocommerce.android.AppPrefsWrapper -import com.woocommerce.android.extensions.isEligibleForAI -import com.woocommerce.android.tools.SelectedSite -import javax.inject.Inject - -class AIProductBannerDialogShouldBeShown @Inject constructor( - private val selectedSite: SelectedSite, - private val appPrefsWrapper: AppPrefsWrapper, -) { - operator fun invoke(): Boolean { - return selectedSite.getOrNull()?.isEligibleForAI == true && - !appPrefsWrapper.wasAIProductDescriptionPromoDialogShown - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt deleted file mode 100644 index adad8c49487..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogViewModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.woocommerce.android.ui.products.ai.banner - -import androidx.lifecycle.SavedStateHandle -import com.woocommerce.android.AppPrefsWrapper -import com.woocommerce.android.viewmodel.MultiLiveEvent -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit -import com.woocommerce.android.viewmodel.ScopedViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class AIProductBannerDialogViewModel @Inject constructor( - savedState: SavedStateHandle, - appPrefsWrapper: AppPrefsWrapper -) : ScopedViewModel(savedState) { - init { - launch { - appPrefsWrapper.wasAIProductDescriptionPromoDialogShown = true - } - } - - fun onTryNowButtonClicked() { - triggerEvent(TryAIProductDescriptionGeneration) - } - - fun onDismissButtonClicked() { - triggerEvent(Exit) - } - - object TryAIProductDescriptionGeneration : MultiLiveEvent.Event() -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt index c7b3a6363e6..0d3b5b5f7ed 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt @@ -38,6 +38,4 @@ sealed class ProductListEvent : MultiLiveEvent.Event() { data object OpenEmptyProduct : ProductListEvent() data class SelectProducts(val productsIds: List) : ProductListEvent() - - data object ShowAIProductBannerDialog : ProductListEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt index 7cc9ccd4f31..dcbde1cbebf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListFragment.kt @@ -56,7 +56,6 @@ import com.woocommerce.android.ui.products.list.ProductListEvent.OpenEmptyProduc import com.woocommerce.android.ui.products.list.ProductListEvent.OpenProduct import com.woocommerce.android.ui.products.list.ProductListEvent.ScrollToTop import com.woocommerce.android.ui.products.list.ProductListEvent.SelectProducts -import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAIProductBannerDialog import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAddProductBottomSheet import com.woocommerce.android.ui.products.list.ProductListEvent.ShowDiscardProductChangesConfirmationDialog import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductFilterScreen @@ -404,12 +403,6 @@ class ProductListFragment : ) } - is ShowAIProductBannerDialog -> { - findNavController().navigateSafely( - ProductListFragmentDirections.actionProductListFragmentToAIProductBannerDialogFragment() - ) - } - else -> event.isHandled = false } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt index 7836573a044..3f345351cec 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModel.kt @@ -20,10 +20,8 @@ import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.media.MediaFileUploadHandler import com.woocommerce.android.ui.products.ProductStatus -import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogShouldBeShown import com.woocommerce.android.ui.products.list.ProductListEvent.ScrollToTop import com.woocommerce.android.ui.products.list.ProductListEvent.SelectProducts -import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAIProductBannerDialog import com.woocommerce.android.ui.products.list.ProductListEvent.ShowAddProductBottomSheet import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductFilterScreen import com.woocommerce.android.ui.products.list.ProductListEvent.ShowProductSortingBottomSheet @@ -58,7 +56,6 @@ class ProductListViewModel @Inject constructor( private val selectedSite: SelectedSite, private val wooCommerceStore: WooCommerceStore, private val isWindowClassLargeThanCompact: IsWindowClassLargeThanCompact, - aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown ) : ScopedViewModel(savedState) { companion object { private const val KEY_PRODUCT_FILTER_OPTIONS = "key_product_filter_options" @@ -109,13 +106,6 @@ class ProductListViewModel @Inject constructor( mediaFileUploadHandler.observeProductImageChanges() .onEach { loadProducts() } .launchIn(this) - - if (aiProductBannerDialogShouldBeShown()) { - triggerEventWithDelay( - ShowAIProductBannerDialog, - delay = 500 - ) - } } override fun onCleared() { diff --git a/WooCommerce/src/main/res/drawable-hdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-hdpi/img_ai_dialog.webp deleted file mode 100644 index 77e7a85fcc755edfee649d9fef8df4ba228e5cf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2446 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 IW6%Hq05cNb%7 diff --git a/WooCommerce/src/main/res/drawable-night-hdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-night-hdpi/img_ai_dialog.webp deleted file mode 100644 index e3c5429e00c623b151074c8410ddc67b14dcdf48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2180 zcmV-~2z&QZNk&F|2mkaWsGwxg6|CA_k_t<5wo_rCT7g-skE4}) zWVo--yDAFJqV}URHs~)Sd2G1GRCrS9|MY+QKmDKnPyeU?)BoxJ^ndz4{h$6%|EK@c z|LOnqfBHZDpZ-t(r~lLcfA%vzIF`qTYxLrb4f=WF-Ka9jP z$haVvyOrTeg;Bwx3m6OysrHEQT57-m7_Mhhp~r%g(&QF~&xV8kTmiZ29l-Fl0VnOd z5hH=GkIi$y00011P&gn!2LJ#tKmeTqDxv`h0X~sNpG>8rqN1TP*ubz731@EMZILng z0^9F19Qb9N<8YPSM)X!f_nkk++H)FxzTEely0V+medtDhUwc6H6Gs^F8aT&@#(g1@ zG=XATKhn3ymgeZ=9wSF5c#P!7Aunb~=TOw|pBI`M4UBk=9Daly`GlN1{og;hOL9S0 z;&?qo<1Ew7<+;uiO$wPOgVatkm()!hh&}~w7YaF4A%lN=qNx!zqxO0Wy;drr$)9l+ z=IG-!sM&y?dQe&N0#9SNx+Op!OIbL*!^9Sneg+LMXG#)rb6hmFf@cCgFmrTV}khd1eksL}A@_9vXr zd4c7uX0S3&60@hI0XXX(!P6Hd!JLKL$#i!zEiSI?d=)QeYYZENNK#LiZ!Xq$s zio#Kk5f!yRcu`xMgk4D?y6Xk?M1?cg7ynE@tja%Nd0nH1tG2+1fBQ+V43)ro`X>&X zXPQCZ;xuvp0K}qVPbFT~g@bnBjtJzLWeeGn&Xzwem#Xu|(qT%T2gIBMN{m;CX8p>?T9YSjULb#ymv0 zX3TMx(@R*#h|$J8MvgIsxiJD5fB^p5IM!7gMa}k$+fcT%<6G?v@$a{Oz$lC0pDnaC zQ}?k6V99se>&U37JnSQp)_hmvIexkr-z|Eh2=2M zZnhI69tC1NfN9`s#6_}V2z`JUyLZ;vuU`aqISr%f=-0Pqe>6e2bFW+e3=(BK##4Q* zJi~B1+jkPTN!#abyrO%u>R2;GDbBy;uhZcVAL&cBwKsh&OvM_=QF=~@i5eAq)!g5 zGssoB5~E)^E%n(nvExXh{uplqP@}ZX*ss%s{Y?C(q$t5RBG;NI&J-x99f#lUW!^MKfVeh#9cRD@h{rd}r;)aiGWhDG>FMG0kcl|_kp1fz&nGZ9` z?i_2}NZa~lhjlvK5v%bdUg;Id?B>^bm55p_mm@QyweRii_i!2zsG!>-{+UQ$@+&itm~RLJuqxj4c~o*ImrD0L{a(EaWoLu3LIP3wx}{t9iP z_2R$L!fN?T8+qW@A6yd``{4Ex^QZu}%?Tesh{KY4+TqHHjxj|(`LuuL-|Q2+e)YX( z;TtbdM&K)hQOvH#6NCK|QsprB*^}vX4%k+d-Jycj>8SUS)^0Hc6<4>dicqGJ@ahq~ z%;}QUh)wLKi8m|7vtA82{#6)Z)m04FC{~(aj2@%6N>64s?_cJ&d^0jcEsJLN%*lzq z{kQ@euztrCxh!X4b^8g~yQdG|10dC*yhO-3Wf+>%+=I3a^0VY%mDY|1gDo8C{g45g z)6J+{AVO(^d;gNHDa%)?LiH+{t)^L-+=Ms)cf0^y%e9BfvwlpF00;Zt%WzJM;c>Or zKi+_1>CaTxy>Vq_dA#qL8vv?5EU?6rSkYR4uifdh^U`UX-Mb|&9y_t@`{y&oy;h&QFx zOtVuZzYpMY=D*(Luv95HNpxjnfCeZpO?l>uE}_DLc5=i^uuZ`15uI?~l9^CN<#&4k z01BHHpbQCiD0DAaJP&m<%oq-c?V5?Wu~2Xbxxn77zv)u#YTYDzk0rrL0BW3(7bF6H G0000cEHzaC diff --git a/WooCommerce/src/main/res/drawable-night-mdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-night-mdpi/img_ai_dialog.webp deleted file mode 100644 index 9be0a1714c1f0ebc2ee9ccf105d52424784b85b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1406 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 ZV{L3e>&Eao_5(KJ6TR-cRdCeW?th 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 R>+V;ZZ}vzp`Rh~Te*p^8d&U3& diff --git a/WooCommerce/src/main/res/drawable-night-xxxhdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-night-xxxhdpi/img_ai_dialog.webp deleted file mode 100644 index 3d791a4abf7d4aa7a7982adaaa55800bfbfc2586..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6352 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 VgvXv47@TO>2Mg>FY2+6G@E<6}YGVKZ diff --git a/WooCommerce/src/main/res/drawable-xxhdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-xxhdpi/img_ai_dialog.webp deleted file mode 100644 index d5dd8e3b4224c3beb85693e8c0827af31a73f694..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5482 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 diff --git a/WooCommerce/src/main/res/drawable-xxxhdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-xxxhdpi/img_ai_dialog.webp deleted file mode 100644 index 7a7334b214f739600eb9ddd4034bab3cc81524b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6824 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 - - - حل المشكلات قد يرتبط ذلك بنزاع مع الاضافة. يرجى المحاولة مرة أخرى لاحقًا أو تواصل معنا وستسرنا مساعدتك! تعذر علينا تحميل بياناتك - أضف أوصاف في لمح البصر باستخدام الذكاء الاصطناعي. جرّب الميزة التي نطرحها الآن! - إضافة وصف المنتج باستخدام الذكاء الاصطناعي فهمت ترجى مراعاة أن وصف هذا المنتج تم إنشاؤه باستخدام أداة لدينا مدعومة من الذكاء الاصطناعي. ترجى مراجعة المحتوى وتحريره لضمان محاذاته مع علامتك التجارية ورسائلك. بداية رائعة! diff --git a/WooCommerce/src/main/res/values-de/strings.xml b/WooCommerce/src/main/res/values-de/strings.xml index 4327ad9d9c9..8a2b204a124 100644 --- a/WooCommerce/src/main/res/values-de/strings.xml +++ b/WooCommerce/src/main/res/values-de/strings.xml @@ -954,8 +954,6 @@ Language: de Problembehandlung Dies könnte mit einem Konflikt mit einem Plugin zusammenhängen. Bitte versuche es später erneut oder kontaktiere uns – wir helfen dir gerne weiter! Deine Daten konnten nicht geladen werden. - Füge mit KI im Handumdrehen Beschreibungen hinzu. Probiere die Funktion noch heute aus! - Produktbeschreibung mit KI hinzufügen Verstanden Beachte bitte, dass diese Produktbeschreibung mit unserem KI-gestützten Tool erstellt wurde. Überprüfe und bearbeite den Inhalt entsprechend, damit er deiner Marke und Botschaft entspricht. Guter Start! diff --git a/WooCommerce/src/main/res/values-es/strings.xml b/WooCommerce/src/main/res/values-es/strings.xml index a43ae7a37ed..cbdb9b207cf 100644 --- a/WooCommerce/src/main/res/values-es/strings.xml +++ b/WooCommerce/src/main/res/values-es/strings.xml @@ -954,8 +954,6 @@ Language: es Solución de problemas Este problema se puede producir a causa de un conflicto con un plugin. Inténtalo de nuevo más tarde o ponte en contacto con nosotros y estaremos encantados de ayudarte. No hemos podido cargar los datos. - Añade las descripciones rápidamente con la IA. ¡Prueba nuestra función hoy mismo! - Añadir la descripción del producto usando la IA De acuerdo Ten en cuenta que esta descripción de producto se ha generado con nuestra herramienta con tecnología de IA. Revisa y edita el contenido para asegurarte de que esté en línea con tu marca y tu mensaje. ¡Gran comienzo! diff --git a/WooCommerce/src/main/res/values-fr/strings.xml b/WooCommerce/src/main/res/values-fr/strings.xml index f616db3c32a..7ed55ff8ca7 100644 --- a/WooCommerce/src/main/res/values-fr/strings.xml +++ b/WooCommerce/src/main/res/values-fr/strings.xml @@ -954,8 +954,6 @@ Language: fr Résolution de problèmes Ceci peut être dû à un conflit avec une extension. Réessayez plus tard ou contactez-nous et nous vous aiderons avec plaisir ! Nous n’avons pas pu charger vos données. - Ajoutez des descriptions en un clin d’œil grâce à l’IA. Essayez notre fonctionnalité dès aujourd’hui ! - Ajouter la description du produit avec l’IA J’ai compris N’oubliez pas que cette description de produit a été générée à l’aide de notre outil d’intelligence artificielle. Passez-la en revue et modifiez le contenu pour vous assurer qu’il correspond à votre marque et au message que vous voulez communiquer. C’est un excellent début ! diff --git a/WooCommerce/src/main/res/values-he/strings.xml b/WooCommerce/src/main/res/values-he/strings.xml index b13d408ba96..d6ec08b305e 100644 --- a/WooCommerce/src/main/res/values-he/strings.xml +++ b/WooCommerce/src/main/res/values-he/strings.xml @@ -954,8 +954,6 @@ Language: he_IL פתרון בעיות ייתכן שזה קשור להתנגשות עם תוסף. יש לנסות שוב מאוחר יותר או לפנות אלינו בכל עת ואנחנו נשמח לעזור! לא ניתן לטעון את הנתונים שלך. - להוסיף תיאורים במהירות בעזרת בינה מלאכותית. כדאי לנסות את האפשרות כבר היום! - להוסיף תיאור מוצר בעזרת בינה מלאכותית הבנתי חשוב לשים לב שתיאור המוצר נוצר דרך כלי שאנחנו מפעילים באמצעות בינה מלאכותית. יש לבדוק ולערוך את התוכן כדי לוודא שהוא מתאים למותג ולמסר שלך. התחלה נהדרת! diff --git a/WooCommerce/src/main/res/values-id/strings.xml b/WooCommerce/src/main/res/values-id/strings.xml index 313aa3470d5..e49346dab77 100644 --- a/WooCommerce/src/main/res/values-id/strings.xml +++ b/WooCommerce/src/main/res/values-id/strings.xml @@ -954,8 +954,6 @@ Language: id Pemecahan Masalah Hal ini mungkin karena adanya konflik dengan plugin. Coba lagi nanti atau hubungi kami dan dengan senang hati kami akan membantu Anda! Data Anda tidak dapat dimuat. - Tambahkan deskripsi secara cepat dengan AI. Coba fitur kami sekarang juga! - Tambahkan deskripsi produk dengan AI Saya mengerti Harap perhatikan bahwa deskripsi produk ini dibuat dengan alat berbasis teknologi AI. Harap periksa dan edit isinya agar sesuai dengan citra dan pesan yang ingin Anda sampaikan. Mantap! diff --git a/WooCommerce/src/main/res/values-it/strings.xml b/WooCommerce/src/main/res/values-it/strings.xml index e32a4d57080..0d149c18ec6 100644 --- a/WooCommerce/src/main/res/values-it/strings.xml +++ b/WooCommerce/src/main/res/values-it/strings.xml @@ -954,8 +954,6 @@ Language: it Risoluzione dei problemi Questo potrebbe essere correlato a un conflitto con un plugin. Riprova più tardi o contattaci per ricevere assistenza. Impossibile caricare i tuoi dati. - Aggiungi descrizioni in un attimo con l\'IA. Prova la nostra funzionalità oggi stesso. - Aggiungi la descrizione del prodotto con l\'IA OK Tieni presente che questa descrizione del prodotto è stata generata usando il nostro strumento basato sull\'IA. Rivedi e modifica il contenuto per assicurarti che sia in linea con il marchio e il messaggio. Ottimo inizio. diff --git a/WooCommerce/src/main/res/values-ja/strings.xml b/WooCommerce/src/main/res/values-ja/strings.xml index 04dc7ce46d8..63a5e5dd52f 100644 --- a/WooCommerce/src/main/res/values-ja/strings.xml +++ b/WooCommerce/src/main/res/values-ja/strings.xml @@ -954,8 +954,6 @@ Language: ja_JP トラブルシューティング このエラーはプラグインとの競合に関連する可能性があります。 しばらくしてから再度お試しいただくか、サポートをご依頼ください。 データを読み込めませんでした。 - AI で簡単に説明を追加します。 今すぐこの機能をお試しください。 - AI で商品説明を追加する OK この商品説明は AI を活用したツールを使用して生成されています。 コンテンツを確認して編集し、ブランドやメッセージと合っていることを確認してください。 良い出だしです ! diff --git a/WooCommerce/src/main/res/values-ko/strings.xml b/WooCommerce/src/main/res/values-ko/strings.xml index 70d768159a0..2e8254b6d56 100644 --- a/WooCommerce/src/main/res/values-ko/strings.xml +++ b/WooCommerce/src/main/res/values-ko/strings.xml @@ -954,8 +954,6 @@ Language: ko_KR 문제 해결 플러그인 충돌과 관련이 있을 수 있습니다. 나중에 다시 시도해 보세요. 또는 당사에 연락하면 기꺼이 도와드리겠습니다! 데이터를 로드할 수 없습니다. - AI로 곧장 설명을 추가하세요. 지금 기능을 사용해 보세요! - AI로 상품 설명 추가 알겠습니다. 이 상품 설명은 AI 기반 도구로 생성되었다는 점을 유념하시기를 바랍니다. 콘텐츠를 검토하고 브랜드 및 메시지 내용과 부합하도록 편집하시기를 바랍니다. 시작이 좋습니다! diff --git a/WooCommerce/src/main/res/values-nl/strings.xml b/WooCommerce/src/main/res/values-nl/strings.xml index b0a9bad03f5..2dec674a010 100644 --- a/WooCommerce/src/main/res/values-nl/strings.xml +++ b/WooCommerce/src/main/res/values-nl/strings.xml @@ -954,8 +954,6 @@ Language: nl Probleemoplossing Dit kan liggen aan een conflict met een plugin. Probeer het later opnieuw of neem contact met ons op zodat wij je kunnen helpen. We konden je gegevens niet laden. - Voeg in mum van tijd beschrijvingen toe met behulp van AI. Bekijk deze functie vandaag nog! - AI-productbeschrijving toevoegen Duidelijk Onthoud dat deze productbeschrijving is gegenereerd met ons AI-hulpmiddel. Bekijk en bewerk de content om er zeker van te zijn dat deze bij je merk en boodschap past. Geweldige start! diff --git a/WooCommerce/src/main/res/values-pt-rBR/strings.xml b/WooCommerce/src/main/res/values-pt-rBR/strings.xml index 5fc2219b08a..40a57fd1de2 100644 --- a/WooCommerce/src/main/res/values-pt-rBR/strings.xml +++ b/WooCommerce/src/main/res/values-pt-rBR/strings.xml @@ -954,8 +954,6 @@ Language: pt_BR Solução de problemas Não foi possível carregar seus dados. É possível que seja um conflito com o plugin. Tente novamente mais tarde ou fale a gente. Vamos ter prazer em ajudar você! - Adicione descrições em um piscar de olhos com IA. Experimente nossa funcionalidade hoje mesmo! - Adicionar descrição do produto com IA Entendi Saiba que esta descrição do produto foi gerada usando nossa ferramenta com IA. Revise e edite o conteúdo para garantir que esteja alinhado com sua marca e mensagem. Ótimo começo! diff --git a/WooCommerce/src/main/res/values-ru/strings.xml b/WooCommerce/src/main/res/values-ru/strings.xml index 044aa260aa0..32f094f2f0b 100644 --- a/WooCommerce/src/main/res/values-ru/strings.xml +++ b/WooCommerce/src/main/res/values-ru/strings.xml @@ -954,8 +954,6 @@ Language: ru Устранение неполадок Возможно, дело в конфликте с плагином. Повторите попытку позже или свяжитесь с нами. Мы будем рады помочь! Не удалось загрузить данные. - Используйте искусственный интеллект, чтобы быстро создавать описания. Попробуйте эту функцию уже сегодня! - Создание описания товара с помощью ИИ Понятно Помните, что это описание товара было создано с помощью искусственного интеллекта. Проверьте и отредактируйте содержимое, чтобы оно точно соответствовало вашему бренду и концепции. Превосходное начало! diff --git a/WooCommerce/src/main/res/values-sv/strings.xml b/WooCommerce/src/main/res/values-sv/strings.xml index bf5b114daa3..e63ba9785f4 100644 --- a/WooCommerce/src/main/res/values-sv/strings.xml +++ b/WooCommerce/src/main/res/values-sv/strings.xml @@ -954,8 +954,6 @@ Language: sv_SE Felsökning Detta kan bero på en tilläggskonflikt. Försök igen senare eller kontakta oss så hjälper vi dig gärna. Vi kunde inte ladda dina data. - Lägg till beskrivningar på nolltid med AI. Prova vår funktion idag. - Lägg till produktbeskrivning med AI Jag förstår Tänk på att den här produktbeskrivningen har genererats med vårt AI-drivna verktyg. Granska och redigera innehållet för att säkerställa att det överensstämmer med ditt varumärke och ditt budskap. Bra start! diff --git a/WooCommerce/src/main/res/values-tr/strings.xml b/WooCommerce/src/main/res/values-tr/strings.xml index 03eb14a0535..be666c8ecbc 100644 --- a/WooCommerce/src/main/res/values-tr/strings.xml +++ b/WooCommerce/src/main/res/values-tr/strings.xml @@ -954,8 +954,6 @@ Language: tr Sorun giderme Bu, bir eklentiyle çakışmadan kaynaklanabilir. Lütfen daha sonra tekrar deneyin veya bize ulaşın, size yardımcı olmaktan memnuniyet duyarız. Verileriniz yüklenemedi. - Yapay zekayla açıklamaları anında ekleyin. Özelliğimizi bugün deneyin! - Yapay zekayla ürün açıklaması ekleyin Anladım Bu ürün açıklamasının yapay zeka destekli aracımız kullanılarak oluşturulduğunu lütfen unutmayın. Lütfen markanız ve vermek istediğiniz mesajla uyumlu olduğundan emin olmak için bu açıklamayı inceleyip düzenleyin. Mükemmel başlangıç! diff --git a/WooCommerce/src/main/res/values-zh-rCN/strings.xml b/WooCommerce/src/main/res/values-zh-rCN/strings.xml index 5b12b17b25c..32561118c81 100644 --- a/WooCommerce/src/main/res/values-zh-rCN/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rCN/strings.xml @@ -954,8 +954,6 @@ Language: zh_CN 故障排除 这可能与插件冲突有关。 请稍后再试或与我们联系,我们很乐意为您提供帮助! 我们无法加载您的数据。 - 使用 AI 快速添加描述。 立即试用我们的功能! - 使用 AI 添加产品描述 知道了 请注意,此产品描述是使用我们的 AI 赋能工具生成的。 请检查和编辑内容以确保相关内容与您的品牌和消息相契合。 不错的开局! diff --git a/WooCommerce/src/main/res/values-zh-rTW/strings.xml b/WooCommerce/src/main/res/values-zh-rTW/strings.xml index 538f4a4adbe..e5daa8bd3fb 100644 --- a/WooCommerce/src/main/res/values-zh-rTW/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rTW/strings.xml @@ -954,8 +954,6 @@ Language: zh_TW 疑難排解 這可能與外掛程式衝突有關。 請稍後再試或與我們聯絡,我們很樂意提供協助! 無法載入你的資料。 - 讓 AI 幫助你迅速新增說明。 立即試用我們的功能! - 使用 AI 新增產品說明 知道了 在此提醒,這是系統使用 AI 工具產生的產品說明。 請詳細檢閱及編輯內容,確認內容符合你的品牌形象和你想傳遞的訊息。 很棒的開始! diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index cc2f5a96ae5..ae148e21f1c 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3827,8 +3827,6 @@ Powered by AI. <a href=\'\'><u>Learn more</u></a>. Description generated by AI There was a problem generating the product description. Please try again later. - Add product description with AI - Add descriptions in a snap with AI. Try our feature today! Generate a description using AI ✨ Write with AI Use our AI-powered tool to quickly generate product descriptions. Just input keywords and we\'ll do the rest! diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt deleted file mode 100644 index 70138b7c52a..00000000000 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ai/banner/AIProductBannerDialogShouldBeShownTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -import com.woocommerce.android.AppPrefsWrapper -import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogShouldBeShown -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Test -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever -import org.wordpress.android.fluxc.model.SiteModel - -class AIProductBannerDialogShouldBeShownTest { - private val selectedSite: SelectedSite = mock() - private val appPrefsWrapper: AppPrefsWrapper = mock() - - private lateinit var aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown - - @Before - fun setup() { - aiProductBannerDialogShouldBeShown = AIProductBannerDialogShouldBeShown( - selectedSite, - appPrefsWrapper - ) - } - - @Test - fun `given site is WPComAtomic, when AI promo dialog was not shown, then should return true`() { - // given - val site = mock() - whenever(site.isWPComAtomic).thenReturn(true) - whenever(site.planActiveFeatures).thenReturn("") - whenever(selectedSite.getOrNull()).thenReturn(site) - whenever(appPrefsWrapper.wasAIProductDescriptionPromoDialogShown).thenReturn(false) - - // when - val result = aiProductBannerDialogShouldBeShown() - - // then - assertThat(result).isTrue - } - - @Test - fun `given site has AI assistant in plan features, when AI promo dialog was not shown, then should return true`() { - // given - val site = mock() - whenever(site.isWPComAtomic).thenReturn(false) - whenever(site.planActiveFeatures).thenReturn("ai-assistant") - whenever(selectedSite.getOrNull()).thenReturn(site) - whenever(appPrefsWrapper.wasAIProductDescriptionPromoDialogShown).thenReturn(false) - - // when - val result = aiProductBannerDialogShouldBeShown() - - // then - assertThat(result).isTrue - } - - @Test - fun `given site is not eligible for AI, when checking if dialog should be shown, then should return false`() { - // given - val site = mock() - whenever(site.isWPComAtomic).thenReturn(false) - whenever(site.planActiveFeatures).thenReturn("") - whenever(selectedSite.getOrNull()).thenReturn(site) - - // when - val result = aiProductBannerDialogShouldBeShown() - - // then - assertThat(result).isFalse - } - - @Test - fun `given AI promo dialog was already shown, when site is eligible for AI, then should return false`() { - // given - val site = mock() - whenever(site.isWPComAtomic).thenReturn(true) - whenever(site.planActiveFeatures).thenReturn("") - whenever(selectedSite.getOrNull()).thenReturn(site) - whenever(appPrefsWrapper.wasAIProductDescriptionPromoDialogShown).thenReturn(true) - - // when - val result = aiProductBannerDialogShouldBeShown() - - // then - assertThat(result).isFalse - } - - @Test - fun `given no site is selected, when checking if dialog should be shown, then should return false`() { - // given - whenever(selectedSite.getOrNull()).thenReturn(null) - - // when - val result = aiProductBannerDialogShouldBeShown() - - // then - assertThat(result).isFalse - } -} diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt index 5f271b3bfe2..33843234325 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/list/ProductListViewModelTest.kt @@ -13,7 +13,6 @@ import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.media.MediaFileUploadHandler import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ProductTestUtils -import com.woocommerce.android.ui.products.ai.banner.AIProductBannerDialogShouldBeShown import com.woocommerce.android.util.IsWindowClassLargeThanCompact import com.woocommerce.android.viewmodel.BaseUnitTest import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -48,7 +47,6 @@ class ProductListViewModelTest : BaseUnitTest() { private val wooCommerceStore: WooCommerceStore = mock() private val selectedSite: SelectedSite = mock() private val isWindowClassLargeThanCompact: IsWindowClassLargeThanCompact = mock() - private val aiProductBannerDialogShouldBeShown: AIProductBannerDialogShouldBeShown = mock() private val productList = ProductTestUtils.generateProductList() private lateinit var viewModel: ProductListViewModel @@ -70,7 +68,6 @@ class ProductListViewModelTest : BaseUnitTest() { selectedSite, wooCommerceStore, isWindowClassLargeThanCompact, - aiProductBannerDialogShouldBeShown, ) ) } @@ -700,21 +697,4 @@ class ProductListViewModelTest : BaseUnitTest() { mapOf("horizontal_size_class" to "compact") ) } - - @Test - fun `given ai product banner should be shown, when vm init, then ShowAIProductBannerDialog event emitted`() = testBlocking { - // given - whenever(aiProductBannerDialogShouldBeShown()).thenReturn(true) - - createViewModel() - - val events = mutableListOf() - viewModel.event.observeForever { - events.add(it) - } - advanceUntilIdle() - - // then - assertThat(events.count { it is ProductListEvent.ShowAIProductBannerDialog }).isEqualTo(1) - } } From a522f6dbbc1aecd8d4993dcb23776045b0bf8f77 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 30 Sep 2024 15:35:17 +0200 Subject: [PATCH 30/48] Removed wasAIProductDescriptionPromoDialogShown flag --- .../woocommerce/android/e2e/helpers/util/Screen.kt | 3 --- .../main/kotlin/com/woocommerce/android/AppPrefs.kt | 11 ----------- .../kotlin/com/woocommerce/android/AppPrefsWrapper.kt | 2 -- 3 files changed, 16 deletions(-) diff --git a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt index 91b03e268a2..5b445371e6e 100644 --- a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt +++ b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/e2e/helpers/util/Screen.kt @@ -70,9 +70,6 @@ open class Screen { private fun initializeAppPrefs() { AppPrefs.init(getInstrumentation().targetContext.applicationContext) - // hide the promo dialog because it breaks the tests - AppPrefs.wasAIProductDescriptionPromoDialogShown = true - // also hide AI description tooltip to make test more simple AppPrefs.isAIProductDescriptionTooltipDismissed = true diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index 8e07d5a77ef..b2865bc1dbc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -113,7 +113,6 @@ object AppPrefs { NOTIFICATIONS_PERMISSION_BAR, IS_EU_SHIPPING_NOTICE_DISMISSED, HAS_SAVED_PRIVACY_SETTINGS, - WAS_AI_DESCRIPTION_PROMO_DIALOG_SHOWN, IS_AI_DESCRIPTION_TOOLTIP_DISMISSED, NUMBER_OF_TIMES_AI_DESCRIPTION_TOOLTIP_SHOWN, STORE_CREATION_PROFILER_ANSWERS, @@ -975,16 +974,6 @@ object AppPrefs { value = value ) - var wasAIProductDescriptionPromoDialogShown: Boolean - get() = getBoolean( - key = DeletablePrefKey.WAS_AI_DESCRIPTION_PROMO_DIALOG_SHOWN, - default = false - ) - set(value) = setBoolean( - key = DeletablePrefKey.WAS_AI_DESCRIPTION_PROMO_DIALOG_SHOWN, - value = value - ) - var isAIProductDescriptionTooltipDismissed: Boolean get() = getBoolean( key = DeletablePrefKey.IS_AI_DESCRIPTION_TOOLTIP_DISMISSED, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt index 3befd5d5447..42492af450f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -14,8 +14,6 @@ import javax.inject.Inject class AppPrefsWrapper @Inject constructor() { var savedPrivacyBannerSettings by AppPrefs::savedPrivacySettings - var wasAIProductDescriptionPromoDialogShown by AppPrefs::wasAIProductDescriptionPromoDialogShown - var isAIProductDescriptionTooltipDismissed by AppPrefs::isAIProductDescriptionTooltipDismissed var aiContentGenerationTone by AppPrefs::aiContentGenerationTone From 4f3a92e5f54b06b7fb4cc26d68c1f4a145d812cc Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 17:14:17 +0200 Subject: [PATCH 31/48] Add empty BlazeCampaignObjectiveViewModel --- .../objective/BlazeCampaignObjectiveFragment.kt | 15 ++++++++++----- .../objective/BlazeCampaignObjectiveViewModel.kt | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt index fa905e6208e..f5224873a8a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt @@ -5,9 +5,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.material.Text +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController 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 @AndroidEntryPoint @@ -15,6 +18,8 @@ class BlazeCampaignObjectiveFragment : BaseFragment() { override val activityAppBarStatus: AppBarStatus get() = AppBarStatus.Hidden + val viewModel: BlazeCampaignObjectiveViewModel by viewModels() + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return composeView { Text(text = "Select Campaign Objective") @@ -26,10 +31,10 @@ class BlazeCampaignObjectiveFragment : BaseFragment() { } private fun handleEvents() { -// viewModel.event.observe(viewLifecycleOwner) { event -> -// when (event) { -// is MultiLiveEvent.Event.Exit -> findNavController().navigateUp() -// } -// } + viewModel.event.observe(viewLifecycleOwner) { event -> + when (event) { + is MultiLiveEvent.Event.Exit -> findNavController().navigateUp() + } + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveViewModel.kt new file mode 100644 index 00000000000..2d617ba22c9 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveViewModel.kt @@ -0,0 +1,16 @@ +package com.woocommerce.android.ui.blaze.creation.objective + +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit +import com.woocommerce.android.viewmodel.ScopedViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class BlazeCampaignObjectiveViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : ScopedViewModel(savedStateHandle) { + fun onDismissClick() { + triggerEvent(Exit) + } +} From 2c7bd7b2a55dca304bc030de7b4036692c919704 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 30 Sep 2024 16:38:46 +0100 Subject: [PATCH 32/48] Fix issue where inserted values shown in the popup --- .../android/ui/customfields/list/CustomFieldsViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c4c786b2234..10f7df2d7a9 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 @@ -98,7 +98,7 @@ class CustomFieldsViewModel @Inject constructor( customFieldsWithChanges, overlayedFieldId ) { (customFields, _), fieldId -> - customFields.find { it.id == fieldId } + fieldId?.let { customFields.find { it.id == fieldId } } }.asLiveData() fun onBackClick() { From 59538ad5a9f12cfbc1fa4f04a97f87effcd8e406 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 17:40:23 +0200 Subject: [PATCH 33/48] Update FluxC changeset to latest trunk --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 877a2437171..2fefc34db5c 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '3103-6a3c656de5377ca7677548baa0a259edfc9c9ffe' + fluxCVersion = 'trunk-7e2a6b2524a39103ade0b034e40a9e1a013a6d2a' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 63e78a45ffd087825209d9bc4a094e57daa750dc Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 18:04:35 +0200 Subject: [PATCH 34/48] Retrieve saved objective and use it as default --- .../com/woocommerce/android/ui/blaze/BlazeRepository.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt index 9e3bb89bfa6..e1f4b3f3064 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.blaze import android.os.Parcelable +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.AppUrls.FETCH_PAYMENT_METHOD_URL_PATH import com.woocommerce.android.AppUrls.WPCOM_ADD_PAYMENT_METHOD import com.woocommerce.android.BuildConfig @@ -36,7 +37,8 @@ class BlazeRepository @Inject constructor( private val selectedSite: SelectedSite, private val blazeCampaignsStore: BlazeCampaignsStore, private val productDetailRepository: ProductDetailRepository, - private val mediaFilesRepository: MediaFilesRepository + private val mediaFilesRepository: MediaFilesRepository, + private val appPrefsWrapper: AppPrefsWrapper ) { companion object { private const val BLAZE_CAMPAIGN_CREATION_ORIGIN = "wc-android" @@ -192,7 +194,7 @@ class BlazeRepository @Inject constructor( targetUrl = product.permalink, parameters = emptyMap() ), - objectiveId = "" + objectiveId = appPrefsWrapper.blazeCampaignSelectedObjective ) } From f02cbecb370859441d283fe61075f719508acacf Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 18:08:15 +0200 Subject: [PATCH 35/48] Remove unnecessary app prefs dependency --- .../preview/BlazeCampaignCreationPreviewViewModel.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt index 6794239e5a4..805d693d9b0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt @@ -45,8 +45,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val blazeRepository: BlazeRepository, private val resourceProvider: ResourceProvider, private val currencyFormatter: CurrencyFormatter, - private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, - private val appPrefsWrapper: AppPrefsWrapper + private val analyticsTrackerWrapper: AnalyticsTrackerWrapper ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationPreviewFragmentArgs by savedStateHandle.navArgs() private val campaignDetails = savedStateHandle.getNullableStateFlow( @@ -258,7 +257,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private fun getSelectedObjective(objectives: List): CampaignDetailItemUi { val selectedObjectiveDisplayValue = objectives - .find { it.id == appPrefsWrapper.blazeCampaignSelectedObjective } + .find { it.id == campaignDetails.value?.objectiveId } ?.title ?: resourceProvider.getString(R.string.blaze_campaign_preview_details_choose_objective) return CampaignDetailItemUi( From ae3ce1ea35d476b5b9e5251b9b58f76a473a2a20 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 18:21:07 +0200 Subject: [PATCH 36/48] Fix unit test compile issues --- .../com/woocommerce/android/ui/blaze/BlazeRepositoryTest.kt | 6 +++++- .../payment/BlazeCampaignPaymentSummaryViewModelTests.kt | 3 ++- .../preview/BlazeCampaignCreationPreviewViewModelTests.kt | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/BlazeRepositoryTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/BlazeRepositoryTest.kt index 4a510e479a7..af42c4a247e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/BlazeRepositoryTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/BlazeRepositoryTest.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.blaze +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.media.MediaFilesRepository import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.blaze.BlazeRepository.BlazeCampaignImage.RemoteImage @@ -47,6 +48,7 @@ class BlazeRepositoryTest : BaseUnitTest() { onBlocking { fetchWordPressMedia(AD_IMAGE.mediaId) } doReturn Result.success(mediaModel) } + private val appPrefsWrapper: AppPrefsWrapper = mock() private val createCampaignRequestCaptor = argumentCaptor() @@ -54,7 +56,8 @@ class BlazeRepositoryTest : BaseUnitTest() { selectedSite, blazeCampaignsStore, productDetailRepository, - mediaFilesRepository + mediaFilesRepository, + appPrefsWrapper ) @Test @@ -129,6 +132,7 @@ class BlazeRepositoryTest : BaseUnitTest() { budget = DEFAULT_BUDGET, targetingParameters = EMPTY_TARGETING_PARAMETERS, destinationParameters = EMPTY_DESTINATION_PARAMETERS, + objectiveId = "sales", ) private val ENDLESS_CAMPAIGN_DETAILS = DEFAULT_CAMPAIGN_DETAILS.copy( budget = DEFAULT_BUDGET.copy(isEndlessCampaign = true) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModelTests.kt index 98786c84542..e10ddcc2409 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModelTests.kt @@ -41,7 +41,8 @@ class BlazeCampaignPaymentSummaryViewModelTests : BaseUnitTest() { destinationParameters = BlazeRepository.DestinationParameters( targetUrl = "https://test.com", parameters = emptyMap() - ) + ), + objectiveId = "sales" ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt index 385c00127c1..2330fb7d5f8 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt @@ -57,7 +57,8 @@ class BlazeCampaignCreationPreviewViewModelTests : BaseUnitTest() { targetUrl = "http://test_url", parameters = emptyMap() ), - targetingParameters = BlazeRepository.TargetingParameters() + targetingParameters = BlazeRepository.TargetingParameters(), + objectiveId = "sales" ) private val locations = listOf(Location(1, "Location 1"), Location(2, "Location 2")) private val languages = listOf(Language("en", "English"), Language("es", "Spanish")) From 588efcf82f2d42d0de592d0b2a62e499ed265989 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 19:24:29 +0200 Subject: [PATCH 37/48] Remove unused import --- .../creation/preview/BlazeCampaignCreationPreviewViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt index 805d693d9b0..7363bb22809 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt @@ -3,7 +3,6 @@ package com.woocommerce.android.ui.blaze.creation.preview import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_CREATION_CONFIRM_DETAILS_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_CREATION_EDIT_AD_TAPPED From d43c454f5767c5ab1ab4ac5f934eeb883eb0fa73 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 30 Sep 2024 19:30:00 +0200 Subject: [PATCH 38/48] Add missing mock to fix tests --- .../preview/BlazeCampaignCreationPreviewViewModelTests.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt index 2330fb7d5f8..523e513d31e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModelTests.kt @@ -6,6 +6,7 @@ import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.model.UiString import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.BlazeCampaignImage +import com.woocommerce.android.ui.blaze.BlazeRepository.Objective import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.Device import com.woocommerce.android.ui.blaze.Interest @@ -64,6 +65,7 @@ class BlazeCampaignCreationPreviewViewModelTests : BaseUnitTest() { private val languages = listOf(Language("en", "English"), Language("es", "Spanish")) private val interests = listOf(Interest("1", "Interest 1"), Interest("2", "Interest 2")) private val devices = listOf(Device("1", "Device 1"), Device("2", "Device 2")) + private val objectives = listOf(Objective("sales", "Sales", "Get more sales", "")) } private val currencyFormatter: CurrencyFormatter = mock { @@ -78,6 +80,7 @@ class BlazeCampaignCreationPreviewViewModelTests : BaseUnitTest() { on { observeDevices() } doReturn flowOf(devices) on { observeInterests() } doReturn flowOf(interests) on { observeLanguages() } doReturn flowOf(languages) + on { observeObjectives() } doReturn flowOf(objectives) } private val analyticsTracker: AnalyticsTrackerWrapper = mock() private lateinit var viewModel: BlazeCampaignCreationPreviewViewModel From dfa94120c69cfc9e69402d94cb10aae1a9b335e7 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 1 Oct 2024 09:57:57 +0200 Subject: [PATCH 39/48] Removed shadow from check box --- .../totals/payment/success/WooPosTotalsPaymentSuccessScreen.kt | 2 -- 1 file changed, 2 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 f1d5034a356..a7a53e143b1 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 @@ -26,7 +26,6 @@ import androidx.compose.runtime.remember 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.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -176,7 +175,6 @@ private fun CheckMarkIcon( contentAlignment = Alignment.Center, modifier = modifier .size(size) - .shadow(8.dp, CircleShape) .background(WooPosTheme.colors.success, CircleShape) ) { Icon( From a36ae4fe301d27a286cf3f78368bb16c9706adf5 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 1 Oct 2024 10:03:52 +0200 Subject: [PATCH 40/48] Optional colors for a button. Improved preview --- .../composeui/component/WooPosButtons.kt | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt index c7e24c93e6c..2544188965f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt @@ -1,18 +1,19 @@ package com.woocommerce.android.ui.woopos.common.composeui.component import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button +import androidx.compose.material.ButtonColors import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -23,6 +24,10 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme fun WooPosButton( modifier: Modifier = Modifier, text: String, + colors: ButtonColors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.primary, + contentColor = MaterialTheme.colors.onPrimary, + ), enabled: Boolean = true, onClick: () -> Unit, ) { @@ -30,6 +35,7 @@ fun WooPosButton( onClick = onClick, shape = RoundedCornerShape(8.dp), enabled = enabled, + colors = colors, modifier = modifier .fillMaxWidth() .height(72.dp), @@ -114,54 +120,41 @@ fun WooPosOutlinedButton( @Composable @WooPosPreview -fun WooPosButtonPreview() { +fun WooPosButtonsPreview() { WooPosTheme { - Box( + Column( modifier = Modifier .fillMaxSize() .padding(32.dp), - contentAlignment = Alignment.Center ) { - WooPosButton( - text = "Button", + WooPosButtonLarge( + text = "Button Large", onClick = {}, ) - } - } -} -@Composable -@WooPosPreview -fun WooPosButtonLargePreview() { - WooPosTheme { - Box( - modifier = Modifier - .fillMaxSize() - .padding(32.dp), - contentAlignment = Alignment.Center - ) { - WooPosButtonLarge( - text = "Button", + Spacer(modifier = Modifier.height(16.dp)) + + WooPosOutlinedButton( + text = "Button Outlined", onClick = {}, ) - } - } -} -@Composable -@WooPosPreview -fun WooPosOutlinedButtonPreview() { - WooPosTheme { - Box( - modifier = Modifier - .fillMaxSize() - .padding(32.dp), - contentAlignment = Alignment.Center - ) { - WooPosOutlinedButton( + Spacer(modifier = Modifier.height(16.dp)) + + WooPosButton( text = "Button", onClick = {}, ) + + Spacer(modifier = Modifier.height(16.dp)) + + WooPosButton( + text = "Button Black And White", + colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.onBackground + ), + onClick = {}, + ) } } } From 4352fe4c7c8a32e1cf48da7a18c3d5a6a573a050 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 1 Oct 2024 10:08:57 +0200 Subject: [PATCH 41/48] Use wooposbutton instead of custom one with passed background color --- .../success/WooPosTotalsPaymentSuccessScreen.kt | 17 ++++------------- 1 file changed, 4 insertions(+), 13 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 a7a53e143b1..c93f42d1a78 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 @@ -10,8 +10,6 @@ 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 @@ -34,6 +32,7 @@ import androidx.constraintlayout.compose.ConstraintLayout import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState import kotlinx.coroutines.delay @@ -120,7 +119,7 @@ fun WooPosPaymentSuccessScreen( } ) - Button( + WooPosButton( modifier = Modifier .constrainAs(button) { bottom.linkTo(parent.bottom) @@ -132,17 +131,9 @@ fun WooPosPaymentSuccessScreen( colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.onBackground ), - shape = RoundedCornerShape(8.dp), onClick = onNewTransactionClicked, - ) { - Text( - text = stringResource(R.string.woopos_new_order_button), - style = MaterialTheme.typography.h5, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colors.background, - textAlign = TextAlign.Center - ) - } + text = stringResource(R.string.woopos_new_order_button), + ) } } } From 8cf93cd3a7067cd057f2d1674156d8d91e2a6111 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 1 Oct 2024 11:16:32 +0200 Subject: [PATCH 42/48] Update fluxc changeset --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d0ed888e5ef..68a33305f93 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2.98.0' + fluxCVersion = 'trunk-06e2a179771b9d8c726a15843d56fc897b6d5ca0' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 8f2d555489ce1b9f209ec3e4a8623b77bedd3ac2 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 1 Oct 2024 13:00:54 +0200 Subject: [PATCH 43/48] Updates release notes --- RELEASE-NOTES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 1241e0cfd17..29fbca31b5e 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,12 +1,13 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] *** Use [*****] to indicate smoke tests of all critical flows should be run on the final APK before release (e.g. major library or targetSdk updates). *** For entries which are touching the Android Wear app's, start entry with `[WEAR]` too. -20.8 +20.7 ----- - [**] Improve barcode scanner reading accuracy [https://github.com/woocommerce/woocommerce-android/pull/12673] - [Internal] AI product creation banner is removed [https://github.com/woocommerce/woocommerce-android/pull/12705] - [*] [Login] Fix an issue where the app doesn't show the correct error screen when application passwords are disabled [https://github.com/woocommerce/woocommerce-android/pull/12717] - [**] Fixed bug with coupons disappearing from the order creation screen unexpectedly [https://github.com/woocommerce/woocommerce-android/pull/12724] +- [Internal] Fixes crash [https://github.com/woocommerce/woocommerce-android/issues/12715] 20.6 ----- From dbcf0be884d7f1750a82464857a01ecf3de6063c Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 1 Oct 2024 13:07:12 +0100 Subject: [PATCH 44/48] Disable focus on the JSON custom field text fields --- .../ui/customfields/list/CustomFieldsScreen.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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 2cc989fc2ae..27584b895c0 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 @@ -3,6 +3,9 @@ package com.woocommerce.android.ui.customfields.list import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -62,6 +65,8 @@ import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.customfields.CustomField import com.woocommerce.android.ui.customfields.CustomFieldContentType import com.woocommerce.android.ui.customfields.CustomFieldUiModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import org.json.JSONArray import org.json.JSONObject @@ -262,6 +267,15 @@ private fun JsonCustomFieldViewer( customField: CustomFieldUiModel, onDismiss: () -> Unit ) { + // We use this to disable focus on the text fields used to show the key and value as it's not needed for our case + val inactiveInteractionSource = remember { + object : MutableInteractionSource { + override val interactions: Flow = emptyFlow() + override suspend fun emit(interaction: Interaction) {} + override fun tryEmit(interaction: Interaction): Boolean = false + } + } + Dialog(onDismissRequest = onDismiss) { Surface( shape = MaterialTheme.shapes.medium, @@ -285,7 +299,8 @@ private fun JsonCustomFieldViewer( onValueChange = {}, label = { Text(text = stringResource(id = R.string.custom_fields_editor_key_label)) }, readOnly = true, - modifier = Modifier + interactionSource = inactiveInteractionSource, + modifier = Modifier.focusable(enabled = false) ) OutlinedTextField( @@ -293,6 +308,7 @@ private fun JsonCustomFieldViewer( onValueChange = {}, label = { Text(text = stringResource(id = R.string.custom_fields_editor_value_label)) }, readOnly = true, + interactionSource = inactiveInteractionSource, modifier = Modifier.weight(1f, fill = false) ) From c5eb9e5c5d1408cbd7925c53be003d2bd0f3e273 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 1 Oct 2024 13:10:20 +0100 Subject: [PATCH 45/48] Some UI updates to the JSON viewer --- .../android/ui/customfields/list/CustomFieldsScreen.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 27584b895c0..46cda466e96 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 @@ -58,7 +58,6 @@ import com.woocommerce.android.ui.compose.component.DiscardChangesDialog import com.woocommerce.android.ui.compose.component.ExpandableTopBanner import com.woocommerce.android.ui.compose.component.ProgressDialog import com.woocommerce.android.ui.compose.component.Toolbar -import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.component.WCTextButton import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground @@ -279,6 +278,7 @@ private fun JsonCustomFieldViewer( Dialog(onDismissRequest = onDismiss) { Surface( shape = MaterialTheme.shapes.medium, + modifier = Modifier.padding(vertical = 16.dp) ) { val jsonFormatted = remember(customField.value) { runCatching { @@ -292,7 +292,7 @@ private fun JsonCustomFieldViewer( Column( verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.padding(16.dp) + modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp) ) { OutlinedTextField( value = customField.key, @@ -312,9 +312,9 @@ private fun JsonCustomFieldViewer( modifier = Modifier.weight(1f, fill = false) ) - WCColoredButton( + WCTextButton( onClick = onDismiss, - modifier = Modifier.align(Alignment.CenterHorizontally) + modifier = Modifier.align(Alignment.End) ) { Text(text = stringResource(id = R.string.close)) } From f3b515ebd312e5ef787c2eb38037c32757facf28 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 1 Oct 2024 13:11:16 +0100 Subject: [PATCH 46/48] Bump fluxc --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 508012d3f68..0ec13e3cdc5 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '3102-bd6d5b76934f3662d55106b701530c451739cdd2' + fluxCVersion = 'trunk-373bc6d30f8d9da6b7750f3ddc38929611b50056' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From e4a8e17299613d6bd33a88815e4456b90274bd3b Mon Sep 17 00:00:00 2001 From: Rooney Date: Tue, 1 Oct 2024 10:00:48 -0400 Subject: [PATCH 47/48] Update release notes --- RELEASE-NOTES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 1241e0cfd17..323ec1556c1 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,12 +1,13 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] *** Use [*****] to indicate smoke tests of all critical flows should be run on the final APK before release (e.g. major library or targetSdk updates). *** For entries which are touching the Android Wear app's, start entry with `[WEAR]` too. -20.8 +20.7 ----- - [**] Improve barcode scanner reading accuracy [https://github.com/woocommerce/woocommerce-android/pull/12673] - [Internal] AI product creation banner is removed [https://github.com/woocommerce/woocommerce-android/pull/12705] - [*] [Login] Fix an issue where the app doesn't show the correct error screen when application passwords are disabled [https://github.com/woocommerce/woocommerce-android/pull/12717] - [**] Fixed bug with coupons disappearing from the order creation screen unexpectedly [https://github.com/woocommerce/woocommerce-android/pull/12724] +- [*] Fixed incorrect instructions on "What is Tap to Pay" screen in the Payments section [https://github.com/woocommerce/woocommerce-android/pull/12709] 20.6 ----- From 8452fc707f5e3dadfc13ab8e7915938c26397a55 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 1 Oct 2024 16:38:39 +0100 Subject: [PATCH 48/48] Fix detekt issue --- .../android/ui/customfields/list/CustomFieldsScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 46cda466e96..1c869665465 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 @@ -270,7 +270,7 @@ private fun JsonCustomFieldViewer( val inactiveInteractionSource = remember { object : MutableInteractionSource { override val interactions: Flow = emptyFlow() - override suspend fun emit(interaction: Interaction) {} + override suspend fun emit(interaction: Interaction) = Unit override fun tryEmit(interaction: Interaction): Boolean = false } }