diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 89d82787fb1..208d31c615e 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -4,8 +4,11 @@ 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] +- [*] Fixed incorrect instructions on "What is Tap to Pay" screen in the Payments section [https://github.com/woocommerce/woocommerce-android/pull/12709] 20.6 ----- 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 4abf337fc9f..3f8582a7dba 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -113,12 +113,10 @@ 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, AI_CONTENT_GENERATION_TONE, - AI_PRODUCT_CREATION_IS_FIRST_ATTEMPT, BLAZE_FIRST_TIME_WITHOUT_CAMPAIGN, BLAZE_CAMPAIGN_CREATED, BLAZE_CELEBRATION_SCREEN_SHOWN, @@ -129,6 +127,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 +273,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) { @@ -976,16 +979,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, @@ -1013,16 +1006,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..a33fc00387e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -14,14 +14,10 @@ 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 - var aiProductCreationIsFirstAttempt by AppPrefs::aiProductCreationIsFirstAttempt - var isBlazeCelebrationScreenShown by AppPrefs::isBlazeCelebrationScreenShown var blazeFirstTimeWithoutCampaign by AppPrefs::blazeFirstTimeWithoutCampaign @@ -40,6 +36,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/BlazeRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt index 2d94bf3da21..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" @@ -191,7 +193,8 @@ class BlazeRepository @Inject constructor( destinationParameters = DestinationParameters( targetUrl = product.permalink, parameters = emptyMap() - ) + ), + objectiveId = appPrefsWrapper.blazeCampaignSelectedObjective ) } @@ -318,7 +321,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 +388,7 @@ class BlazeRepository @Inject constructor( val budget: Budget, val targetingParameters: TargetingParameters, val destinationParameters: DestinationParameters, + val objectiveId: String ) : Parcelable sealed interface BlazeCampaignImage : Parcelable { 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..f5224873a8a --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/objective/BlazeCampaignObjectiveFragment.kt @@ -0,0 +1,40 @@ +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 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 +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") + } + } + + 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/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) + } +} 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/BlazeCampaignCreationPreviewScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt index 9865dff0cbd..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 @@ -319,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 @@ -449,7 +449,12 @@ fun CampaignScreenPreview() { displayValue = "https://www.myer.com.au/p/white-t-shirt-797334760-797334760", onItemSelected = {}, maxLinesValue = 1, - ) + ), + selectedObjective = CampaignDetailItemUi( + displayTitle = stringResource(R.string.blaze_campaign_preview_details_objective), + displayValue = "Sales", + onItemSelected = {}, + ), ) ), 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..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 @@ -15,6 +15,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 @@ -43,7 +44,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val blazeRepository: BlazeRepository, private val resourceProvider: ResourceProvider, private val currencyFormatter: CurrencyFormatter, - private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, + private val analyticsTrackerWrapper: AnalyticsTrackerWrapper ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationPreviewFragmentArgs by savedStateHandle.navArgs() private val campaignDetails = savedStateHandle.getNullableStateFlow( @@ -53,6 +54,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) @@ -60,8 +62,10 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val viewState = combine( campaignDetails.filterNotNull(), adDetailsState, - dialogState - ) { campaignDetails, adDetailsState, dialogState -> + dialogState, + blazeRepository.observeObjectives() + ) { campaignDetails, adDetailsState, dialogState, objectives -> + campaignObjectives = objectives CampaignPreviewUiState( adDetails = when (adDetailsState) { AdDetailsUiState.LOADING -> AdDetailsUi.Loading @@ -234,6 +238,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) } } + blazeRepository.fetchObjectives() } } @@ -245,9 +250,22 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( getTargetLocationsDetails(), getTargetInterestsDetails(), ), - destinationUrl = getTargetDestinationDetails() + destinationUrl = getTargetDestinationDetails(), + selectedObjective = getSelectedObjective(campaignObjectives) ) + private fun getSelectedObjective(objectives: List): CampaignDetailItemUi { + val selectedObjectiveDisplayValue = objectives + .find { it.id == campaignDetails.value?.objectiveId } + ?.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 = { triggerEvent(NavigateToObjectiveSelectionScreen) } + ) + } + private fun CampaignDetails.getBudgetDetails() = CampaignDetailItemUi( displayTitle = resourceProvider.getString(R.string.blaze_campaign_preview_details_budget), @@ -365,6 +383,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val budget: CampaignDetailItemUi, val targetDetails: List, val destinationUrl: CampaignDetailItemUi, + val selectedObjective: CampaignDetailItemUi ) data class CampaignDetailItemUi( @@ -404,4 +423,6 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( data class NavigateToPaymentSummary( val campaignDetails: CampaignDetails ) : MultiLiveEvent.Event() + + data object NavigateToObjectiveSelectionScreen : MultiLiveEvent.Event() } 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 { 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..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 @@ -3,6 +3,10 @@ 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 import androidx.compose.foundation.layout.Row @@ -20,9 +24,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,6 +52,7 @@ 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 @@ -57,6 +64,10 @@ 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 @Composable fun CustomFieldsScreen( @@ -75,6 +86,13 @@ fun CustomFieldsScreen( snackbarHostState = snackbarHostState ) } + + viewModel.overlayedField.observeAsState().value?.let { overlayedField -> + JsonCustomFieldViewer( + customField = overlayedField, + onDismiss = viewModel::onOverlayedFieldDismissed + ) + } } @OptIn(ExperimentalMaterialApi::class) @@ -243,6 +261,68 @@ private fun CustomFieldItem( } } +@Composable +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) = Unit + override fun tryEmit(interaction: Interaction): Boolean = false + } + } + + Dialog(onDismissRequest = onDismiss) { + Surface( + shape = MaterialTheme.shapes.medium, + modifier = Modifier.padding(vertical = 16.dp) + ) { + val jsonFormatted = remember(customField.value) { + runCatching { + if (customField.value.trimStart().startsWith("[")) { + JSONArray(customField.value).toString(4) + } else { + JSONObject(customField.value).toString(4) + } + }.getOrDefault(customField.value) + } + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp) + ) { + OutlinedTextField( + value = customField.key, + onValueChange = {}, + label = { Text(text = stringResource(id = R.string.custom_fields_editor_key_label)) }, + readOnly = true, + interactionSource = inactiveInteractionSource, + modifier = Modifier.focusable(enabled = false) + ) + + OutlinedTextField( + value = jsonFormatted, + onValueChange = {}, + label = { Text(text = stringResource(id = R.string.custom_fields_editor_value_label)) }, + readOnly = true, + interactionSource = inactiveInteractionSource, + modifier = Modifier.weight(1f, fill = false) + ) + + WCTextButton( + onClick = onDismiss, + modifier = Modifier.align(Alignment.End) + ) { + Text(text = stringResource(id = R.string.close)) + } + } + } + } +} + @LightDarkThemePreviews @Preview @Composable @@ -274,3 +354,21 @@ private fun CustomFieldsScreenPreview() { ) } } + +@LightDarkThemePreviews +@Preview +@Composable +private fun JsonCustomFieldViewerPreview() { + WooThemeWithBackground { + JsonCustomFieldViewer( + customField = CustomFieldUiModel( + CustomField( + id = 0, + key = "key1", + value = "[{\"key\": \"value\"}]" + ) + ), + 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 c9dc9e8653f..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 @@ -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,43 @@ 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( + scope = viewModelScope, + initialValue = null, + clazz = Long::class.java, + key = "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 +94,13 @@ class CustomFieldsViewModel @Inject constructor( ) }.asLiveData() + val overlayedField = combine( + customFieldsWithChanges, + overlayedFieldId + ) { (customFields, _), fieldId -> + fieldId?.let { customFields.find { it.id == fieldId } } + }.asLiveData() + fun onBackClick() { if (pendingChanges.value.hasChanges) { showDiscardChangesDialog.value = true @@ -95,7 +120,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) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialog.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialog.kt deleted file mode 100644 index 54e2171882e..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialog.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.woocommerce.android.ui.dashboard - -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 AIProductDescriptionDialog( - 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 { - AIProductDescriptionDialog({}, {}) - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogFragment.kt deleted file mode 100644 index 4302a25f2d8..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogFragment.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.woocommerce.android.ui.dashboard - -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.dashboard.AIProductDescriptionDialogViewModel.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() { - companion object { - private const val TABLET_LANDSCAPE_WIDTH_RATIO = 0.35f - } - - private val viewModel: AIProductDescriptionDialogViewModel 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 { - AIProductDescriptionDialog(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/dashboard/AIProductDescriptionDialogViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogViewModel.kt deleted file mode 100644 index 3eeeca490e7..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/AIProductDescriptionDialogViewModel.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.woocommerce.android.ui.dashboard - -import androidx.lifecycle.SavedStateHandle -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 javax.inject.Inject - -@HiltViewModel -class AIProductDescriptionDialogViewModel @Inject constructor( - savedState: SavedStateHandle -) : ScopedViewModel(savedState) { - fun onTryNowButtonClicked() { - triggerEvent(TryAIProductDescriptionGeneration) - } - - fun onDismissButtonClicked() { - triggerEvent(Exit) - } - - object TryAIProductDescriptionGeneration : MultiLiveEvent.Event() -} 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/list/ProductListEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt new file mode 100644 index 00000000000..0d3b5b5f7ed --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListEvent.kt @@ -0,0 +1,41 @@ +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..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 @@ -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 @@ -503,9 +503,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 +513,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 f9974ec37bc..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 @@ -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 @@ -21,12 +20,12 @@ 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.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 @@ -40,8 +39,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 @@ -79,7 +76,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 { @@ -119,7 +116,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 @@ -500,7 +497,7 @@ class ProductListViewModel @Inject constructor( private fun enterSelectionMode(count: Int) { viewState = viewState.copy( - productListState = ProductListState.Selecting, + productListState = ProductListViewState.ProductListState.Selecting, isAddProductButtonVisible = false, selectionCount = count ) @@ -508,7 +505,7 @@ class ProductListViewModel @Inject constructor( fun exitSelectionMode() { viewState = viewState.copy( - productListState = ProductListState.Browsing, + productListState = ProductListViewState.ProductListState.Browsing, isAddProductButtonVisible = true, selectionCount = null ) @@ -684,6 +681,7 @@ class ProductListViewModel @Inject constructor( delay = EXPAND_COLLAPSE_ANIMATION_DURATION_MILLIS ) } + else -> { exitSelectionMode() onFailure() @@ -739,65 +737,4 @@ 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 - } - - 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/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..df85685301c --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListViewState.kt @@ -0,0 +1,32 @@ +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: 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/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 = {}, + ) } } } 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..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 @@ -26,7 +24,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 @@ -35,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 @@ -121,7 +119,7 @@ fun WooPosPaymentSuccessScreen( } ) - Button( + WooPosButton( modifier = Modifier .constrainAs(button) { bottom.linkTo(parent.bottom) @@ -133,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), + ) } } } @@ -176,7 +166,6 @@ private fun CheckMarkIcon( contentAlignment = Alignment.Center, modifier = modifier .size(size) - .shadow(8.dp, CircleShape) .background(WooPosTheme.colors.success, CircleShape) ) { Icon( 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..ee5da816aff 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) @@ -41,9 +41,10 @@ fun SavedStateHandle.getNullableStateFlow( key: String = clazz.name ): MutableStateFlow { if (!Parcelable::class.java.isAssignableFrom(clazz) && - !Serializable::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) 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 77e7a85fcc7..00000000000 Binary files a/WooCommerce/src/main/res/drawable-hdpi/img_ai_dialog.webp and /dev/null differ diff --git a/WooCommerce/src/main/res/drawable-mdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-mdpi/img_ai_dialog.webp deleted file mode 100644 index 967a7e1fca1..00000000000 Binary files a/WooCommerce/src/main/res/drawable-mdpi/img_ai_dialog.webp and /dev/null differ 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 e3c5429e00c..00000000000 Binary files a/WooCommerce/src/main/res/drawable-night-hdpi/img_ai_dialog.webp and /dev/null differ 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 9be0a1714c1..00000000000 Binary files a/WooCommerce/src/main/res/drawable-night-mdpi/img_ai_dialog.webp and /dev/null differ diff --git a/WooCommerce/src/main/res/drawable-night-xhdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-night-xhdpi/img_ai_dialog.webp deleted file mode 100644 index 7dc620623a3..00000000000 Binary files a/WooCommerce/src/main/res/drawable-night-xhdpi/img_ai_dialog.webp and /dev/null differ diff --git a/WooCommerce/src/main/res/drawable-night-xxhdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-night-xxhdpi/img_ai_dialog.webp deleted file mode 100644 index dcffd148947..00000000000 Binary files a/WooCommerce/src/main/res/drawable-night-xxhdpi/img_ai_dialog.webp and /dev/null differ 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 3d791a4abf7..00000000000 Binary files a/WooCommerce/src/main/res/drawable-night-xxxhdpi/img_ai_dialog.webp and /dev/null differ diff --git a/WooCommerce/src/main/res/drawable-xhdpi/img_ai_dialog.webp b/WooCommerce/src/main/res/drawable-xhdpi/img_ai_dialog.webp deleted file mode 100644 index 48e844bf8ac..00000000000 Binary files a/WooCommerce/src/main/res/drawable-xhdpi/img_ai_dialog.webp and /dev/null differ 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 d5dd8e3b422..00000000000 Binary files a/WooCommerce/src/main/res/drawable-xxhdpi/img_ai_dialog.webp and /dev/null differ 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 7a7334b214f..00000000000 Binary files a/WooCommerce/src/main/res/drawable-xxxhdpi/img_ai_dialog.webp and /dev/null differ 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 @@ + + diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index d2e7483e45f..dbceed064a8 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 @@ - @@ -748,10 +745,6 @@ - حل المشكلات قد يرتبط ذلك بنزاع مع الاضافة. يرجى المحاولة مرة أخرى لاحقًا أو تواصل معنا وستسرنا مساعدتك! تعذر علينا تحميل بياناتك - أضف أوصاف في لمح البصر باستخدام الذكاء الاصطناعي. جرّب الميزة التي نطرحها الآن! - إضافة وصف المنتج باستخدام الذكاء الاصطناعي فهمت ترجى مراعاة أن وصف هذا المنتج تم إنشاؤه باستخدام أداة لدينا مدعومة من الذكاء الاصطناعي. ترجى مراجعة المحتوى وتحريره لضمان محاذاته مع علامتك التجارية ورسائلك. بداية رائعة! 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..95a45c89efe 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. 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. @@ -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! @@ -3901,6 +3899,8 @@ Details Audience Budget + Campaign objective + Choose campaign objective Language Devices Location 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..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 @@ -57,12 +58,14 @@ 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")) 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 { @@ -77,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 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() + } } 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) } diff --git a/build.gradle b/build.gradle index d0ed888e5ef..0ec13e3cdc5 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2.98.0' + fluxCVersion = 'trunk-373bc6d30f8d9da6b7750f3ddc38929611b50056' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' @@ -143,6 +143,7 @@ ext { // Compose and its module versions need to be consistent with each other (for example 'compose-theme-adapter') composeBOMVersion = "2024.04.00" composeCompilerVersion = "1.5.9" + composeAccompanistVersion = "0.32.0" wearComposeVersion = "1.3.1" // Testing