From 21267a2454e39ad6a3e107848df1bd89c9889076 Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 27 Dec 2023 18:31:22 -0600 Subject: [PATCH 001/160] track range id --- .../com/woocommerce/android/ui/mystore/MyStoreViewModel.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt index caaef6431c2..5c1c3c5bbd5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt @@ -61,6 +61,7 @@ import org.greenrobot.eventbus.ThreadMode import org.wordpress.android.fluxc.model.WCRevenueStatsModel import org.wordpress.android.fluxc.store.WCStatsStore.StatsGranularity import org.wordpress.android.fluxc.store.WooCommerceStore +import org.wordpress.android.fluxc.utils.putIfNotNull import org.wordpress.android.util.FormatUtils import org.wordpress.android.util.PhotonUtils import java.math.BigDecimal @@ -270,7 +271,10 @@ class MyStoreViewModel @Inject constructor( ) analyticsTrackerWrapper.track( AnalyticsEvent.DASHBOARD_MAIN_STATS_LOADED, - mapOf(AnalyticsTracker.KEY_RANGE to selectedGranularity.name.lowercase()) + buildMap { + put(AnalyticsTracker.KEY_RANGE, selectedGranularity.name.lowercase()) + putIfNotNull(AnalyticsTracker.KEY_ID to it.stats?.rangeId) + } ) } From b9b85a3b525b1d1e541707a9bcfc71285e2522de Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 14:36:21 -0300 Subject: [PATCH 002/160] Add gift card presence track to order creation success --- .../com/woocommerce/android/analytics/AnalyticsTracker.kt | 1 + .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt index 167992dd50a..895da3f876f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt @@ -286,6 +286,7 @@ class AnalyticsTracker private constructor( const val KEY_TIME_ELAPSED_SINCE_CARD_COLLECT_PAYMENT_IN_MILLIS = "milliseconds_since_card_collect_payment_flow" const val KEY_COUPONS_COUNT = "coupons_count" + const val KEY_USE_GIFT_CARD = "use_gift_card" const val KEY_WAS_ECOMMERCE_TRIAL = "was_ecommerce_trial" const val KEY_PLAN_PRODUCT_SLUG = "plan_product_slug" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index 1bccc44f802..4749b720fb8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -164,6 +164,7 @@ import java.math.BigDecimal import java.util.Date import javax.inject.Inject import com.woocommerce.android.model.Product as ModelProduct +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_USE_GIFT_CARD @HiltViewModel @Suppress("LargeClass") @@ -1177,6 +1178,7 @@ class OrderCreateEditViewModel @Inject constructor( timeElapsed.toString() } mutableMap[KEY_COUPONS_COUNT] = orderDraft.value?.couponLines?.size ?: 0 + mutableMap[KEY_USE_GIFT_CARD] = orderDraft.value?.selectedGiftCard.isNotNullOrEmpty() } ) } From 828a529136511f09e89634574f250d95dd7615d6 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 14:36:58 -0300 Subject: [PATCH 003/160] Add gift card presence track to order creation failure --- .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index 4749b720fb8..da5bf10e98c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -1329,7 +1329,8 @@ class OrderCreateEditViewModel @Inject constructor( mapOf( KEY_ERROR_CONTEXT to this::class.java.simpleName, KEY_ERROR_TYPE to (it as? WooException)?.error?.type?.name, - KEY_ERROR_DESC to it.message + KEY_ERROR_DESC to it.message, + KEY_USE_GIFT_CARD to orderDraft.value?.selectedGiftCard.isNotNullOrEmpty() ) ) } From 52b00f86a9da1a899a47c8f891a6a224043d9047 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 14:44:31 -0300 Subject: [PATCH 004/160] Add gift card presence track to order sync failure --- .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index da5bf10e98c..4bee96dd964 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -1347,7 +1347,10 @@ class OrderCreateEditViewModel @Inject constructor( private fun trackOrderSyncFailed(throwable: Throwable) { tracker.track( stat = AnalyticsEvent.ORDER_SYNC_FAILED, - properties = mapOf(KEY_FLOW to flow), + properties = mapOf( + KEY_FLOW to flow, + KEY_USE_GIFT_CARD to orderDraft.value?.selectedGiftCard.isNotNullOrEmpty() + ), errorContext = this::class.java.simpleName, errorType = (throwable as? WooException)?.error?.type?.name, errorDescription = (throwable as? WooException)?.error?.message From 3aea45e17285cea9131bdffde94892dae4b9fc6a Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 14:48:16 -0300 Subject: [PATCH 005/160] Add gift card new analytics events --- .../kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt index 6b269a756c9..b1b12695812 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt @@ -290,6 +290,9 @@ enum class AnalyticsEvent(val siteless: Boolean = false) { TAX_RATE_AUTO_TAX_RATE_SET_NEW_RATE_FOR_ORDER_TAPPED, TAX_RATE_AUTO_TAX_RATE_CLEAR_ADDRESS_TAPPED, ORDER_FORM_TOTALS_PANEL_TOGGLED, + ORDER_FORM_ADD_GIFT_CARD_CTA_SHOWN, + ORDER_FORM_ADD_GIFT_CARD_CTA_TAPPED, + ORDER_FORM_GIFT_CARD_SET, // -- Custom Amounts ORDER_CREATION_ADD_CUSTOM_AMOUNT_TAPPED, From b56bfedb5f6e0b34f5079da390d9c8d37ebc9dd9 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 16:12:08 -0300 Subject: [PATCH 006/160] Add all three gift card operations calls --- .../android/analytics/AnalyticsTracker.kt | 1 + .../creation/OrderCreateEditViewModel.kt | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt index 895da3f876f..462d08ac54f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt @@ -287,6 +287,7 @@ class AnalyticsTracker private constructor( const val KEY_COUPONS_COUNT = "coupons_count" const val KEY_USE_GIFT_CARD = "use_gift_card" + const val KEY_IS_GIFT_CARD_REMOVED = "removed" const val KEY_WAS_ECOMMERCE_TRIAL = "was_ecommerce_trial" const val KEY_PLAN_PRODUCT_SLUG = "plan_product_slug" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index 4bee96dd964..1b7835dab3b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -164,6 +164,7 @@ import java.math.BigDecimal import java.util.Date import javax.inject.Inject import com.woocommerce.android.model.Product as ModelProduct +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_IS_GIFT_CARD_REMOVED import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_USE_GIFT_CARD @HiltViewModel @@ -1709,6 +1710,30 @@ class OrderCreateEditViewModel @Inject constructor( } } + private fun trackGiftCardCTAShown() { + tracker.track( + AnalyticsEvent.ORDER_FORM_ADD_GIFT_CARD_CTA_SHOWN, + mapOf(KEY_FLOW to flow) + ) + } + + private fun trackGiftCardCTAClicked() { + tracker.track( + AnalyticsEvent.ORDER_FORM_ADD_GIFT_CARD_CTA_TAPPED, + mapOf(KEY_FLOW to flow) + ) + } + + private fun trackGiftCardSet(giftCardWasRemoved: Boolean) { + tracker.track( + AnalyticsEvent.ORDER_FORM_GIFT_CARD_SET, + mapOf( + KEY_FLOW to flow, + KEY_IS_GIFT_CARD_REMOVED to giftCardWasRemoved + ) + ) + } + @Parcelize data class ViewState( val isProgressDialogShown: Boolean = false, From ea6965155372d4e9133ae75c9b6e08c01e947672 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 16:53:26 -0300 Subject: [PATCH 007/160] Connect gift card analytic events with View Model events --- .../ui/orders/creation/OrderCreateEditViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index 1b7835dab3b..d2a3fa7ce03 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -166,6 +166,7 @@ import javax.inject.Inject import com.woocommerce.android.model.Product as ModelProduct import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_IS_GIFT_CARD_REMOVED import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_USE_GIFT_CARD +import kotlinx.coroutines.flow.firstOrNull @HiltViewModel @Suppress("LargeClass") @@ -221,6 +222,7 @@ class OrderCreateEditViewModel @Inject constructor( scope = viewModelScope, initialValue = HashMap() ) + val isGiftCardExtensionEnabled get() = pluginsInformation.value[WOO_GIFT_CARDS.pluginName] ?.isOperational ?: false @@ -1110,10 +1112,13 @@ class OrderCreateEditViewModel @Inject constructor( } fun onAddGiftCardButtonClicked() { + trackGiftCardCTAClicked() triggerEvent(OrderCreateEditNavigationTarget.AddGiftCard) } fun onGiftCardSelected(selectedGiftCard: String) { + val giftCardWasRemoved = selectedGiftCard.isEmpty() && _selectedGiftCard.value.isNotEmpty() + trackGiftCardSet(giftCardWasRemoved) _selectedGiftCard.update { selectedGiftCard } } @@ -1296,6 +1301,7 @@ class OrderCreateEditViewModel @Inject constructor( .onEach { val isGiftCardExtensionEnabled = it[WOO_GIFT_CARDS.pluginName]?.isOperational ?: false viewState = viewState.copy(shouldDisplayAddGiftCardButton = isGiftCardExtensionEnabled) + if (isGiftCardExtensionEnabled) { trackGiftCardCTAAvailable() } }.launchIn(viewModelScope) pluginsInformation.update { @@ -1710,7 +1716,7 @@ class OrderCreateEditViewModel @Inject constructor( } } - private fun trackGiftCardCTAShown() { + private fun trackGiftCardCTAAvailable() { tracker.track( AnalyticsEvent.ORDER_FORM_ADD_GIFT_CARD_CTA_SHOWN, mapOf(KEY_FLOW to flow) From 727ca27a7e2fd1911ac06110909da4575d4d0bda Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 17:13:53 -0300 Subject: [PATCH 008/160] Add gift card tracks unit test coverage --- .../creation/UnifiedOrderEditViewModelTest.kt | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt index ee492233821..5b805087725 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt @@ -453,13 +453,72 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { verify(tracker).track( stat = AnalyticsEvent.ORDER_SYNC_FAILED, - properties = mapOf(AnalyticsTracker.KEY_FLOW to tracksFlow), + properties = mapOf( + AnalyticsTracker.KEY_FLOW to tracksFlow, + AnalyticsTracker.KEY_USE_GIFT_CARD to false + ), errorContext = sut::class.java.simpleName, errorType = wooError.type.name, errorDescription = wooError.message ) } + + @Test + fun `given a standard order creation, when add gift card is clicked, then track expected event`() = testBlocking { + initMocksForAnalyticsWithOrder(defaultOrderValue) + createSut() + + sut.onAddGiftCardButtonClicked() + + verify(tracker).track( + AnalyticsEvent.ORDER_FORM_ADD_GIFT_CARD_CTA_TAPPED, + mapOf(AnalyticsTracker.KEY_FLOW to tracksFlow) + ) + } + + @Test + fun `given a standard order creation, when a gift card code is set, then track expected event`() = testBlocking { + initMocksForAnalyticsWithOrder(defaultOrderValue) + createSut() + + sut.onGiftCardSelected("abc") + + verify(tracker).track( + AnalyticsEvent.ORDER_FORM_GIFT_CARD_SET, + mapOf( + AnalyticsTracker.KEY_FLOW to tracksFlow, + AnalyticsTracker.KEY_IS_GIFT_CARD_REMOVED to false + ) + ) + } + + @Test + fun `given a order creation with gift card already set, when a gift card is removed, then track expected event`() = testBlocking { + initMocksForAnalyticsWithOrder(defaultOrderValue) + createSut() + + sut.onGiftCardSelected("abc") + + verify(tracker).track( + AnalyticsEvent.ORDER_FORM_GIFT_CARD_SET, + mapOf( + AnalyticsTracker.KEY_FLOW to tracksFlow, + AnalyticsTracker.KEY_IS_GIFT_CARD_REMOVED to false + ) + ) + + sut.onGiftCardSelected("") + + verify(tracker).track( + AnalyticsEvent.ORDER_FORM_GIFT_CARD_SET, + mapOf( + AnalyticsTracker.KEY_FLOW to tracksFlow, + AnalyticsTracker.KEY_IS_GIFT_CARD_REMOVED to true + ) + ) + } + // region Scanned and Deliver @Test fun `when scan succeeds, then set isUpdatingOrderDraft to true`() { From e83331166ba752a295e8047c320ad956c419d245 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Wed, 17 Jan 2024 17:45:34 -0300 Subject: [PATCH 009/160] Fix lint issues --- .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 6 +++--- .../ui/orders/creation/UnifiedOrderEditViewModelTest.kt | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index d2a3fa7ce03..9c9e86016a3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -53,6 +53,7 @@ import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_HAS_DIFF import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_HAS_FEES import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_HAS_SHIPPING_METHOD import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_ID +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_IS_GIFT_CARD_REMOVED import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_PARENT_ID import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_PRODUCT_ADDED_VIA import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_PRODUCT_COUNT @@ -63,6 +64,7 @@ import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_SOURCE import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_STATUS import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_TO import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_TYPE +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_USE_GIFT_CARD import com.woocommerce.android.analytics.AnalyticsTracker.Companion.OrderNoteType.CUSTOMER import com.woocommerce.android.analytics.AnalyticsTracker.Companion.PRODUCT_TYPES import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_CUSTOM_AMOUNT_TAX_STATUS_NONE @@ -147,6 +149,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -164,9 +167,6 @@ import java.math.BigDecimal import java.util.Date import javax.inject.Inject import com.woocommerce.android.model.Product as ModelProduct -import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_IS_GIFT_CARD_REMOVED -import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_USE_GIFT_CARD -import kotlinx.coroutines.flow.firstOrNull @HiltViewModel @Suppress("LargeClass") diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt index 5b805087725..6502f134f62 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/UnifiedOrderEditViewModelTest.kt @@ -463,7 +463,6 @@ abstract class UnifiedOrderEditViewModelTest : BaseUnitTest() { ) } - @Test fun `given a standard order creation, when add gift card is clicked, then track expected event`() = testBlocking { initMocksForAnalyticsWithOrder(defaultOrderValue) From 3edc6347d828f43e41e6969212b3ada468f1ea69 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 18 Jan 2024 12:38:45 +0100 Subject: [PATCH 010/160] Expandabilit as VM state --- .../creation/OrderCreateEditViewModel.kt | 9 +++++--- .../totals/OrderCreateEditTotalsFullView.kt | 23 +++++-------------- .../totals/OrderCreateEditTotalsHelper.kt | 6 +++-- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index ce80ae44628..8bc3da20703 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -269,7 +269,7 @@ class OrderCreateEditViewModel @Inject constructor( onGiftClicked = { onEditGiftCardButtonClicked(selectedGiftCard) }, onTaxesLearnMore = { onTaxHelpButtonClicked() }, onMainButtonClicked = { onTotalsSectionPrimaryButtonClicked() }, - onExpandCollapseClicked = { onExpandCollapseTotalsClicked(it) } + onExpandCollapseClicked = { onExpandCollapseTotalsClicked() } ) } @@ -1139,12 +1139,14 @@ class OrderCreateEditViewModel @Inject constructor( } } - private fun onExpandCollapseTotalsClicked(expanded: Boolean) { + private fun onExpandCollapseTotalsClicked() { + val newTotalsExpandedState = !viewState.isTotalsExpanded + viewState = viewState.copy(isTotalsExpanded = newTotalsExpandedState) tracker.track( AnalyticsEvent.ORDER_FORM_TOTALS_PANEL_TOGGLED, mapOf( KEY_FLOW to flow, - KEY_EXPANDED to expanded + KEY_EXPANDED to newTotalsExpandedState ) ) } @@ -1718,6 +1720,7 @@ class OrderCreateEditViewModel @Inject constructor( val isAddGiftCardButtonEnabled: Boolean = false, val shouldDisplayAddGiftCardButton: Boolean = false, val isEditable: Boolean = true, + val isTotalsExpanded: Boolean = false, val multipleLinesContext: MultipleLinesContext = MultipleLinesContext.None, val taxBasedOnSettingLabel: String = "", val autoTaxRateSetting: AutoTaxRateSettingState = AutoTaxRateSettingState(), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt index f18fa193d6b..2ced92184e1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt @@ -35,17 +35,12 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -148,12 +143,7 @@ private fun PanelWithShadow(content: @Composable ColumnScope.() -> Unit) { } @Composable -private fun TotalsView( - state: TotalsSectionsState.Full, - isPreview: Boolean = LocalInspectionMode.current, -) { - var isExpanded by rememberSaveable { mutableStateOf(isPreview) } - +private fun TotalsView(state: TotalsSectionsState.Full) { val totalsIs = remember { MutableInteractionSource() } Column( @@ -169,8 +159,7 @@ private fun TotalsView( interactionSource = totalsIs, indication = null ) { - isExpanded = !isExpanded - state.onExpandCollapseClicked(isExpanded) + state.onExpandCollapseClicked() } .animateContentSize() ) { @@ -181,14 +170,13 @@ private fun TotalsView( contentAlignment = Alignment.Center ) { Crossfade( - targetState = isExpanded, + targetState = state.isExpanded, label = "totals_icon", ) { expanded -> IconButton( interactionSource = totalsIs, onClick = { - isExpanded = !isExpanded - state.onExpandCollapseClicked(isExpanded) + state.onExpandCollapseClicked() }, ) { Icon( @@ -204,7 +192,7 @@ private fun TotalsView( } } - if (isExpanded) { + if (state.isExpanded) { Lines(lines = state.lines, smallGaps = false) Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.major_100))) @@ -529,6 +517,7 @@ private fun OrderCreateEditTotalsFullViewPreview() { enabled = true, onClick = {}, ), + isExpanded = true, onExpandCollapseClicked = {}, ) ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt index 52dea239673..4328c7b71f1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt @@ -25,7 +25,7 @@ class OrderCreateEditTotalsHelper @Inject constructor( onGiftClicked: () -> Unit, onTaxesLearnMore: () -> Unit, onMainButtonClicked: () -> Unit, - onExpandCollapseClicked: (Boolean) -> Unit + onExpandCollapseClicked: () -> Unit ): TotalsSectionsState { val bigDecimalFormatter = currencyFormatter.buildBigDecimalFormatter( currencyCode = order.currency @@ -68,6 +68,7 @@ class OrderCreateEditTotalsHelper @Inject constructor( enabled = viewState.canCreateOrder, onClick = onMainButtonClicked, ), + isExpanded = viewState.isTotalsExpanded, onExpandCollapseClicked = onExpandCollapseClicked ) } @@ -212,7 +213,8 @@ sealed class TotalsSectionsState { val lines: List, val orderTotal: OrderTotal, val mainButton: Button, - val onExpandCollapseClicked: (Boolean) -> Unit, + val isExpanded: Boolean, + val onExpandCollapseClicked: () -> Unit, ) : TotalsSectionsState() data class Minimised( From df1979d6bd92d52a25637d71c5f03c115051e908 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 18 Jan 2024 13:38:01 +0100 Subject: [PATCH 011/160] Collapse the totals when scrolled to the bottom --- .../ui/orders/creation/OrderCreateEditFormFragment.kt | 7 +++++++ .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt index 7f3c8b426d7..e375ea0f33f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt @@ -190,6 +190,13 @@ class OrderCreateEditFormFragment : initProductsSection() initAdditionalInfoCollectionSection() initTaxRateSelectorSection() + initTotalsSection() + } + + private fun FragmentOrderCreateEditFormBinding.initTotalsSection() { + scrollView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> + viewModel.onScreenScrolledVertically(scrollY, oldScrollY) + } } private fun FragmentOrderCreateEditFormBinding.initTaxRateSelectorSection() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index 8bc3da20703..9a418da779f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -1687,6 +1687,12 @@ class OrderCreateEditViewModel @Inject constructor( } } + fun onScreenScrolledVertically(scrollY: Int, oldScrollY: Int) { + if (scrollY > oldScrollY && viewState.isTotalsExpanded) { + viewState = viewState.copy(isTotalsExpanded = false) + } + } + fun orderContainsProductsOrCustomAmounts() = orderDraft.value?.hasProducts() == true || orderDraft.value?.hasCustomAmounts() == true From d2dee089a24b09d4a316ae3abad237b8445cca85 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 18 Jan 2024 14:52:52 +0100 Subject: [PATCH 012/160] Set content once. React on the state change --- .../creation/OrderCreateEditFormFragment.kt | 16 ++++++---------- .../totals/OrderCreateEditTotalsFullView.kt | 11 ++++++++++- .../layout/fragment_order_create_edit_form.xml | 4 +--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt index e375ea0f33f..0aab10d78db 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt @@ -197,6 +197,12 @@ class OrderCreateEditFormFragment : scrollView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> viewModel.onScreenScrolledVertically(scrollY, oldScrollY) } + totalsSection.setContent { + OrderCreateEditTotalsView(viewModel) + } + totalsSection.post { + scrollView.setPadding(0, 0, 0, totalsSection.height) + } } private fun FragmentOrderCreateEditFormBinding.initTaxRateSelectorSection() { @@ -339,16 +345,6 @@ class OrderCreateEditFormFragment : bindCustomAmountsSection(binding.customAmountsSection, it) } - viewModel.totalsData.observe(viewLifecycleOwner) { - binding.totalsSection.show() - binding.totalsSection.setContent { - OrderCreateEditTotalsView(state = it) - } - binding.scrollView.post { - binding.scrollView.setPadding(0, 0, 0, binding.totalsSection.height) - } - } - observeViewStateChanges(binding) viewModel.event.observe(viewLifecycleOwner, ::handleViewModelEvents) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt index 2ced92184e1..997d0af6533 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt @@ -35,6 +35,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,9 +56,17 @@ import androidx.compose.ui.unit.sp import com.woocommerce.android.R import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.theme.WooTheme +import com.woocommerce.android.ui.orders.creation.OrderCreateEditViewModel @Composable -fun OrderCreateEditTotalsView(state: TotalsSectionsState) { +fun OrderCreateEditTotalsView(viewModel: OrderCreateEditViewModel) { + viewModel.totalsData.observeAsState().value?.let { + OrderCreateEditTotalsView(it) + } +} + +@Composable +private fun OrderCreateEditTotalsView(state: TotalsSectionsState) { AnimatedVisibility( visible = state is TotalsSectionsState.Full, enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), diff --git a/WooCommerce/src/main/res/layout/fragment_order_create_edit_form.xml b/WooCommerce/src/main/res/layout/fragment_order_create_edit_form.xml index 5975516365e..aed03ba9314 100644 --- a/WooCommerce/src/main/res/layout/fragment_order_create_edit_form.xml +++ b/WooCommerce/src/main/res/layout/fragment_order_create_edit_form.xml @@ -114,9 +114,7 @@ android:id="@+id/totals_section" tools:composableName="com.woocommerce.android.ui.orders.creation.totals.OrderCreateEditTotalsFullViewKt" android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:visibility="visible" - android:visibility="gone" /> + android:layout_height="wrap_content"/> From ae49f94fd4614866a88c633c7b0b1310f68959a0 Mon Sep 17 00:00:00 2001 From: Alejo Date: Thu, 18 Jan 2024 10:22:32 -0600 Subject: [PATCH 013/160] include range id in the stats model --- .../com/woocommerce/android/ui/mystore/MyStoreUiModels.kt | 3 ++- .../com/woocommerce/android/ui/mystore/MyStoreViewModel.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreUiModels.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreUiModels.kt index 7870c703a6c..09ba96a4925 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreUiModels.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreUiModels.kt @@ -9,7 +9,8 @@ data class RevenueStatsUiModel( val intervalList: List = emptyList(), val totalOrdersCount: Int? = null, val totalSales: Double? = null, - val currencyCode: String? + val currencyCode: String?, + val rangeId: String ) data class StatsIntervalUiModel( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt index 5c1c3c5bbd5..8b54984b3da 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreViewModel.kt @@ -371,7 +371,8 @@ class MyStoreViewModel @Inject constructor( intervalList = getIntervalList().toStatsIntervalUiModelList(), totalOrdersCount = totals?.ordersCount, totalSales = totals?.totalSales, - currencyCode = wooCommerceStore.getSiteSettings(selectedSite.get())?.currencyCode + currencyCode = wooCommerceStore.getSiteSettings(selectedSite.get())?.currencyCode, + rangeId = rangeId ) } From b8252978691d751e3293dfecec12591bb2a73f31 Mon Sep 17 00:00:00 2001 From: Alejo Date: Thu, 18 Jan 2024 10:23:32 -0600 Subject: [PATCH 014/160] add new event --- .../kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt | 1 + .../com/woocommerce/android/analytics/AnalyticsTracker.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt index 6b269a756c9..3f039955689 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt @@ -173,6 +173,7 @@ enum class AnalyticsEvent(val siteless: Boolean = false) { DASHBOARD_SEE_MORE_ANALYTICS_TAPPED, DASHBOARD_STORE_TIMEZONE_DIFFER_FROM_DEVICE, USED_ANALYTICS, + STATS_UNEXPECTED_FORMAT, // -- Analytics Hub ANALYTICS_HUB_DATE_RANGE_BUTTON_TAPPED, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt index 167992dd50a..cdb0458c1de 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt @@ -194,6 +194,8 @@ class AnalyticsTracker private constructor( const val KEY_SOFTWARE_UPDATE_TYPE = "software_update_type" const val KEY_SUBJECT = "subject" const val KEY_DATE_RANGE = "date_range" + const val KEY_DATE = "date" + const val KEY_GRANULARITY = "granularity" const val KEY_SOURCE = "source" const val KEY_CUSTOM_FIELDS_COUNT = "custom_fields_count" const val KEY_CUSTOM_FIELDS_SIZE = "custom_fields_size" From be87c4c908cbab8b3167bdd6aa20482a7a8c46e6 Mon Sep 17 00:00:00 2001 From: Alejo Date: Thu, 18 Jan 2024 10:24:09 -0600 Subject: [PATCH 015/160] track unexpected format --- .../android/ui/mystore/MyStoreStatsView.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt index 3f9ed7e1fe4..1fc9f32e970 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt @@ -29,6 +29,9 @@ import com.google.android.material.card.MaterialCardView import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent import com.woocommerce.android.analytics.AnalyticsTracker +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_DATE +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_GRANULARITY +import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_RANGE import com.woocommerce.android.databinding.MyStoreStatsBinding import com.woocommerce.android.extensions.convertedFrom import com.woocommerce.android.tools.SelectedSite @@ -319,7 +322,7 @@ class MyStoreStatsView @JvmOverloads constructor( StatsGranularity.WEEKS -> dateUtils.getShortMonthDayString(dateString).orEmpty() StatsGranularity.MONTHS -> dateUtils.getMonthString(dateString).orEmpty() StatsGranularity.YEARS -> dateUtils.getYearString(dateString).orEmpty() - } + }.also { result -> trackUnexpectedFormat(result, dateString) } } override fun onValueSelected(entry: Entry?, h: Highlight?) { @@ -368,7 +371,7 @@ class MyStoreStatsView @JvmOverloads constructor( StatsGranularity.WEEKS -> dateUtils.getShortMonthDayString(dateString).orEmpty() StatsGranularity.MONTHS -> dateUtils.getLongMonthDayString(dateString).orEmpty() StatsGranularity.YEARS -> dateUtils.getFriendlyLongMonthYear(dateString).orEmpty() - } + }.also { result -> trackUnexpectedFormat(result, dateString) } } /** @@ -605,6 +608,19 @@ class MyStoreStatsView @JvmOverloads constructor( StatsGranularity.WEEKS -> dateUtils.getShortMonthDayString(dateString).orEmpty() StatsGranularity.MONTHS -> dateUtils.getShortMonthDayString(dateString).orEmpty() StatsGranularity.YEARS -> dateUtils.getShortMonthString(dateString).orEmpty() + }.also { result -> trackUnexpectedFormat(result, dateString) } + } + + private fun trackUnexpectedFormat(result: String, dateString: String){ + if (result.isEmpty()) { + AnalyticsTracker.track( + AnalyticsEvent.STATS_UNEXPECTED_FORMAT, + mapOf( + KEY_DATE to dateString, + KEY_GRANULARITY to activeGranularity.name, + KEY_RANGE to revenueStatsModel?.rangeId + ) + ) } } @@ -637,7 +653,7 @@ class MyStoreStatsView @JvmOverloads constructor( StatsGranularity.WEEKS -> getWeekLabelValue(dateString) StatsGranularity.MONTHS -> dateUtils.getDayString(dateString).orEmpty() StatsGranularity.YEARS -> dateUtils.getShortMonthString(dateString).orEmpty() - } + }.also { result -> trackUnexpectedFormat(result, dateString) } } /** From 6ea7b6a59bd219927f10dc4ee895215305346d72 Mon Sep 17 00:00:00 2001 From: Alejo Date: Thu, 18 Jan 2024 16:04:11 -0600 Subject: [PATCH 016/160] fix detekt warning --- .../com/woocommerce/android/ui/mystore/MyStoreStatsView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt index 1fc9f32e970..534f35708bc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreStatsView.kt @@ -611,14 +611,14 @@ class MyStoreStatsView @JvmOverloads constructor( }.also { result -> trackUnexpectedFormat(result, dateString) } } - private fun trackUnexpectedFormat(result: String, dateString: String){ + private fun trackUnexpectedFormat(result: String, dateString: String) { if (result.isEmpty()) { AnalyticsTracker.track( AnalyticsEvent.STATS_UNEXPECTED_FORMAT, mapOf( KEY_DATE to dateString, KEY_GRANULARITY to activeGranularity.name, - KEY_RANGE to revenueStatsModel?.rangeId + KEY_RANGE to revenueStatsModel?.rangeId ) ) } From 289af61a82f821417ebb9003c91d8e544bf19530 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2024 11:33:21 +0100 Subject: [PATCH 017/160] Set bottom padding only when there no layout change events for some time to avoid "lagging" --- .../creation/OrderCreateEditFormFragment.kt | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt index 0aab10d78db..8b422e01259 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt @@ -2,6 +2,8 @@ package com.woocommerce.android.ui.orders.creation import android.annotation.SuppressLint import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.Menu import android.view.MenuInflater import android.view.MenuItem @@ -90,7 +92,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.util.ToastUtils import javax.inject.Inject -@Suppress("LargeClass") +@Suppress("LargeClass", "UNUSED_ANONYMOUS_PARAMETER") @AndroidEntryPoint class OrderCreateEditFormFragment : BaseFragment(R.layout.fragment_order_create_edit_form), @@ -107,6 +109,9 @@ class OrderCreateEditFormFragment : private var createOrderMenuItem: MenuItem? = null private var progressDialog: CustomProgressDialog? = null private var orderUpdateFailureSnackBar: Snackbar? = null + private var totalsSectionLayoutChangeListener: View.OnLayoutChangeListener? = null + + private var binding: FragmentOrderCreateEditFormBinding? = null private val args: OrderCreateEditFormFragmentArgs by navArgs() @@ -124,7 +129,8 @@ class OrderCreateEditFormFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().addMenuProvider(this, viewLifecycleOwner) - with(FragmentOrderCreateEditFormBinding.bind(view)) { + binding = FragmentOrderCreateEditFormBinding.bind(view) + with(binding!!) { setupObserversWith(this) setupHandleResults() initView() @@ -184,6 +190,13 @@ class OrderCreateEditFormFragment : orderUpdateFailureSnackBar?.dismiss() } + override fun onDestroyView() { + super.onDestroyView() + totalsSectionLayoutChangeListener?.let { + binding?.totalsSection?.removeOnLayoutChangeListener(it) + } + } + private fun FragmentOrderCreateEditFormBinding.initView() { initOrderStatusView() initCustomerAndNotesEmptySection() @@ -197,12 +210,23 @@ class OrderCreateEditFormFragment : scrollView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> viewModel.onScreenScrolledVertically(scrollY, oldScrollY) } - totalsSection.setContent { - OrderCreateEditTotalsView(viewModel) - } - totalsSection.post { - scrollView.setPadding(0, 0, 0, totalsSection.height) + totalsSection.setContent { OrderCreateEditTotalsView(viewModel) } + val handler = Handler(Looper.getMainLooper()) + totalsSectionLayoutChangeListener = View.OnLayoutChangeListener { _, _, p2, p3, p4, p5, p6, p7, p8 -> + handler.removeCallbacksAndMessages(null) + handler.postDelayed( + { + scrollView.setPadding( + 0, + 0, + 0, + totalsSection.height + ) + }, 300 + ) + } + totalsSection.addOnLayoutChangeListener(totalsSectionLayoutChangeListener) } private fun FragmentOrderCreateEditFormBinding.initTaxRateSelectorSection() { From d427e4e3fea4f7a1d95e20b0f8fff96731204303 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2024 11:42:03 +0100 Subject: [PATCH 018/160] Fixed detekt complains --- .../orders/creation/OrderCreateEditFormFragment.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt index 8b422e01259..cb55a7c2d32 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt @@ -92,7 +92,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.util.ToastUtils import javax.inject.Inject -@Suppress("LargeClass", "UNUSED_ANONYMOUS_PARAMETER") +@Suppress("LargeClass") @AndroidEntryPoint class OrderCreateEditFormFragment : BaseFragment(R.layout.fragment_order_create_edit_form), @@ -211,8 +211,9 @@ class OrderCreateEditFormFragment : viewModel.onScreenScrolledVertically(scrollY, oldScrollY) } totalsSection.setContent { OrderCreateEditTotalsView(viewModel) } + val handler = Handler(Looper.getMainLooper()) - totalsSectionLayoutChangeListener = View.OnLayoutChangeListener { _, _, p2, p3, p4, p5, p6, p7, p8 -> + totalsSectionLayoutChangeListener = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> handler.removeCallbacksAndMessages(null) handler.postDelayed( { @@ -222,9 +223,8 @@ class OrderCreateEditFormFragment : 0, totalsSection.height ) - }, 300 + }, TOTALS_PADDING_BOUNCE_TIME ) - } totalsSection.addOnLayoutChangeListener(totalsSectionLayoutChangeListener) } @@ -1102,4 +1102,8 @@ class OrderCreateEditFormFragment : isLocked = true } } + + private companion object { + private const val TOTALS_PADDING_BOUNCE_TIME = 300L + } } From 7bfc9931207f367a88849134be26271be21d4c91 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2024 13:31:19 +0100 Subject: [PATCH 019/160] Another way of pushing height of the drawer to the scroll view --- .../creation/OrderCreateEditFormFragment.kt | 44 ++++-------------- .../creation/OrderCreateEditViewModel.kt | 11 ++++- .../totals/OrderCreateEditTotalsFullView.kt | 45 ++++++++++++++----- .../totals/OrderCreateEditTotalsHelper.kt | 17 ++++--- 4 files changed, 62 insertions(+), 55 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt index cb55a7c2d32..5b320593734 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt @@ -2,8 +2,6 @@ package com.woocommerce.android.ui.orders.creation import android.annotation.SuppressLint import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.view.Menu import android.view.MenuInflater import android.view.MenuItem @@ -109,9 +107,6 @@ class OrderCreateEditFormFragment : private var createOrderMenuItem: MenuItem? = null private var progressDialog: CustomProgressDialog? = null private var orderUpdateFailureSnackBar: Snackbar? = null - private var totalsSectionLayoutChangeListener: View.OnLayoutChangeListener? = null - - private var binding: FragmentOrderCreateEditFormBinding? = null private val args: OrderCreateEditFormFragmentArgs by navArgs() @@ -129,8 +124,7 @@ class OrderCreateEditFormFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().addMenuProvider(this, viewLifecycleOwner) - binding = FragmentOrderCreateEditFormBinding.bind(view) - with(binding!!) { + with(FragmentOrderCreateEditFormBinding.bind(view)) { setupObserversWith(this) setupHandleResults() initView() @@ -190,13 +184,6 @@ class OrderCreateEditFormFragment : orderUpdateFailureSnackBar?.dismiss() } - override fun onDestroyView() { - super.onDestroyView() - totalsSectionLayoutChangeListener?.let { - binding?.totalsSection?.removeOnLayoutChangeListener(it) - } - } - private fun FragmentOrderCreateEditFormBinding.initView() { initOrderStatusView() initCustomerAndNotesEmptySection() @@ -211,22 +198,6 @@ class OrderCreateEditFormFragment : viewModel.onScreenScrolledVertically(scrollY, oldScrollY) } totalsSection.setContent { OrderCreateEditTotalsView(viewModel) } - - val handler = Handler(Looper.getMainLooper()) - totalsSectionLayoutChangeListener = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - handler.removeCallbacksAndMessages(null) - handler.postDelayed( - { - scrollView.setPadding( - 0, - 0, - 0, - totalsSection.height - ) - }, TOTALS_PADDING_BOUNCE_TIME - ) - } - totalsSection.addOnLayoutChangeListener(totalsSectionLayoutChangeListener) } private fun FragmentOrderCreateEditFormBinding.initTaxRateSelectorSection() { @@ -371,7 +342,7 @@ class OrderCreateEditFormFragment : observeViewStateChanges(binding) - viewModel.event.observe(viewLifecycleOwner, ::handleViewModelEvents) + viewModel.event.observe(viewLifecycleOwner, { handleViewModelEvents(it, binding) }) } @Suppress("LongMethod") @@ -966,7 +937,7 @@ class OrderCreateEditFormFragment : } } - private fun handleViewModelEvents(event: Event) { + private fun handleViewModelEvents(event: Event, binding: FragmentOrderCreateEditFormBinding) { when (event) { is OrderCreateEditNavigationTarget -> OrderCreateEditNavigator.navigate(this, event) is ViewOrderStatusSelector -> @@ -1017,6 +988,11 @@ class OrderCreateEditFormFragment : ) } + is OnTotalsSectionHeightChanged -> { + println("Totals section height changed: ${event.newHeight}") + binding.scrollView.setPadding(0, 0, 0, event.newHeight) + } + is Exit -> findNavController().navigateUp() } } @@ -1102,8 +1078,4 @@ class OrderCreateEditFormFragment : isLocked = true } } - - private companion object { - private const val TOTALS_PADDING_BOUNCE_TIME = 300L - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index 9a418da779f..98137f3476b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -269,7 +269,8 @@ class OrderCreateEditViewModel @Inject constructor( onGiftClicked = { onEditGiftCardButtonClicked(selectedGiftCard) }, onTaxesLearnMore = { onTaxHelpButtonClicked() }, onMainButtonClicked = { onTotalsSectionPrimaryButtonClicked() }, - onExpandCollapseClicked = { onExpandCollapseTotalsClicked() } + onExpandCollapseClicked = { onExpandCollapseTotalsClicked() }, + onHeightChanged = { onTotalsSectionHeightChanged(it) } ) } @@ -1151,6 +1152,10 @@ class OrderCreateEditViewModel @Inject constructor( ) } + private fun onTotalsSectionHeightChanged(newHeight: Int) { + triggerEvent(OnTotalsSectionHeightChanged(newHeight)) + } + private fun onTotalsSectionPrimaryButtonClicked() { when (mode) { Mode.Creation -> { @@ -1801,6 +1806,10 @@ object OnCouponRejectedByBackend : Event() { val message: Int = string.order_sync_coupon_removed } +data class OnTotalsSectionHeightChanged( + val newHeight: Int +) : Event() + data class OnCustomAmountTypeSelected( val type: CustomAmountType ) : Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt index 997d0af6533..5fa5443ee7b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsFullView.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource @@ -60,20 +61,28 @@ import com.woocommerce.android.ui.orders.creation.OrderCreateEditViewModel @Composable fun OrderCreateEditTotalsView(viewModel: OrderCreateEditViewModel) { - viewModel.totalsData.observeAsState().value?.let { - OrderCreateEditTotalsView(it) + viewModel.totalsData.observeAsState().value?.let { state -> + OrderCreateEditTotalsView( + state, + modifier = Modifier.onGloballyPositioned { + state.onHeightChanged(it.size.height) + } + ) } } @Composable -private fun OrderCreateEditTotalsView(state: TotalsSectionsState) { +private fun OrderCreateEditTotalsView( + state: TotalsSectionsState, + modifier: Modifier = Modifier +) { AnimatedVisibility( visible = state is TotalsSectionsState.Full, enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut() ) { if (state is TotalsSectionsState.Full) { - OrderCreateEditTotalsFullView(state) + OrderCreateEditTotalsFullView(state, modifier) } } @@ -83,25 +92,29 @@ private fun OrderCreateEditTotalsView(state: TotalsSectionsState) { exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut() ) { if (state is TotalsSectionsState.Minimised) { - OrderCreateEditTotalsMinimisedView(state) + OrderCreateEditTotalsMinimisedView(state, modifier) } } } @Composable -private fun OrderCreateEditTotalsFullView(state: TotalsSectionsState.Full) { +private fun OrderCreateEditTotalsFullView( + state: TotalsSectionsState.Full, + modifier: Modifier = Modifier +) { PanelWithShadow { - TotalsView(state) + TotalsView(state, modifier) } } @Composable private fun OrderCreateEditTotalsMinimisedView( - state: TotalsSectionsState.Minimised + state: TotalsSectionsState.Minimised, + modifier: Modifier = Modifier ) { PanelWithShadow { Column( - modifier = Modifier + modifier = modifier .background(color = colorResource(id = R.color.color_surface)) ) { Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.major_100))) @@ -152,13 +165,19 @@ private fun PanelWithShadow(content: @Composable ColumnScope.() -> Unit) { } @Composable -private fun TotalsView(state: TotalsSectionsState.Full) { +private fun TotalsView( + state: TotalsSectionsState.Full, + modifier: Modifier = Modifier +) { val totalsIs = remember { MutableInteractionSource() } Column( - modifier = Modifier + modifier = modifier .background(color = colorResource(id = R.color.color_surface)) .verticalScroll(rememberScrollState()) + .onGloballyPositioned { + state.onHeightChanged(it.size.height) + } ) { Column( modifier = Modifier @@ -528,6 +547,7 @@ private fun OrderCreateEditTotalsFullViewPreview() { ), isExpanded = true, onExpandCollapseClicked = {}, + onHeightChanged = {}, ) ) } @@ -541,7 +561,8 @@ private fun OrderCreateEditTotalsMinimisedViewPreview() { orderTotal = TotalsSectionsState.OrderTotal( label = stringResource(R.string.order_creation_payment_order_total), value = "$143.75" - ) + ), + onHeightChanged = {}, ) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt index 4328c7b71f1..762755ef900 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelper.kt @@ -25,7 +25,8 @@ class OrderCreateEditTotalsHelper @Inject constructor( onGiftClicked: () -> Unit, onTaxesLearnMore: () -> Unit, onMainButtonClicked: () -> Unit, - onExpandCollapseClicked: () -> Unit + onExpandCollapseClicked: () -> Unit, + onHeightChanged: (height: Int) -> Unit ): TotalsSectionsState { val bigDecimalFormatter = currencyFormatter.buildBigDecimalFormatter( currencyCode = order.currency @@ -33,7 +34,8 @@ class OrderCreateEditTotalsHelper @Inject constructor( return if (order.items.isEmpty() && order.feesLines.isEmpty()) { TotalsSectionsState.Minimised( - orderTotal = order.toOrderTotals(bigDecimalFormatter) + orderTotal = order.toOrderTotals(bigDecimalFormatter), + onHeightChanged = onHeightChanged ) } else { TotalsSectionsState.Full( @@ -69,7 +71,8 @@ class OrderCreateEditTotalsHelper @Inject constructor( onClick = onMainButtonClicked, ), isExpanded = viewState.isTotalsExpanded, - onExpandCollapseClicked = onExpandCollapseClicked + onExpandCollapseClicked = onExpandCollapseClicked, + onHeightChanged = onHeightChanged, ) } } @@ -208,18 +211,20 @@ class OrderCreateEditTotalsHelper @Inject constructor( } } -sealed class TotalsSectionsState { +sealed class TotalsSectionsState(open val onHeightChanged: (height: Int) -> Unit) { data class Full( val lines: List, val orderTotal: OrderTotal, val mainButton: Button, val isExpanded: Boolean, val onExpandCollapseClicked: () -> Unit, - ) : TotalsSectionsState() + override val onHeightChanged: (height: Int) -> Unit, + ) : TotalsSectionsState(onHeightChanged) data class Minimised( val orderTotal: OrderTotal, - ) : TotalsSectionsState() + override val onHeightChanged: (height: Int) -> Unit + ) : TotalsSectionsState(onHeightChanged) data class Button( val text: String, From 472fc16c2ebec988576fdb3321ab5a8ec14cdbb8 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2024 13:38:45 +0100 Subject: [PATCH 020/160] Made test compilable --- ...tionFocusedOrderCreateEditViewModelTest.kt | 71 +++++++++++-------- .../totals/OrderCreateEditTotalsHelperTest.kt | 7 +- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt index c766664cb24..96a8b43eb1b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt @@ -1183,11 +1183,11 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( AnalyticsEvent.ORDER_CREATE_BUTTON_TAPPED, mapOf( - AnalyticsTracker.KEY_STATUS to defaultOrderValue.status, - AnalyticsTracker.KEY_PRODUCT_COUNT to productCount, - AnalyticsTracker.KEY_HAS_CUSTOMER_DETAILS to defaultOrderValue.billingAddress.hasInfo(), - AnalyticsTracker.KEY_HAS_FEES to defaultOrderValue.feesLines.isNotEmpty(), - AnalyticsTracker.KEY_HAS_SHIPPING_METHOD to defaultOrderValue.shippingLines.isNotEmpty() + KEY_STATUS to defaultOrderValue.status, + KEY_PRODUCT_COUNT to productCount, + KEY_HAS_CUSTOMER_DETAILS to defaultOrderValue.billingAddress.hasInfo(), + KEY_HAS_FEES to defaultOrderValue.feesLines.isNotEmpty(), + KEY_HAS_SHIPPING_METHOD to defaultOrderValue.shippingLines.isNotEmpty() ) ) } @@ -1201,7 +1201,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( AnalyticsEvent.ORDER_COUPON_ADD, - mapOf(AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION) + mapOf(KEY_FLOW to VALUE_FLOW_CREATION) ) } @@ -1215,7 +1215,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( AnalyticsEvent.ORDER_COUPON_REMOVE, - mapOf(AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION) + mapOf(KEY_FLOW to VALUE_FLOW_CREATION) ) } @@ -1304,7 +1304,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given empty sku, when view model init, then do not fetch product information`() { testBlocking { val navArgs = OrderCreateEditFormFragmentArgs( - OrderCreateEditViewModel.Mode.Creation, "", null, + Creation, "", null, ).toSavedStateHandle() whenever(parameterRepository.getParameters("parameters_key", navArgs)).thenReturn( SiteParameters( @@ -1330,7 +1330,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given scanning initiated from the order list screen, when product search via sku succeeds, then track event with proper source`() { testBlocking { val navArgs = OrderCreateEditFormFragmentArgs( - OrderCreateEditViewModel.Mode.Creation, "12345", BarcodeFormat.FormatUPCA, + Creation, "12345", BarcodeFormat.FormatUPCA, ).toSavedStateHandle() whenever(parameterRepository.getParameters("parameters_key", navArgs)).thenReturn( SiteParameters( @@ -1372,7 +1372,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given scanning initiated from the order list screen, when product search via sku fails, then track event with proper source`() { testBlocking { val navArgs = OrderCreateEditFormFragmentArgs( - OrderCreateEditViewModel.Mode.Creation, "12345", BarcodeFormat.FormatUPCA, + Creation, "12345", BarcodeFormat.FormatUPCA, ).toSavedStateHandle() whenever(parameterRepository.getParameters("parameters_key", navArgs)).thenReturn( SiteParameters( @@ -1408,7 +1408,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given scanning initiated from the order list screen, when product search via sku succeeds but contains no product, then track event with proper source`() { testBlocking { val navArgs = OrderCreateEditFormFragmentArgs( - OrderCreateEditViewModel.Mode.Creation, "12345", BarcodeFormat.FormatQRCode, + Creation, "12345", BarcodeFormat.FormatQRCode, ).toSavedStateHandle() whenever(parameterRepository.getParameters("parameters_key", navArgs)).thenReturn( SiteParameters( @@ -1444,7 +1444,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given variable product from order list screen, when product added via scanning, then track correct source`() { testBlocking { val navArgs = OrderCreateEditFormFragmentArgs( - OrderCreateEditViewModel.Mode.Creation, "12345", BarcodeFormat.FormatUPCA, + Creation, "12345", BarcodeFormat.FormatUPCA, ).toSavedStateHandle() whenever(parameterRepository.getParameters("parameters_key", navArgs)).thenReturn( SiteParameters( @@ -1476,8 +1476,8 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( AnalyticsEvent.ORDER_PRODUCT_ADD, mapOf( - AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION, - AnalyticsTracker.KEY_PRODUCT_COUNT to 1, + KEY_FLOW to VALUE_FLOW_CREATION, + KEY_PRODUCT_COUNT to 1, AnalyticsTracker.KEY_SCANNING_SOURCE to ScanningSource.ORDER_LIST.source, KEY_PRODUCT_ADDED_VIA to ProductAddedVia.SCANNING.addedVia, KEY_HAS_BUNDLE_CONFIGURATION to false @@ -1490,7 +1490,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given non-variable product from order list screen, when product added via scanning, then track correct source`() { testBlocking { val navArgs = OrderCreateEditFormFragmentArgs( - OrderCreateEditViewModel.Mode.Creation, "12345", BarcodeFormat.FormatUPCA, + Creation, "12345", BarcodeFormat.FormatUPCA, ).toSavedStateHandle() whenever(parameterRepository.getParameters("parameters_key", navArgs)).thenReturn( SiteParameters( @@ -1520,8 +1520,8 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( AnalyticsEvent.ORDER_PRODUCT_ADD, mapOf( - AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION, - AnalyticsTracker.KEY_PRODUCT_COUNT to 1, + KEY_FLOW to VALUE_FLOW_CREATION, + KEY_PRODUCT_COUNT to 1, AnalyticsTracker.KEY_SCANNING_SOURCE to ScanningSource.ORDER_LIST.source, KEY_PRODUCT_ADDED_VIA to ProductAddedVia.SCANNING.addedVia, KEY_HAS_BUNDLE_CONFIGURATION to false @@ -1880,7 +1880,8 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes any(), any(), any(), - any() + any(), + any(), ) ).thenReturn(totalsSectionsState) @@ -1910,7 +1911,8 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes any(), any(), any(), - any() + any(), + any(), ) ).thenReturn(totalsSectionsState) var totalsData: TotalsSectionsState? = null @@ -1929,18 +1931,22 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given totals helper returns full, when expand clicked, then ORDER_FORM_TOTALS_PANEL_TOGGLED tracked with true`() { testBlocking { val totalsSectionsState = mock() - val onExpandCollapseClickedCaptor = argumentCaptor<(Boolean) -> Unit>() + val onExpandCollapseClickedCaptor = argumentCaptor<() -> Unit>() + val viewState = ViewState( + isTotalsExpanded = false + ) whenever( totalsHelper.mapToPaymentTotalsState( any(), any(), + viewState, any(), any(), any(), any(), any(), + onExpandCollapseClickedCaptor.capture(), any(), - onExpandCollapseClickedCaptor.capture() ) ).thenReturn(totalsSectionsState) @@ -1948,7 +1954,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes createSut() - onExpandCollapseClickedCaptor.firstValue.invoke(true) + onExpandCollapseClickedCaptor.firstValue.invoke() verify(tracker).track( AnalyticsEvent.ORDER_FORM_TOTALS_PANEL_TOGGLED, @@ -1964,18 +1970,22 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes fun `given totals helper returns full, when collapse clicked, then ORDER_FORM_TOTALS_PANEL_TOGGLED tracked with false`() { testBlocking { val totalsSectionsState = mock() - val onExpandCollapseClickedCaptor = argumentCaptor<(Boolean) -> Unit>() + val onExpandCollapseClickedCaptor = argumentCaptor<() -> Unit>() + val viewState = ViewState( + isTotalsExpanded = true + ) whenever( totalsHelper.mapToPaymentTotalsState( any(), any(), + viewState, any(), any(), any(), any(), any(), + onExpandCollapseClickedCaptor.capture(), any(), - onExpandCollapseClickedCaptor.capture() ) ).thenReturn(totalsSectionsState) @@ -1983,7 +1993,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes createSut() - onExpandCollapseClickedCaptor.firstValue.invoke(false) + onExpandCollapseClickedCaptor.firstValue.invoke() verify(tracker).track( AnalyticsEvent.ORDER_FORM_TOTALS_PANEL_TOGGLED, @@ -2010,7 +2020,8 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes any(), any(), onMainButtonClickedCaptor.capture(), - any() + any(), + any(), ) ).thenReturn(totalsSectionsState) @@ -2108,7 +2119,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker, never()).track( ORDER_FEE_ADD, mapOf( - AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION, + KEY_FLOW to VALUE_FLOW_CREATION, KEY_CUSTOM_AMOUNT_TAX_STATUS to "none" ) ) @@ -2144,7 +2155,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( ORDER_FEE_UPDATE, mapOf( - AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION, + KEY_FLOW to VALUE_FLOW_CREATION, KEY_CUSTOM_AMOUNT_TAX_STATUS to "none" ) ) @@ -2196,7 +2207,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( ORDER_FEE_ADD, mapOf( - AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION, + KEY_FLOW to VALUE_FLOW_CREATION, KEY_CUSTOM_AMOUNT_TAX_STATUS to VALUE_CUSTOM_AMOUNT_TAX_STATUS_TAXABLE ) ) @@ -2217,7 +2228,7 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes verify(tracker).track( ORDER_FEE_ADD, mapOf( - AnalyticsTracker.KEY_FLOW to VALUE_FLOW_CREATION, + KEY_FLOW to VALUE_FLOW_CREATION, KEY_CUSTOM_AMOUNT_TAX_STATUS to VALUE_CUSTOM_AMOUNT_TAX_STATUS_NONE ) ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelperTest.kt index 9dac8e674fb..ce5fec0bf8e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/totals/OrderCreateEditTotalsHelperTest.kt @@ -123,7 +123,8 @@ class OrderCreateEditTotalsHelperTest { val onGiftClicked = mock<() -> Unit>() val onTaxesLearnMore = mock<() -> Unit>() val onMainButtonClicked = mock<() -> Unit>() - val onExpandCollapseClicked = mock<(Boolean) -> Unit>() + val onExpandCollapseClicked = mock<() -> Unit>() + val onHeightChanged = mock<(Int) -> Unit>() val taxBasedOnSettingLabel = "tax based on billing address" @@ -140,6 +141,7 @@ class OrderCreateEditTotalsHelperTest { onTaxesLearnMore, onMainButtonClicked, onExpandCollapseClicked, + onHeightChanged, ) // THEN @@ -214,6 +216,7 @@ class OrderCreateEditTotalsHelperTest { {}, {}, {}, + {}, ) // THEN @@ -246,6 +249,7 @@ class OrderCreateEditTotalsHelperTest { {}, {}, {}, + {}, ) // THEN @@ -280,6 +284,7 @@ class OrderCreateEditTotalsHelperTest { {}, {}, {}, + {}, ) // THEN From 6335060ad50d398f298777c387d620bc1635cc17 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2024 13:48:19 +0100 Subject: [PATCH 021/160] Fixed the test of the tracking of expand/collapse --- ...tionFocusedOrderCreateEditViewModelTest.kt | 44 +++---------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt index 96a8b43eb1b..3d9e2e137b3 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt @@ -1928,18 +1928,15 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes } @Test - fun `given totals helper returns full, when expand clicked, then ORDER_FORM_TOTALS_PANEL_TOGGLED tracked with true`() { + fun `given totals helper returns full, when expand collapse clicked, then ORDER_FORM_TOTALS_PANEL_TOGGLED tracked with false and true`() { testBlocking { val totalsSectionsState = mock() val onExpandCollapseClickedCaptor = argumentCaptor<() -> Unit>() - val viewState = ViewState( - isTotalsExpanded = false - ) whenever( totalsHelper.mapToPaymentTotalsState( any(), any(), - viewState, + any(), any(), any(), any(), @@ -1954,52 +1951,21 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes createSut() + onExpandCollapseClickedCaptor.firstValue.invoke() onExpandCollapseClickedCaptor.firstValue.invoke() verify(tracker).track( AnalyticsEvent.ORDER_FORM_TOTALS_PANEL_TOGGLED, mapOf( KEY_FLOW to VALUE_FLOW_CREATION, - KEY_EXPANDED to true + KEY_EXPANDED to false ) ) - } - } - - @Test - fun `given totals helper returns full, when collapse clicked, then ORDER_FORM_TOTALS_PANEL_TOGGLED tracked with false`() { - testBlocking { - val totalsSectionsState = mock() - val onExpandCollapseClickedCaptor = argumentCaptor<() -> Unit>() - val viewState = ViewState( - isTotalsExpanded = true - ) - whenever( - totalsHelper.mapToPaymentTotalsState( - any(), - any(), - viewState, - any(), - any(), - any(), - any(), - any(), - onExpandCollapseClickedCaptor.capture(), - any(), - ) - ).thenReturn(totalsSectionsState) - - sut.totalsData.observeForever { } - - createSut() - - onExpandCollapseClickedCaptor.firstValue.invoke() - verify(tracker).track( AnalyticsEvent.ORDER_FORM_TOTALS_PANEL_TOGGLED, mapOf( KEY_FLOW to VALUE_FLOW_CREATION, - KEY_EXPANDED to false + KEY_EXPANDED to true ) ) } From 74134c27d67264bd834687ac1a24cff69aa0c4bf Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2024 13:53:04 +0100 Subject: [PATCH 022/160] Added a simple test on the new height propogation --- ...tionFocusedOrderCreateEditViewModelTest.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt index 3d9e2e137b3..5f3c9db87f6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/creation/CreationFocusedOrderCreateEditViewModelTest.kt @@ -1971,6 +1971,40 @@ class CreationFocusedOrderCreateEditViewModelTest : UnifiedOrderEditViewModelTes } } + @Test + fun `given totals helper returns full, when height changed, then event OnTotalsSectionHeightChanged with height emitted`() { + testBlocking { + val totalsSectionsState = mock() + val onHeightChangedCaptor = argumentCaptor<(Int) -> Unit>() + whenever( + totalsHelper.mapToPaymentTotalsState( + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any(), + onHeightChangedCaptor.capture(), + ) + ).thenReturn(totalsSectionsState) + + var lastReceivedEvent: Event? = null + sut.event.observeForever { + lastReceivedEvent = it + } + + sut.totalsData.observeForever { } + + createSut() + + onHeightChangedCaptor.firstValue.invoke(100) + assertThat(lastReceivedEvent).isEqualTo(OnTotalsSectionHeightChanged(100)) + } + } + @Test fun `given totals helper returns full and creation, when main button clicked, then PAYMENTS_FLOW_ORDER_COLLECT_PAYMENT_TAPPED tracked`() { testBlocking { From 5b304e9bfa467946be46cbe4578d6be1bc19d24f Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2024 14:00:05 +0100 Subject: [PATCH 023/160] Updated release notes --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 4239b7feaea..28cee68b2f2 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,4 +1,8 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] +17.3 +----- +- [*] [Internal] Collapse bottom drawer with totals on the order creation/editing screen when scrolling down [https://github.com/woocommerce/woocommerce-android/pull/10573] + 17.2 ----- - [*] [Internal] Tracking of "country" and "currency" properties for entry and exit of the payments flows [https://github.com/woocommerce/woocommerce-android/pull/10528] From c8c5188485030498cfda9a0ea1cc40514bb476dc Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 23 Jan 2024 21:46:39 +0100 Subject: [PATCH 024/160] Show a character counter when there are < 10 characters left --- .../ad/BlazeCampaignCreationEditAdScreen.kt | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt index 7f614b41f55..74f760e3e17 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest.Builder @@ -169,13 +170,20 @@ private fun AdDataSection( .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - WCOutlinedTextField( - value = viewState.tagLine, - onValueChange = onTagLineChanged, - label = stringResource(id = string.blaze_campaign_edit_ad_change_tagline_title), - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) - ) + Box { + WCOutlinedTextField( + value = viewState.tagLine, + onValueChange = onTagLineChanged, + label = stringResource(id = string.blaze_campaign_edit_ad_change_tagline_title), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) + ) + + CornerCharacterWarning( + charsLeft = viewState.taglineCharactersRemaining, + modifier = Modifier.align(Alignment.TopEnd) + ) + } Text( text = stringResource( @@ -189,16 +197,24 @@ private fun AdDataSection( .fillMaxWidth() ) - WCOutlinedTextField( - value = viewState.description, - onValueChange = onDescriptionChanged, - label = stringResource(id = string.blaze_campaign_edit_ad_change_description_title), - maxLines = 3, - minLines = 3, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + Box( modifier = Modifier .padding(top = dimensionResource(id = dimen.major_150)) - ) + ) { + WCOutlinedTextField( + value = viewState.description, + onValueChange = onDescriptionChanged, + label = stringResource(id = string.blaze_campaign_edit_ad_change_description_title), + maxLines = 3, + minLines = 3, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) + ) + + CornerCharacterWarning( + charsLeft = viewState.descriptionCharactersRemaining, + modifier = Modifier.align(Alignment.TopEnd) + ) + } Text( text = stringResource( @@ -247,6 +263,23 @@ private fun AdDataSection( } } +@Composable +private fun CornerCharacterWarning(charsLeft: Int, modifier: Modifier = Modifier) { + if (charsLeft < 10) { + Text( + text = charsLeft.toString(), + style = MaterialTheme.typography.caption, + color = colorResource(id = color.color_error), + textAlign = TextAlign.End, + modifier = modifier + .padding( + top = dimensionResource(id = dimen.major_75), + end = dimensionResource(id = dimen.minor_75) + ) + ) + } +} + @Composable private fun AdImageSection(viewState: ViewState, onChangeImageTapped: () -> Unit) { Column( From 8a70ee4d2b9c5c617c506443c0a78eaed2f89683 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 23 Jan 2024 21:47:39 +0100 Subject: [PATCH 025/160] Show a character counter when there are < 10 characters left --- .../android/ui/blaze/BlazeRepository.kt | 6 +++++- .../BlazeCampaignCreationPreviewViewModel.kt | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 6 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 a600d2db662..1dc8bf6a0ac 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 @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.blaze import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.util.TimezoneProvider +import kotlinx.coroutines.delay import org.wordpress.android.fluxc.store.blaze.BlazeCampaignsStore import java.util.Date import javax.inject.Inject @@ -22,6 +23,9 @@ class BlazeRepository @Inject constructor( suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get()) + @Suppress("MagicNumber") + fun observeLanguages() = blazeCampaignsStore.observeBlazeTargetingLanguages() + fun getCampaignPreviewDetails(productId: Long): CampaignPreview { val product = productDetailRepository.getProduct(productId) return CampaignPreview( @@ -60,8 +64,8 @@ class BlazeRepository @Inject constructor( ) data class AiSuggestionForAd( - val title: String, val tagLine: String, + val description: String, ) data class Budget( 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 1ae11ddff99..5b734658b93 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 @@ -13,7 +13,6 @@ import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @@ -35,9 +34,17 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( init { launch { - @Suppress("MagicNumber") - delay(3000) - _viewState.value = _viewState.value?.copy(isLoading = false) + val suggestions = blazeRepository.getAdSuggestions(navArgs.productId) + _viewState.value?.let { + _viewState.value = it.copy( + adDetails = it.adDetails.copy( + tagLine = suggestions.firstOrNull()?.title ?: "", + description = suggestions.firstOrNull()?.description ?: "", + tagLine = suggestions.firstOrNull()?.tagLine ?: "", + ) + ) + } + _viewState.value = _viewState.value?.copy(adDetails = _, isLoading = false) } } @@ -50,7 +57,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( isLoading = isLoading, adDetails = AdDetailsUi( productId = productId, - description = aiSuggestions.firstOrNull()?.title ?: "", + description = aiSuggestions.firstOrNull()?.description ?: "", tagLine = aiSuggestions.firstOrNull()?.tagLine ?: "", campaignImageUrl = campaignImageUrl ?: "", ), From d84ce0e48ec82c4070be222f405839f17c2763aa Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 24 Jan 2024 14:55:05 +0100 Subject: [PATCH 026/160] Removed redundant logging --- .../android/ui/orders/creation/OrderCreateEditFormFragment.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt index 18b52730148..165fa84459c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditFormFragment.kt @@ -1022,7 +1022,6 @@ class OrderCreateEditFormFragment : } is OnTotalsSectionHeightChanged -> { - println("Totals section height changed: ${event.newHeight}") binding.scrollView.setPadding(0, 0, 0, event.newHeight) } From b37d59f402eb392f714696d4c029c72f3d9302a2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 24 Jan 2024 15:58:57 +0100 Subject: [PATCH 027/160] Barcode scanner auto focus --- .../android/ui/barcodescanner/BarcodeScanner.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt index 3eb482acec9..b8423dbccd6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt @@ -3,9 +3,11 @@ package com.woocommerce.android.ui.barcodescanner import android.content.res.Configuration import android.util.Size import androidx.camera.core.CameraSelector +import androidx.camera.core.FocusMeteringAction import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.camera.core.ImageProxy +import androidx.camera.core.SurfaceOrientedMeteringPointFactory import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.compose.foundation.background @@ -33,6 +35,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import com.woocommerce.android.R import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import java.util.concurrent.TimeUnit import androidx.camera.core.Preview as CameraPreview @Suppress("TooGenericExceptionCaught") @@ -75,7 +78,15 @@ fun BarcodeScanner( } cameraProvider.unbindAll() - cameraProvider.bindToLifecycle(lifecycleOwner, selector, cameraPreview, imageAnalysisUseCase) + val camera = cameraProvider.bindToLifecycle(lifecycleOwner, selector, cameraPreview, imageAnalysisUseCase) + + val factory = SurfaceOrientedMeteringPointFactory(previewView.width.toFloat(), previewView.height.toFloat()) + val centerPoint = factory.createPoint(previewView.width.toFloat() / 2, previewView.height.toFloat() / 2) + val action = FocusMeteringAction.Builder(centerPoint, FocusMeteringAction.FLAG_AF).apply { + // Confusing naming - that means focus and metering will reset after 2 seconds + setAutoCancelDuration(2, TimeUnit.SECONDS) + }.build() + camera.cameraControl.startFocusAndMetering(action) } catch (e: Exception) { onBindingException(e) } From acbd77e7c338822b04ec32c87dd721d2d1a43e5f Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 24 Jan 2024 16:06:48 +0100 Subject: [PATCH 028/160] Fixed formatting --- .../android/ui/barcodescanner/BarcodeScanner.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt index b8423dbccd6..51f85ccbba9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/barcodescanner/BarcodeScanner.kt @@ -78,10 +78,21 @@ fun BarcodeScanner( } cameraProvider.unbindAll() - val camera = cameraProvider.bindToLifecycle(lifecycleOwner, selector, cameraPreview, imageAnalysisUseCase) + val camera = cameraProvider.bindToLifecycle( + lifecycleOwner, + selector, + cameraPreview, + imageAnalysisUseCase + ) - val factory = SurfaceOrientedMeteringPointFactory(previewView.width.toFloat(), previewView.height.toFloat()) - val centerPoint = factory.createPoint(previewView.width.toFloat() / 2, previewView.height.toFloat() / 2) + val factory = SurfaceOrientedMeteringPointFactory( + previewView.width.toFloat(), + previewView.height.toFloat() + ) + val centerPoint = factory.createPoint( + previewView.width.toFloat() / 2, + previewView.height.toFloat() / 2 + ) val action = FocusMeteringAction.Builder(centerPoint, FocusMeteringAction.FLAG_AF).apply { // Confusing naming - that means focus and metering will reset after 2 seconds setAutoCancelDuration(2, TimeUnit.SECONDS) From 129f40c9b94ced729fff4074e056eaa05b16f883 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 24 Jan 2024 16:20:37 +0100 Subject: [PATCH 029/160] Updated release notes --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 463fb94b49d..6590fbebcce 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,4 +1,8 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] +17.4 +----- +- [*] Improvement to the autofocus logic in the barcode scanning screen [https://github.com/woocommerce/woocommerce-android/pull/10605] + 17.1 ----- From 46f4a48e3a63d753a0a660350d6c3e2d8a2eeb8a Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Wed, 24 Jan 2024 23:45:34 +0100 Subject: [PATCH 030/160] Fetch and load the first ad suggestion when creating a Blaze campaign --- .../android/ui/blaze/BlazeRepository.kt | 16 +++++++++++++--- .../BlazeCampaignCreationPreviewViewModel.kt | 14 ++++++-------- 2 files changed, 19 insertions(+), 11 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 e80a4ef863c..6a80df6d92f 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 @@ -3,7 +3,7 @@ package com.woocommerce.android.ui.blaze import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.util.TimezoneProvider -import kotlinx.coroutines.delay +import org.wordpress.android.fluxc.persistence.blaze.BlazeCampaignsDao.BlazeAdSuggestionEntity import org.wordpress.android.fluxc.store.blaze.BlazeCampaignsStore import java.util.Date import javax.inject.Inject @@ -26,8 +26,18 @@ class BlazeRepository @Inject constructor( suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get()) - @Suppress("MagicNumber") - fun observeLanguages() = blazeCampaignsStore.observeBlazeTargetingLanguages() + suspend fun getAdSuggestions(productId: Long): List? { + fun List.mapToUiModel(): List { + return map { AiSuggestionForAd(it.tagLine, it.description) } + } + + val suggestions = blazeCampaignsStore.getBlazeAdSuggestions(selectedSite.get(), productId) + return if (suggestions.isNotEmpty()) { + suggestions.mapToUiModel() + } else { + blazeCampaignsStore.fetchBlazeAdSuggestions(selectedSite.get(), productId).model?.mapToUiModel() + } + } fun getCampaignPreviewDetails(productId: Long): CampaignPreview { val product = productDetailRepository.getProduct(productId) 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 ef3622b5219..d5634507e84 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 @@ -33,17 +33,15 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( init { launch { - val suggestions = blazeRepository.getAdSuggestions(navArgs.productId) - _viewState.value?.let { - _viewState.value = it.copy( - adDetails = it.adDetails.copy( - tagLine = suggestions.firstOrNull()?.title ?: "", - description = suggestions.firstOrNull()?.description ?: "", - tagLine = suggestions.firstOrNull()?.tagLine ?: "", + blazeRepository.getAdSuggestions(navArgs.productId)?.let { adSuggestions -> + _viewState.value = _viewState.value?.copy( + isLoading = false, + adDetails = _viewState.value?.adDetails!!.copy( + description = adSuggestions.firstOrNull()?.description ?: "", + tagLine = adSuggestions.firstOrNull()?.tagLine ?: "", ) ) } - _viewState.value = _viewState.value?.copy(adDetails = _, isLoading = false) } } From 666b65750e743d4744536d0917fa8bfd7478a112 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 23 Jan 2024 20:01:50 +0100 Subject: [PATCH 031/160] Implement Parcelable by WooPlugin class --- .../kotlin/com/woocommerce/android/model/WooPlugin.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/WooPlugin.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/WooPlugin.kt index 827bda12c35..91251c554f3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/WooPlugin.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/WooPlugin.kt @@ -1,9 +1,15 @@ package com.woocommerce.android.model +import android.os.Parcelable +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize + +@Parcelize data class WooPlugin( val isInstalled: Boolean, val isActive: Boolean, val version: String? -) { +) : Parcelable { + @IgnoredOnParcel val isOperational = isInstalled && isActive } From bd40ef18ce79ec479b576bee6d4c8549278dfb93 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 23 Jan 2024 20:03:05 +0100 Subject: [PATCH 032/160] Make saving state of pluginsInformation safer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just in case — not to relate on HashMap's class name as the key --- .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index ce80ae44628..ae67867e6ae 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -216,7 +216,8 @@ class OrderCreateEditViewModel @Inject constructor( private val pluginsInformation: MutableStateFlow> = savedState.getStateFlow( scope = viewModelScope, - initialValue = HashMap() + initialValue = HashMap(), + key = "plugins_information" ) val isGiftCardExtensionEnabled get() = pluginsInformation.value[WOO_GIFT_CARDS.pluginName] From bbf53282d0b34128f99cb46e5dc57c5beb21ca47 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 24 Jan 2024 09:10:11 +0100 Subject: [PATCH 033/160] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 463fb94b49d..74134cbe787 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] 17.1 ----- +- [*] [Internal] Fixed crash when going to background from the order creation screen [https://github.com/woocommerce/woocommerce-android/pull/10600] 17.0 ----- From 390b4d19efc6c917902a3e3f978da90345ce63aa Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 25 Jan 2024 12:07:11 +0100 Subject: [PATCH 034/160] Add product ID navigation parameter --- .../creation/preview/BlazeCampaignCreationPreviewFragment.kt | 1 + .../creation/preview/BlazeCampaignCreationPreviewViewModel.kt | 2 ++ .../main/res/navigation/nav_graph_blaze_campaign_creation.xml | 3 +++ 3 files changed, 6 insertions(+) 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 8eaed86c00a..ea833c55a1c 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 @@ -48,6 +48,7 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { is NavigateToEditAdScreen -> findNavController().navigate( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignCreationEditAdFragment( + event.productId, event.tagLine, event.description, event.campaignImageUrl 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 d5634507e84..a1b5f90b6e6 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 @@ -119,6 +119,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( viewState.value?.let { campaignPreviewContent -> triggerEvent( NavigateToEditAdScreen( + productId = navArgs.productId, tagLine = campaignPreviewContent.adDetails.tagLine, description = campaignPreviewContent.adDetails.description, campaignImageUrl = campaignPreviewContent.adDetails.campaignImageUrl @@ -139,6 +140,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } data class NavigateToEditAdScreen( + val productId: Long, val tagLine: String, val description: String, val campaignImageUrl: String? 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 e1e2c94186c..2993f47b054 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 @@ -61,6 +61,9 @@ android:id="@+id/blazeCampaignCreationEditAdFragment" android:name="com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdFragment" android:label="BlazeCampaignCreationEditAdFragment" > + Date: Thu, 25 Jan 2024 12:07:31 +0100 Subject: [PATCH 035/160] Remove the redundant preview strings --- .../ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt index 7f614b41f55..2a2908212f5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt @@ -332,8 +332,6 @@ fun PreviewCampaignEditAdContent() { WooThemeWithBackground { CampaignEditAdContent( viewState = ViewState( - tagLine = "From 45.00 USD", - description = "Get the latest white t-shirts", adImageUrl = "https://rb.gy/gmjuwb" ), onTagLineChanged = { }, From 2f193704995779c8087ae72ba1d0834a630b937a Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 25 Jan 2024 12:11:28 +0100 Subject: [PATCH 036/160] Load suggestions and allow toggling between them --- .../BlazeCampaignCreationEditAdViewModel.kt | 80 ++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt index 23b5b1d9bff..452398f7819 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt @@ -4,6 +4,8 @@ import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope +import com.woocommerce.android.ui.blaze.BlazeRepository +import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ViewState.Suggestion import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult @@ -12,12 +14,14 @@ import com.woocommerce.android.viewmodel.getStateFlow import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.wordpress.android.mediapicker.api.MediaPickerSetup import javax.inject.Inject @HiltViewModel class BlazeCampaignCreationEditAdViewModel @Inject constructor( + private val blazeRepository: BlazeRepository, savedStateHandle: SavedStateHandle ) : ScopedViewModel(savedStateHandle) { companion object { @@ -29,16 +33,50 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( private val _viewState = savedStateHandle.getStateFlow( scope = viewModelScope, - initialValue = ViewState(navArgs.tagline, navArgs.description, navArgs.adImageUrl) + initialValue = ViewState(navArgs.adImageUrl) ) val viewState = _viewState.asLiveData() + init { + loadSuggestions() + } + + private fun loadSuggestions() { + viewModelScope.launch { + blazeRepository.getAdSuggestions(navArgs.productId)?.let { list -> + val index = list.indexOfFirst { it.tagLine == navArgs.tagline && it.description == navArgs.description } + val suggestions = list.map { Suggestion(it.tagLine, it.description) } + if (index != -1) { + _viewState.update { + _viewState.value.copy( + suggestions = suggestions, + suggestionIndex = index + ) + } + } else { + _viewState.update { + _viewState.value.copy( + suggestions = listOf(Suggestion(navArgs.tagline, navArgs.description)) + suggestions, + suggestionIndex = 0 + ) + } + } + } + } + } + fun onNextSuggestionTapped() { - /* TODO */ + _viewState.update { + val index = _viewState.value.suggestionIndex + _viewState.value.copy(suggestionIndex = index + 1) + } } fun onPreviousSuggestionTapped() { - /* TODO */ + _viewState.update { + val index = _viewState.value.suggestionIndex + _viewState.value.copy(suggestionIndex = index - 1) + } } fun onSaveTapped() { @@ -71,11 +109,11 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( } fun onTagLineChanged(tagLine: String) { - _viewState.value = _viewState.value.copy(tagLine = tagLine.take(TAGLINE_MAX_LENGTH)) + updateSuggestion(Suggestion(tagLine.take(TAGLINE_MAX_LENGTH), _viewState.value.description)) } fun onDescriptionChanged(description: String) { - _viewState.value = _viewState.value.copy(description = description.take(DESCRIPTION_MAX_LENGTH)) + updateSuggestion(Suggestion(_viewState.value.tagLine, description.take(TAGLINE_MAX_LENGTH))) } fun onImageChanged(url: String) { @@ -84,6 +122,14 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( } } + private fun updateSuggestion(suggestion: Suggestion) { + _viewState.update { + val suggestions = _viewState.value.suggestions.toMutableList() + suggestions[_viewState.value.suggestionIndex] = suggestion + _viewState.value.copy(suggestions = suggestions) + } + } + private fun setMediaPickerDialogVisibility(isVisible: Boolean) { _viewState.update { _viewState.value.copy(isMediaPickerDialogVisible = isVisible) @@ -94,17 +140,29 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( @Parcelize data class ViewState( - val tagLine: String, - val description: String, val adImageUrl: String?, - val isPreviousSuggestionButtonEnabled: Boolean = false, - val isNextSuggestionButtonEnabled: Boolean = true, + val suggestions: List = emptyList(), + val suggestionIndex: Int = 0, val isMediaPickerDialogVisible: Boolean = false ) : Parcelable { + val tagLine: String + get() = suggestions.getOrNull(suggestionIndex)?.tagLine ?: "" + val description: String + get() = suggestions.getOrNull(suggestionIndex)?.description ?: "" val taglineCharactersRemaining: Int - get() = TAGLINE_MAX_LENGTH - tagLine.length + get() = TAGLINE_MAX_LENGTH - (suggestions.getOrNull(suggestionIndex)?.tagLine?.length ?: 0) val descriptionCharactersRemaining: Int - get() = DESCRIPTION_MAX_LENGTH - description.length + get() = DESCRIPTION_MAX_LENGTH - (suggestions.getOrNull(suggestionIndex)?.description?.length ?: 0) + val isPreviousSuggestionButtonEnabled: Boolean + get() = suggestionIndex > 0 + val isNextSuggestionButtonEnabled: Boolean + get() = suggestionIndex < suggestions.size - 1 + + @Parcelize + data class Suggestion( + var tagLine: String, + var description: String + ) : Parcelable } @Parcelize From af2ad26c5b4233d11b4d077fe9fb23372821544c Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 25 Jan 2024 12:13:42 +0100 Subject: [PATCH 037/160] Update the FluxC hash --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 009d40a67be..512050e4762 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2.63.0' + fluxCVersion = 'trunk-0c336369772d70082143ab140391fea0ddcb41a2' glideVersion = '4.13.2' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 002c7bef65a4c8dc61864a49cda443f34aac1cd7 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 25 Jan 2024 12:15:06 +0100 Subject: [PATCH 038/160] Fix the FluxC reference --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 512050e4762..2c5abf24892 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-0c336369772d70082143ab140391fea0ddcb41a2' + fluxCVersion = '2947-0c336369772d70082143ab140391fea0ddcb41a2' glideVersion = '4.13.2' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 2b5703af2999b1a7ce24a8f12dfecfbf931d8eb8 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Thu, 25 Jan 2024 14:47:59 -0300 Subject: [PATCH 039/160] Adjust OrderCreateEditViewModel to only trigger gift card CTA shown tracks when the Gift card button is enabled --- .../creation/OrderCreateEditViewModel.kt | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index f30ad6296dc..f422de5e73c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -166,6 +166,7 @@ import java.math.BigDecimal import java.util.Date import javax.inject.Inject import com.woocommerce.android.model.Product as ModelProduct +import kotlinx.coroutines.flow.filter @HiltViewModel @Suppress("LargeClass") @@ -340,6 +341,9 @@ class OrderCreateEditViewModel @Inject constructor( private val orderCreationStatus = Order.Status.Custom(Order.Status.AUTO_DRAFT) + private val giftCardWasEnabledAtLeastOnce: MutableStateFlow = + savedState.getStateFlow(viewModelScope, false) + init { monitorPluginAvailabilityChanges() @@ -705,11 +709,15 @@ class OrderCreateEditViewModel @Inject constructor( } private fun updateAddGiftCardButtonVisibility(order: Order) { - viewState = viewState.copy( - isAddGiftCardButtonEnabled = order.hasProducts() && - order.isEditable && - _selectedGiftCard.value.isEmpty() - ) + val shouldEnableAddGiftCardButton = order.hasProducts() && + order.isEditable && + _selectedGiftCard.value.isEmpty() + + viewState = viewState.copy(isAddGiftCardButtonEnabled = shouldEnableAddGiftCardButton) + + if (shouldEnableAddGiftCardButton) { + giftCardWasEnabledAtLeastOnce.update { true } + } } private fun Order.hasProducts() = items.any { it.quantity > 0 } @@ -1303,11 +1311,16 @@ class OrderCreateEditViewModel @Inject constructor( launch { pluginsInformation .onEach { - val isGiftCardExtensionEnabled = it[WOO_GIFT_CARDS.pluginName]?.isOperational ?: false - viewState = viewState.copy(shouldDisplayAddGiftCardButton = isGiftCardExtensionEnabled) - if (isGiftCardExtensionEnabled) { trackGiftCardCTAAvailable() } + viewState = viewState.copy( + shouldDisplayAddGiftCardButton = it[WOO_GIFT_CARDS.pluginName]?.isOperational ?: false + ) }.launchIn(viewModelScope) + giftCardWasEnabledAtLeastOnce + .filter { it && isGiftCardExtensionEnabled } + .onEach { trackGiftCardCTAAvailable() } + .launchIn(viewModelScope) + pluginsInformation.update { orderCreateEditRepository.fetchOrderSupportedPlugins() } From 7d87fce1765e8d5b88ac510925783276895b7398 Mon Sep 17 00:00:00 2001 From: ThomazFB Date: Thu, 25 Jan 2024 14:56:04 -0300 Subject: [PATCH 040/160] Fix lint issues --- .../android/ui/orders/creation/OrderCreateEditViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt index f422de5e73c..aea4ea7454e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/creation/OrderCreateEditViewModel.kt @@ -148,6 +148,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -166,7 +167,6 @@ import java.math.BigDecimal import java.util.Date import javax.inject.Inject import com.woocommerce.android.model.Product as ModelProduct -import kotlinx.coroutines.flow.filter @HiltViewModel @Suppress("LargeClass") From c765e99727a1d6b6988adfb6c959e069f4f2da71 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 25 Jan 2024 22:45:24 +0100 Subject: [PATCH 041/160] Point the hash ref to trunk --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2c5abf24892..7bf7df5415c 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2947-0c336369772d70082143ab140391fea0ddcb41a2' + fluxCVersion = 'trunk-bf45b08090e8241eb930e1eef32f935bc6cd0424' glideVersion = '4.13.2' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 94ebe81d59dd59d765780c536a654a64ebabaf47 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Thu, 25 Jan 2024 22:55:51 +0100 Subject: [PATCH 042/160] Add the language selection boiler plate --- .../BlazeCampaignTargetSelectionFragment.kt | 39 +++++++++++++++++++ .../BlazeCampaignTargetSelectionScreen.kt | 7 ++++ .../BlazeCampaignTargetSelectionViewModel.kt | 23 +++++++++++ 3 files changed, 69 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt new file mode 100644 index 00000000000..0e7e47cf1b5 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt @@ -0,0 +1,39 @@ +package com.woocommerce.android.ui.blaze.creation.targets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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 BlazeCampaignTargetSelectionFragment : BaseFragment() { + override val activityAppBarStatus: AppBarStatus + get() = AppBarStatus.Hidden + + val viewModel: BlazeCampaignTargetSelectionViewModel by viewModels() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return composeView { + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupObservers() + } + + private fun setupObservers() { + viewModel.event.observe(viewLifecycleOwner) { event -> + when (event) { + is MultiLiveEvent.Event.Exit -> findNavController().popBackStack() + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt new file mode 100644 index 00000000000..0c9d32089ef --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt @@ -0,0 +1,7 @@ +package com.woocommerce.android.ui.blaze.creation.targets + +import androidx.compose.runtime.Composable + +@Composable +fun BlazeCampaignTargetSelectionScreen(viewModel: BlazeCampaignTargetSelectionViewModel) { +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt new file mode 100644 index 00000000000..dc39ed7f34c --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -0,0 +1,23 @@ +package com.woocommerce.android.ui.blaze.creation.targets + +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.ui.products.ProductListRepository +import com.woocommerce.android.ui.products.ProductStatus +import com.woocommerce.android.util.CoroutineDispatchers +import com.woocommerce.android.util.WooLog +import com.woocommerce.android.viewmodel.MultiLiveEvent +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit +import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.navArgs +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.wordpress.android.fluxc.store.WCProductStore.ProductFilterOption +import org.wordpress.android.fluxc.store.WCProductStore.ProductSorting +import javax.inject.Inject + +@HiltViewModel +class BlazeCampaignTargetSelectionViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, +) : ScopedViewModel(savedStateHandle) { +} From 9e634e121932ba19c0a14b5bd0a2f1b53a829484 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 10:09:50 +0100 Subject: [PATCH 043/160] Moved receipt package to the top payments package --- .../payment/CardReaderPaymentDialogFragment.kt | 4 ++-- .../payment/CardReaderPaymentViewModel.kt | 4 ++-- .../{cardreader => }/receipt/ReceiptEvent.kt | 2 +- .../receipt/ReceiptPreviewFragment.kt | 8 ++++---- .../receipt/ReceiptPreviewViewModel.kt | 12 ++++++------ .../src/main/res/navigation/nav_graph_orders.xml | 2 +- .../cardreader/CardReaderPaymentViewModelTest.kt | 4 ++-- .../cardreader/ReceiptPreviewViewModelTest.kt | 14 +++++++------- 8 files changed, 25 insertions(+), 25 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/{cardreader => }/receipt/ReceiptEvent.kt (84%) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/{cardreader => }/receipt/ReceiptPreviewFragment.kt (92%) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/{cardreader => }/receipt/ReceiptPreviewViewModel.kt (86%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt index e91bcbb8bf2..8c60220f92e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt @@ -31,8 +31,8 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInR import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderPaymentSuccessfulState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulReceiptSentAutomaticallyState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulState -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.SendReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt import com.woocommerce.android.ui.payments.refunds.RefundSummaryFragment.Companion.KEY_INTERAC_SUCCESS import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 0f09f739469..a4318c6c086 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -67,8 +67,8 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.Processi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchingOrderState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.SendReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CoroutineDispatchers diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptEvent.kt similarity index 84% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptEvent.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptEvent.kt index b0a546966d4..879ce3eb15c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptEvent.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.cardreader.receipt +package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.model.UiString import com.woocommerce.android.viewmodel.MultiLiveEvent.Event diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt similarity index 92% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptPreviewFragment.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt index 5b23fa8ac44..8012b1dafdf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.cardreader.receipt +package com.woocommerce.android.ui.payments.receipt import android.os.Bundle import android.view.Menu @@ -14,9 +14,9 @@ import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentReceiptPreviewBinding import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.SendReceipt -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptPreviewViewModel.ReceiptPreviewEvent.LoadUrl +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewEvent.LoadUrl import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper import com.woocommerce.android.util.UiHelpers diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt similarity index 86% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptPreviewViewModel.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt index f75d69ce284..ebde95220b0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/receipt/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.cardreader.receipt +package com.woocommerce.android.ui.payments.receipt import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -14,11 +14,11 @@ import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.model.UiString.UiStringRes import com.woocommerce.android.model.UiString.UiStringText import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptEvent.SendReceipt -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptPreviewViewModel.ReceiptPreviewEvent.LoadUrl -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content -import com.woocommerce.android.ui.payments.cardreader.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt +import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewEvent.LoadUrl +import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content +import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED diff --git a/WooCommerce/src/main/res/navigation/nav_graph_orders.xml b/WooCommerce/src/main/res/navigation/nav_graph_orders.xml index 894bd9aa164..4d294c0b08b 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_orders.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_orders.xml @@ -488,7 +488,7 @@ Date: Fri, 26 Jan 2024 10:19:06 +0100 Subject: [PATCH 044/160] Removed shared Receipt related events --- .../payment/CardReaderPaymentDialogFragment.kt | 2 -- .../cardreader/payment/CardReaderPaymentViewModel.kt | 2 -- .../payment/CardReaderPaymentViewModelEvent.kt | 5 +++++ .../android/ui/payments/receipt/ReceiptEvent.kt | 9 --------- .../ui/payments/receipt/ReceiptPreviewFragment.kt | 3 --- .../ui/payments/receipt/ReceiptPreviewViewModel.kt | 8 -------- .../payments/receipt/ReceiptPreviewViewModelEvent.kt | 10 ++++++++++ .../cardreader/CardReaderPaymentViewModelTest.kt | 4 ++-- .../payments/cardreader/ReceiptPreviewViewModelTest.kt | 6 +++--- 9 files changed, 20 insertions(+), 29 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptEvent.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModelEvent.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt index 8c60220f92e..a317c226d28 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt @@ -31,8 +31,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInR import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderPaymentSuccessfulState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulReceiptSentAutomaticallyState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulState -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt import com.woocommerce.android.ui.payments.refunds.RefundSummaryFragment.Companion.KEY_INTERAC_SUCCESS import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index a4318c6c086..571343d9bde 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -67,8 +67,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.Processi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchingOrderState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CoroutineDispatchers diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt index d7d95a7ae46..5a44a75ebb3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.payments.cardreader.payment import androidx.annotation.StringRes +import com.woocommerce.android.model.UiString import com.woocommerce.android.viewmodel.MultiLiveEvent.Event class ShowSnackbarInDialog(@StringRes val message: Int) : Event() @@ -14,3 +15,7 @@ object ContactSupport : Event() object EnableNfc : Event() data class PurchaseCardReader(val url: String) : Event() + +data class PrintReceipt(val receiptUrl: String, val documentName: String) : Event() + +data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptEvent.kt deleted file mode 100644 index 879ce3eb15c..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.woocommerce.android.ui.payments.receipt - -import com.woocommerce.android.model.UiString -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event - -sealed class ReceiptEvent : Event() { - data class PrintReceipt(val receiptUrl: String, val documentName: String) : ReceiptEvent() - data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : ReceiptEvent() -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt index 8012b1dafdf..8c51e16c263 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt @@ -14,9 +14,6 @@ import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentReceiptPreviewBinding import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewEvent.LoadUrl import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper import com.woocommerce.android.util.UiHelpers diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt index ebde95220b0..63af044351b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt @@ -14,16 +14,12 @@ import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.model.UiString.UiStringRes import com.woocommerce.android.model.UiString.UiStringText import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewEvent.LoadUrl import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.STARTED -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs @@ -97,8 +93,4 @@ class ReceiptPreviewViewModel object Loading : ReceiptPreviewViewState(isProgressVisible = true) object Content : ReceiptPreviewViewState(isContentVisible = true) } - - sealed class ReceiptPreviewEvent : Event() { - data class LoadUrl(val url: String) : ReceiptPreviewEvent() - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModelEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModelEvent.kt new file mode 100644 index 00000000000..f6cce6c1413 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModelEvent.kt @@ -0,0 +1,10 @@ +package com.woocommerce.android.ui.payments.receipt + +import com.woocommerce.android.model.UiString +import com.woocommerce.android.viewmodel.MultiLiveEvent + +data class LoadUrl(val url: String) : MultiLiveEvent.Event() + +data class PrintReceipt(val receiptUrl: String, val documentName: String) : MultiLiveEvent.Event() + +data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : MultiLiveEvent.Event() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 8e3defb7d8a..073a1103bcf 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -67,7 +67,9 @@ import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError.AmountTooSmall import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError.Unknown import com.woocommerce.android.ui.payments.cardreader.payment.PlayChaChing +import com.woocommerce.android.ui.payments.cardreader.payment.PrintReceipt import com.woocommerce.android.ui.payments.cardreader.payment.PurchaseCardReader +import com.woocommerce.android.ui.payments.cardreader.payment.SendReceipt import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderCapturingPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderCollectPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderFailedPaymentState @@ -88,8 +90,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.Processi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchingOrderState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CurrencyFormatter diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/ReceiptPreviewViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/ReceiptPreviewViewModelTest.kt index 83bdb70f9df..bf28ef92b09 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/ReceiptPreviewViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/ReceiptPreviewViewModelTest.kt @@ -9,13 +9,13 @@ import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.PrintReceipt -import com.woocommerce.android.ui.payments.receipt.ReceiptEvent.SendReceipt +import com.woocommerce.android.ui.payments.receipt.LoadUrl +import com.woocommerce.android.ui.payments.receipt.PrintReceipt import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewFragmentArgs import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel -import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewEvent.LoadUrl import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading +import com.woocommerce.android.ui.payments.receipt.SendReceipt import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.STARTED From c8dbc0f2e5009c4354dcd5699dc82ac1790c624c Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 10:24:00 +0100 Subject: [PATCH 045/160] Moved receipt preview to preview package --- .../receipt/{ => preview}/ReceiptPreviewFragment.kt | 2 +- .../receipt/{ => preview}/ReceiptPreviewViewModel.kt | 6 +++--- .../{ => preview}/ReceiptPreviewViewModelEvent.kt | 2 +- .../src/main/res/navigation/nav_graph_orders.xml | 2 +- .../preview}/ReceiptPreviewViewModelTest.kt | 11 +++-------- 5 files changed, 9 insertions(+), 14 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/{ => preview}/ReceiptPreviewFragment.kt (98%) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/{ => preview}/ReceiptPreviewViewModel.kt (92%) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/{ => preview}/ReceiptPreviewViewModelEvent.kt (86%) rename WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/{cardreader => receipt/preview}/ReceiptPreviewViewModelTest.kt (89%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt similarity index 98% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt index 8c51e16c263..9523b67ec65 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.receipt +package com.woocommerce.android.ui.payments.receipt.preview import android.os.Bundle import android.view.Menu diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt similarity index 92% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index 63af044351b..e609731afa8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.receipt +package com.woocommerce.android.ui.payments.receipt.preview import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -14,8 +14,8 @@ import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.model.UiString.UiStringRes import com.woocommerce.android.model.UiString.UiStringText import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content -import com.woocommerce.android.ui.payments.receipt.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading +import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content +import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModelEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt similarity index 86% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModelEvent.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt index f6cce6c1413..5edf89f0559 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/ReceiptPreviewViewModelEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.receipt +package com.woocommerce.android.ui.payments.receipt.preview import com.woocommerce.android.model.UiString import com.woocommerce.android.viewmodel.MultiLiveEvent diff --git a/WooCommerce/src/main/res/navigation/nav_graph_orders.xml b/WooCommerce/src/main/res/navigation/nav_graph_orders.xml index 4d294c0b08b..5e9d5f0157e 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_orders.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_orders.xml @@ -488,7 +488,7 @@ Date: Fri, 26 Jan 2024 11:12:20 +0100 Subject: [PATCH 046/160] PaymentReceiptHelper to the receipt package --- .../payment/CardReaderPaymentViewModel.kt | 11 +++++----- .../PaymentReceiptHelper.kt} | 4 ++-- .../CardReaderPaymentViewModelTest.kt | 20 +++++++++---------- .../PaymentReceiptHelperTest.kt} | 6 +++--- 4 files changed, 21 insertions(+), 20 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/{cardreader/payment/CardReaderPaymentReceiptHelper.kt => receipt/PaymentReceiptHelper.kt} (93%) rename WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/{cardreader/payment/CardReaderPaymentReceiptHelperTest.kt => receipt/PaymentReceiptHelperTest.kt} (95%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 571343d9bde..87e76828d0f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -67,6 +67,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.Processi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchingOrderState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CoroutineDispatchers @@ -113,7 +114,7 @@ class CardReaderPaymentViewModel private val cardReaderTrackingInfoKeeper: CardReaderTrackingInfoKeeper, private val cardReaderPaymentReaderTypeStateProvider: CardReaderPaymentReaderTypeStateProvider, private val cardReaderPaymentOrderHelper: CardReaderPaymentOrderHelper, - private val cardReaderPaymentReceiptHelper: CardReaderPaymentReceiptHelper, + private val paymentReceiptHelper: PaymentReceiptHelper, private val cardReaderOnboardingChecker: CardReaderOnboardingChecker, private val cardReaderConfigProvider: CardReaderCountryConfigProvider, ) : ScopedViewModel(savedState) { @@ -300,7 +301,7 @@ class CardReaderPaymentViewModel currency = order.currency, orderKey = order.orderKey, customerEmail = customerEmail.ifEmpty { null }, - isPluginCanSendReceipt = cardReaderPaymentReceiptHelper.isPluginCanSendReceipt(site), + isPluginCanSendReceipt = paymentReceiptHelper.isPluginCanSendReceipt(site), customerName = "${order.billingAddress.firstName} ${order.billingAddress.lastName}".ifBlank { null }, storeName = selectedSite.get().name.ifEmpty { null }, siteUrl = selectedSite.get().url.ifEmpty { null }, @@ -455,7 +456,7 @@ class CardReaderPaymentViewModel paymentStatus: PaymentCompleted, orderId: Long, ) { - cardReaderPaymentReceiptHelper.storeReceiptUrl(orderId, paymentStatus.receiptUrl) + paymentReceiptHelper.storeReceiptUrl(orderId, paymentStatus.receiptUrl) appPrefs.setCardReaderSuccessfulPaymentTime() triggerEvent(PlayChaChing) showPaymentSuccessfulState() @@ -604,7 +605,7 @@ class CardReaderPaymentViewModel launch { val order = requireNotNull(orderRepository.getOrderById(orderId)) { "Order URL not available." } val amountLabel = cardReaderPaymentOrderHelper.getAmountLabel(order) - val receiptUrl = cardReaderPaymentReceiptHelper.getReceiptUrl(order.id) + val receiptUrl = paymentReceiptHelper.getReceiptUrl(order.id) val onPrintReceiptClicked = { onPrintReceiptClicked( amountLabel, @@ -717,7 +718,7 @@ class CardReaderPaymentViewModel ?: throw IllegalStateException("Order URL not available.") triggerEvent( PrintReceipt( - cardReaderPaymentReceiptHelper.getReceiptUrl(order.id), + paymentReceiptHelper.getReceiptUrl(order.id), cardReaderPaymentOrderHelper.getReceiptDocumentName(order) ) ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt similarity index 93% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentReceiptHelper.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 2106c7b57cf..4ce9c100406 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.cardreader.payment +package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.extensions.semverCompareTo @@ -8,7 +8,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.WCPAY_RECEIPTS_ import org.wordpress.android.fluxc.model.SiteModel import javax.inject.Inject -class CardReaderPaymentReceiptHelper @Inject constructor( +class PaymentReceiptHelper @Inject constructor( private val selectedSite: SelectedSite, private val appPrefsWrapper: AppPrefsWrapper ) { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 073a1103bcf..90780aaddfc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -58,7 +58,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentD import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentReaderTypeStateProvider -import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentViewModel import com.woocommerce.android.ui.payments.cardreader.payment.ContactSupport import com.woocommerce.android.ui.payments.cardreader.payment.EnableNfc @@ -178,7 +178,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { private val interacRefundableChecker: CardReaderInteracRefundableChecker = mock() private val cardReaderPaymentReaderTypeStateProvider = CardReaderPaymentReaderTypeStateProvider() private val cardReaderPaymentOrderHelper: CardReaderPaymentOrderHelper = mock() - private val cardReaderPaymentReceiptHelper: CardReaderPaymentReceiptHelper = mock() + private val paymentReceiptHelper: PaymentReceiptHelper = mock() private val cardReaderOnboardingChecker: CardReaderOnboardingChecker = mock() private val cardReaderConfigProvider: CardReaderCountryConfigProvider = mock() private val cardReaderConfig: CardReaderConfigForSupportedCountry = CardReaderConfigForUSA @@ -206,7 +206,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, cardReaderPaymentReaderTypeStateProvider = cardReaderPaymentReaderTypeStateProvider, cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, - cardReaderPaymentReceiptHelper = cardReaderPaymentReceiptHelper, + paymentReceiptHelper = paymentReceiptHelper, cardReaderOnboardingChecker = cardReaderOnboardingChecker, cardReaderConfigProvider = cardReaderConfigProvider, ) @@ -240,8 +240,8 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { flow {} } - whenever(cardReaderPaymentReceiptHelper.isPluginCanSendReceipt(siteModel)).thenReturn(true) - whenever(cardReaderPaymentReceiptHelper.getReceiptUrl(ORDER_ID)).thenReturn("test url") + whenever(paymentReceiptHelper.isPluginCanSendReceipt(siteModel)).thenReturn(true) + whenever(paymentReceiptHelper.getReceiptUrl(ORDER_ID)).thenReturn("test url") whenever(cardReaderPaymentOrderHelper.getPaymentDescription(mockedOrder)).thenReturn("test description") whenever(cardReaderPaymentOrderHelper.getAmountLabel(mockedOrder)) .thenReturn("$DUMMY_CURRENCY_SYMBOL$DUMMY_TOTAL") @@ -1916,7 +1916,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { viewModel.start() - verify(cardReaderPaymentReceiptHelper).storeReceiptUrl(eq(ORDER_ID), eq(receiptUrl)) + verify(paymentReceiptHelper).storeReceiptUrl(eq(ORDER_ID), eq(receiptUrl)) } @Test @@ -3054,7 +3054,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { fun `given plugin can not be send, when flow started, then wc pay can send receipt is false`() = testBlocking { // Given - whenever(cardReaderPaymentReceiptHelper.isPluginCanSendReceipt(siteModel)).thenReturn(false) + whenever(paymentReceiptHelper.isPluginCanSendReceipt(siteModel)).thenReturn(false) val captor = argumentCaptor() // When @@ -3069,7 +3069,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { fun `given plugin can be send, when flow started, then wc pay can send receipt is true`() = testBlocking { // Given - whenever(cardReaderPaymentReceiptHelper.isPluginCanSendReceipt(siteModel)).thenReturn(true) + whenever(paymentReceiptHelper.isPluginCanSendReceipt(siteModel)).thenReturn(true) val captor = argumentCaptor() // When @@ -4328,7 +4328,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, cardReaderPaymentReaderTypeStateProvider = cardReaderPaymentReaderTypeStateProvider, cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, - cardReaderPaymentReceiptHelper = cardReaderPaymentReceiptHelper, + paymentReceiptHelper = paymentReceiptHelper, cardReaderOnboardingChecker = cardReaderOnboardingChecker, cardReaderConfigProvider = cardReaderConfigProvider, ) @@ -4360,7 +4360,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, cardReaderPaymentReaderTypeStateProvider = cardReaderPaymentReaderTypeStateProvider, cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, - cardReaderPaymentReceiptHelper = cardReaderPaymentReceiptHelper, + paymentReceiptHelper = paymentReceiptHelper, cardReaderOnboardingChecker = cardReaderOnboardingChecker, cardReaderConfigProvider = cardReaderConfigProvider, ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentReceiptHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt similarity index 95% rename from WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentReceiptHelperTest.kt rename to WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt index bf8729eaf23..4dacf8a0fc2 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentReceiptHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.payments.cardreader.payment +package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.tools.SelectedSite @@ -12,13 +12,13 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi -class CardReaderPaymentReceiptHelperTest { +class PaymentReceiptHelperTest { private val selectedSite: SelectedSite = mock { on { get() }.thenReturn(mock()) } private val appPrefsWrapper: AppPrefsWrapper = mock() - private val helper = CardReaderPaymentReceiptHelper(selectedSite, appPrefsWrapper) + private val helper = PaymentReceiptHelper(selectedSite, appPrefsWrapper) @Test fun `given selected site, when storeReceiptUrl, then url is stored`() { From 114c3635aa31fa4021766802a66c3aa562fefc3a Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 11:23:30 +0100 Subject: [PATCH 047/160] Fix order of the imports --- .../ui/payments/cardreader/CardReaderPaymentViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 90780aaddfc..7b504fa8b7e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -58,7 +58,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentD import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentReaderTypeStateProvider -import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentViewModel import com.woocommerce.android.ui.payments.cardreader.payment.ContactSupport import com.woocommerce.android.ui.payments.cardreader.payment.EnableNfc @@ -90,6 +89,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.Processi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchingOrderState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CurrencyFormatter From 45a60b1dacfe14a61d60b7bf19d20091de2f98d4 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 12:23:43 +0100 Subject: [PATCH 048/160] Method to check if backend receipt generation supported --- .../onboarding/CardReaderOnboardingChecker.kt | 2 -- .../payments/receipt/PaymentReceiptHelper.kt | 21 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt index c52fc728e83..9f0bceea198 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt @@ -54,8 +54,6 @@ import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPayment import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject -const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" - /** * This class is used to check if the selected store is ready to accept In Person Payments. The app should check store's * eligibility every time it attempts to connect to a card reader. diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 4ce9c100406..e2bb634367d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -4,12 +4,15 @@ import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.extensions.semverCompareTo import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType -import com.woocommerce.android.ui.payments.cardreader.onboarding.WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject class PaymentReceiptHelper @Inject constructor( private val selectedSite: SelectedSite, + private val wooCommerceStore: WooCommerceStore, private val appPrefsWrapper: AppPrefsWrapper ) { fun storeReceiptUrl(orderId: Long, receiptUrl: String) { @@ -41,4 +44,20 @@ class PaymentReceiptHelper @Inject constructor( pluginVersion.semverCompareTo(WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION) >= 0 } } + + private suspend fun isWCCanGenerateReceipts() = + withContext(Dispatchers.IO) { + val currentWooCoreVersion = + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + )?.version ?: return@withContext false + + currentWooCoreVersion.semverCompareTo(WC_CAN_GENERATE_RECEIPTS_VERSION) >= 0 + } + + private companion object { + const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" + const val WC_CAN_GENERATE_RECEIPTS_VERSION = "6.4.0" + } } From fa22ea2ff7ca0892b49d866f6e0ab8b9946c1841 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 14:30:16 +0100 Subject: [PATCH 049/160] Updated fluxc hash --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7bf7df5415c..55a46108b90 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-bf45b08090e8241eb930e1eef32f935bc6cd0424' + fluxCVersion = '2948-235834ae9467ae3ca367a4b13c4f9c6aea0f31bf' glideVersion = '4.13.2' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 553956694142df24073a6657d0e534b3eae4df65 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 14:37:00 +0100 Subject: [PATCH 050/160] Use the receipt helper instead of preference wrapper --- .../ui/orders/details/OrderDetailViewModel.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index 38375776ee7..07788c9b132 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.distinctUntilChanged import com.google.android.material.snackbar.Snackbar import com.woocommerce.android.AppPrefs import com.woocommerce.android.R.string +import com.woocommerce.android.extensions.orNullIfEmpty import com.woocommerce.android.extensions.whenNotNullNorEmpty import com.woocommerce.android.model.GiftCardSummary import com.woocommerce.android.model.Order @@ -49,6 +50,7 @@ import com.woocommerce.android.ui.orders.OrderStatusUpdateSource import com.woocommerce.android.ui.orders.details.customfields.CustomOrderFieldsHelper import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.ui.products.addons.AddonRepository @@ -91,7 +93,8 @@ class OrderDetailViewModel @Inject constructor( private val getOrderSubscriptions: GetOrderSubscriptions, private val giftCardRepository: GiftCardRepository, private val orderProductMapper: OrderProductMapper, - private val productDetailRepository: ProductDetailRepository + private val productDetailRepository: ProductDetailRepository, + private val paymentReceiptHelper: PaymentReceiptHelper, ) : ScopedViewModel(savedState), OnProductFetchedListener { private val navArgs: OrderDetailFragmentArgs by savedState.navArgs() @@ -344,7 +347,7 @@ class OrderDetailViewModel @Inject constructor( fun onSeeReceiptClicked() { tracker.trackReceiptViewTapped(order.id, order.status) - loadReceiptUrl()?.let { + loadReceiptUrl().orNullIfEmpty()?.let { triggerEvent(PreviewReceipt(order.billingAddress.email, it, order.id)) } ?: WooLog.e(T.ORDERS, "ReceiptUrl is null, but SeeReceipt button is visible") } @@ -372,11 +375,7 @@ class OrderDetailViewModel @Inject constructor( triggerEvent(ShowSnackbar(message)) } - private fun loadReceiptUrl(): String? { - return selectedSite.getIfExists()?.let { - appPrefs.getReceiptUrl(it.id, it.siteId, it.selfHostedSiteId, order.id) - } - } + private fun loadReceiptUrl() = paymentReceiptHelper.getReceiptUrl(order.id) fun onViewRefundedProductsClicked() { triggerEvent(ViewRefundedProducts(orderId = order.id)) @@ -580,7 +579,7 @@ class OrderDetailViewModel @Inject constructor( orderInfo = OrderDetailViewState.OrderInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectable, - isReceiptButtonsVisible = !loadReceiptUrl().isNullOrEmpty() + isReceiptButtonsVisible = loadReceiptUrl().isNotEmpty() ), orderStatus = orderStatus, toolbarTitle = resourceProvider.getString( From 0cbba32ab28b5f66c135f3e7f541742cd505c67b Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 14:56:21 +0100 Subject: [PATCH 051/160] Made test compilable --- .../android/ui/payments/receipt/PaymentReceiptHelper.kt | 2 +- .../android/ui/orders/OrderDetailViewModelTest.kt | 7 +++++-- .../ui/payments/receipt/PaymentReceiptHelperTest.kt | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index e2bb634367d..38dba7d44a0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -13,7 +13,7 @@ import javax.inject.Inject class PaymentReceiptHelper @Inject constructor( private val selectedSite: SelectedSite, private val wooCommerceStore: WooCommerceStore, - private val appPrefsWrapper: AppPrefsWrapper + private val appPrefsWrapper: AppPrefsWrapper, ) { fun storeReceiptUrl(orderId: Long, receiptUrl: String) { selectedSite.get().let { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index e9bd5d97a39..e7c1e68b49a 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -115,6 +115,7 @@ class OrderDetailViewModelTest : BaseUnitTest() { private val orderDetailsTransactionLauncher = mock() private val orderProductMapper = OrderProductMapper() private val productDetailRepository: ProductDetailRepository = mock() + private val paymentReceiptHelper: PaymentReceiptHelper = mock() private val order = OrderTestUtils.generateTestOrder(ORDER_ID) private val orderInfo = OrderInfo(OrderTestUtils.generateTestOrder(ORDER_ID)) @@ -177,7 +178,8 @@ class OrderDetailViewModelTest : BaseUnitTest() { getOrderSubscriptions, giftCardRepository, orderProductMapper, - productDetailRepository + productDetailRepository, + paymentReceiptHelper, ) ) } @@ -201,7 +203,8 @@ class OrderDetailViewModelTest : BaseUnitTest() { getOrderSubscriptions, giftCardRepository, orderProductMapper, - productDetailRepository + productDetailRepository, + paymentReceiptHelper ) ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt index 4dacf8a0fc2..fcad01fe160 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt @@ -10,6 +10,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.store.WooCommerceStore @ExperimentalCoroutinesApi class PaymentReceiptHelperTest { @@ -17,8 +18,9 @@ class PaymentReceiptHelperTest { on { get() }.thenReturn(mock()) } private val appPrefsWrapper: AppPrefsWrapper = mock() + private val wooCommerceStore: WooCommerceStore = mock() - private val helper = PaymentReceiptHelper(selectedSite, appPrefsWrapper) + private val helper = PaymentReceiptHelper(selectedSite, wooCommerceStore, appPrefsWrapper) @Test fun `given selected site, when storeReceiptUrl, then url is stored`() { From 1e08d1ecd1ff8e4332361360e491dad421405e3f Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 15:30:06 +0100 Subject: [PATCH 052/160] Modification on the order details screen to fetch the receipt including from the backend --- .../ui/orders/details/OrderDetailViewModel.kt | 18 ++++++----- .../payments/receipt/PaymentReceiptHelper.kt | 31 ++++++++++++++++++- WooCommerce/src/main/res/values/strings.xml | 1 + 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index 07788c9b132..42097b29b17 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -11,7 +11,6 @@ import androidx.lifecycle.distinctUntilChanged import com.google.android.material.snackbar.Snackbar import com.woocommerce.android.AppPrefs import com.woocommerce.android.R.string -import com.woocommerce.android.extensions.orNullIfEmpty import com.woocommerce.android.extensions.whenNotNullNorEmpty import com.woocommerce.android.model.GiftCardSummary import com.woocommerce.android.model.Order @@ -346,10 +345,15 @@ class OrderDetailViewModel @Inject constructor( } fun onSeeReceiptClicked() { - tracker.trackReceiptViewTapped(order.id, order.status) - loadReceiptUrl().orNullIfEmpty()?.let { - triggerEvent(PreviewReceipt(order.billingAddress.email, it, order.id)) - } ?: WooLog.e(T.ORDERS, "ReceiptUrl is null, but SeeReceipt button is visible") + launch { + tracker.trackReceiptViewTapped(order.id, order.status) + val receiptResult = paymentReceiptHelper.getReceiptUrl(order.id) + if (receiptResult.isSuccess) { + triggerEvent(PreviewReceipt(order.billingAddress.email, receiptResult.getOrThrow(), order.id)) + } else { + triggerEvent(ShowSnackbar(string.order_detail_receipt_fetching_error)) + } + } } fun onPrintingInstructionsClicked() { @@ -375,8 +379,6 @@ class OrderDetailViewModel @Inject constructor( triggerEvent(ShowSnackbar(message)) } - private fun loadReceiptUrl() = paymentReceiptHelper.getReceiptUrl(order.id) - fun onViewRefundedProductsClicked() { triggerEvent(ViewRefundedProducts(orderId = order.id)) } @@ -579,7 +581,7 @@ class OrderDetailViewModel @Inject constructor( orderInfo = OrderDetailViewState.OrderInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectable, - isReceiptButtonsVisible = loadReceiptUrl().isNotEmpty() + isReceiptButtonsVisible = paymentReceiptHelper.isReceiptAvailable(order.id), ), orderStatus = orderStatus, toolbarTitle = resourceProvider.getString( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 38dba7d44a0..c483e8f1d97 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -7,6 +7,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.WCOrderStore import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject @@ -14,6 +15,7 @@ class PaymentReceiptHelper @Inject constructor( private val selectedSite: SelectedSite, private val wooCommerceStore: WooCommerceStore, private val appPrefsWrapper: AppPrefsWrapper, + private val orderStore: WCOrderStore, ) { fun storeReceiptUrl(orderId: Long, receiptUrl: String) { selectedSite.get().let { @@ -21,7 +23,32 @@ class PaymentReceiptHelper @Inject constructor( } } - fun getReceiptUrl(orderId: Long) = selectedSite.get().let { + suspend fun getReceiptUrl(orderId: Long): Result = + if (isWCCanGenerateReceipts()) { + val fetchingResult = orderStore.fetchOrdersReceipt( + site = selectedSite.get(), + orderId = orderId, + expirationDays = RECEIPT_EXPIRATION_DAYS, + ) + + val result = fetchingResult.result + if (fetchingResult.isError || result == null) { + Result.failure(Exception(fetchingResult.error.message)) + } else { + Result.success(result.receiptUrl) + } + } else { + Result.success(getReceiptUrlFromAppPrefs(orderId)) + } + + suspend fun isReceiptAvailable(orderId: Long) = + when { + isWCCanGenerateReceipts() -> true + getReceiptUrlFromAppPrefs(orderId).isNotEmpty() -> true + else -> false + } + + private fun getReceiptUrlFromAppPrefs(orderId: Long) = selectedSite.get().let { appPrefsWrapper.getReceiptUrl(it.id, it.siteId, it.selfHostedSiteId, orderId) } @@ -59,5 +86,7 @@ class PaymentReceiptHelper @Inject constructor( private companion object { const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" const val WC_CAN_GENERATE_RECEIPTS_VERSION = "6.4.0" + + const val RECEIPT_EXPIRATION_DAYS = 365 } } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index c3319c2b7db..21290f61aea 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -878,6 +878,7 @@ Use as Shipping Address PAYMENT TOTALS CUSTOM AMOUNTS + Sorry, we couldn\'t load a receipt for this order +## 17.1 +This release focuses on bug fixes and improvements to help you get your business started. Keep your feedback rolling in; it helps us figure out what to work on next. + ## 17.0 Get ready for a smoother experience with our latest update! We've ironed out the pesky bug that kept gift cards from showing up in the 'totals' section during order creation and editing. Managing your sales with our WooCommerce app is now more seamless than ever. Update now and enjoy the hassle-free enhancements! From de8a4c148728e8669b7105cf252767b5fc25e1e1 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 26 Jan 2024 15:56:27 +0100 Subject: [PATCH 056/160] Use dev version of plugin so we can test it --- .../payments/receipt/PaymentReceiptHelper.kt | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index c483e8f1d97..cf07df75256 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -1,11 +1,10 @@ package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.AppPrefsWrapper +import com.woocommerce.android.BuildConfig import com.woocommerce.android.extensions.semverCompareTo import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.WCOrderStore import org.wordpress.android.fluxc.store.WooCommerceStore @@ -72,16 +71,29 @@ class PaymentReceiptHelper @Inject constructor( } } - private suspend fun isWCCanGenerateReceipts() = - withContext(Dispatchers.IO) { - val currentWooCoreVersion = - wooCommerceStore.getSitePlugin( - selectedSite.get(), - WooCommerceStore.WooPlugin.WOO_CORE - )?.version ?: return@withContext false + private suspend fun isWCCanGenerateReceipts(): Boolean { + val currentWooCoreVersion = getWoocommerceCorePluginVersion() - currentWooCoreVersion.semverCompareTo(WC_CAN_GENERATE_RECEIPTS_VERSION) >= 0 - } + return currentWooCoreVersion.semverCompareTo(WC_CAN_GENERATE_RECEIPTS_VERSION) >= 0 + } + + private suspend fun getWoocommerceCorePluginVersion(): String { + val sitePlugin = wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + return if (sitePlugin == null) { + if (BuildConfig.DEBUG) { + wooCommerceStore.getSitePlugins(selectedSite.get()) + .firstOrNull { it.name == "woocommerce-dev/woocommerce" } + ?.let { it.version } + } else { + "" + } + } else { + sitePlugin.version + } ?: "" + } private companion object { const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" From 99d70ad27a55949d0e4d78b2420d35022daeed59 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 26 Jan 2024 07:21:27 -0800 Subject: [PATCH 057/160] Create intermediate backmerge branches --- fastlane/Fastfile | 54 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 89ec7f2bd72..fdd926ee1b9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -208,6 +208,13 @@ platform :android do trigger_release_build(branch_to_build: "release/#{release_version_current}") + # Create an intermediate branch + new_intermediate_branch_name = "merge/#{release_version_current}-code-freeze-into-#{DEFAULT_BRANCH}" + Fastlane::Helper::GitHelper.create_branch(new_intermediate_branch_name) + + # Push up the intermediate branch + push_to_git_remote(tags: false) + create_release_management_pull_request( base_branch: DEFAULT_BRANCH, title: "Merge #{release_version_current} code freeze to #{DEFAULT_BRANCH}" @@ -312,17 +319,20 @@ platform :android do UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") end - # Create an intermediate branch - new_beta_branch_name = "new_beta/#{version_name_current}" - Fastlane::Helper::GitHelper.create_branch(new_beta_branch_name) - push_to_git_remote(tags: false) trigger_release_build(branch_to_build: "release/#{release_version_current}") + # Create an intermediate branch + new_intermediate_branch_name = "merge/#{version_name_current}-into-#{DEFAULT_BRANCH}" + Fastlane::Helper::GitHelper.create_branch(new_intermediate_branch_name) + + # Push up the intermediate branch + push_to_git_remote(tags: false) + create_release_management_pull_request( - base_branch: "release/#{release_version_current}", - title: "Merge #{version_name_current} to release/#{release_version_current}" + base_branch: DEFAULT_BRANCH, + title: "Merge #{version_name_current} into #{DEFAULT_BRANCH}" ) end @@ -406,14 +416,33 @@ platform :android do # Verify that there's nothing in progress in the working copy ensure_git_status_clean - UI.important("Triggering hotfix build for version: #{release_version_current}") + hotfix_version = release_version_current + + UI.important("Triggering hotfix build for version: #{hotfix_version}") unless options[:skip_confirm] || UI.confirm('Do you want to continue?') UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") end push_to_git_remote(tags: false) - trigger_release_build(branch_to_build: "release/#{release_version_current}") + trigger_release_build(branch_to_build: "release/#{hotfix_version}") + + # Retrieve the current non-hotfix release version + Fastlane::Helper::GitHelper.checkout_and_pull(DEFAULT_BRANCH) + non_hotfix_release_version = release_version_current + + # Create an intermediate branch + Fastlane::Helper::GitHelper.checkout_and_pull("release/#{hotfix_version}") + new_intermediate_branch_name = "merge/#{hotfix_version}-hotfix-into-release-#{non_hotfix_release_version}" + Fastlane::Helper::GitHelper.create_branch(new_intermediate_branch_name) + + # Push up the intermediate branch + push_to_git_remote(tags: false) + + create_release_management_pull_request( + base_branch: "release/#{non_hotfix_release_version}", + title: "Merge #{hotfix_version} hotfix into release/#{non_hotfix_release_version}" + ) end ##################################################################################### @@ -512,9 +541,16 @@ platform :android do trigger_release_build(branch_to_build: "release/#{version}") + # Create an intermediate branch + new_intermediate_branch_name = "merge/#{release_version_current}-final-into-#{DEFAULT_BRANCH}" + Fastlane::Helper::GitHelper.create_branch(new_intermediate_branch_name) + + # Push up the intermediate branch + push_to_git_remote(tags: false) + create_release_management_pull_request( base_branch: DEFAULT_BRANCH, - title: "Merge #{version_name_current} final to #{DEFAULT_BRANCH}" + title: "Merge #{release_version_current} final into #{DEFAULT_BRANCH}" ) end From 2f6e6be094c5d670a7e551c09591433e236c3f20 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 26 Jan 2024 22:16:19 +0100 Subject: [PATCH 058/160] Add a combine helper function for 6 flows --- .../android/extensions/FlowExtensions.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt index 6a35efd20e6..da62b980c36 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt @@ -2,6 +2,29 @@ package com.woocommerce.android.extensions import kotlinx.coroutines.flow.Flow +@Suppress("LongParameterList") +inline fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R +): Flow { + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> -> + @Suppress("UNCHECKED_CAST", "MagicNumber") + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + ) + } +} + @Suppress("LongParameterList") inline fun combine( flow: Flow, From 188a3661c9cda39b7120a5d7b5cec9e0163e6fb1 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 26 Jan 2024 22:16:50 +0100 Subject: [PATCH 059/160] Use the existing class instead of the same one --- .../BlazeCampaignCreationEditAdViewModel.kt | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt index 452398f7819..b396fe82b6b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.ui.blaze.BlazeRepository -import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ViewState.Suggestion +import com.woocommerce.android.ui.blaze.BlazeRepository.AiSuggestionForAd import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult @@ -45,7 +45,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( viewModelScope.launch { blazeRepository.getAdSuggestions(navArgs.productId)?.let { list -> val index = list.indexOfFirst { it.tagLine == navArgs.tagline && it.description == navArgs.description } - val suggestions = list.map { Suggestion(it.tagLine, it.description) } + val suggestions = list.map { AiSuggestionForAd(it.tagLine, it.description) } if (index != -1) { _viewState.update { _viewState.value.copy( @@ -56,7 +56,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( } else { _viewState.update { _viewState.value.copy( - suggestions = listOf(Suggestion(navArgs.tagline, navArgs.description)) + suggestions, + suggestions = listOf(AiSuggestionForAd(navArgs.tagline, navArgs.description)) + suggestions, suggestionIndex = 0 ) } @@ -109,11 +109,11 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( } fun onTagLineChanged(tagLine: String) { - updateSuggestion(Suggestion(tagLine.take(TAGLINE_MAX_LENGTH), _viewState.value.description)) + updateSuggestion(AiSuggestionForAd(tagLine.take(TAGLINE_MAX_LENGTH), _viewState.value.description)) } fun onDescriptionChanged(description: String) { - updateSuggestion(Suggestion(_viewState.value.tagLine, description.take(TAGLINE_MAX_LENGTH))) + updateSuggestion(AiSuggestionForAd(_viewState.value.tagLine, description.take(TAGLINE_MAX_LENGTH))) } fun onImageChanged(url: String) { @@ -122,7 +122,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( } } - private fun updateSuggestion(suggestion: Suggestion) { + private fun updateSuggestion(suggestion: AiSuggestionForAd) { _viewState.update { val suggestions = _viewState.value.suggestions.toMutableList() suggestions[_viewState.value.suggestionIndex] = suggestion @@ -141,7 +141,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( @Parcelize data class ViewState( val adImageUrl: String?, - val suggestions: List = emptyList(), + val suggestions: List = emptyList(), val suggestionIndex: Int = 0, val isMediaPickerDialogVisible: Boolean = false ) : Parcelable { @@ -157,12 +157,6 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( get() = suggestionIndex > 0 val isNextSuggestionButtonEnabled: Boolean get() = suggestionIndex < suggestions.size - 1 - - @Parcelize - data class Suggestion( - var tagLine: String, - var description: String - ) : Parcelable } @Parcelize From 62cf4a6aa1c3f5046a0aaec522dcf4dd5571fe6f Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 26 Jan 2024 22:17:21 +0100 Subject: [PATCH 060/160] Use navigateSafely instead of navigate --- .../creation/preview/BlazeCampaignCreationPreviewFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ea833c55a1c..087d1ab0394 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 @@ -45,7 +45,7 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignBudgetFragment() ) - is NavigateToEditAdScreen -> findNavController().navigate( + is NavigateToEditAdScreen -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignCreationEditAdFragment( event.productId, From ddf249b9efab38facc32f87af0326519ecb613a7 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 26 Jan 2024 22:18:07 +0100 Subject: [PATCH 061/160] Parcelize data classes and remove the redundant campaign parameters --- .../android/ui/blaze/BlazeRepository.kt | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 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 6a80df6d92f..f339617f7d0 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,8 +1,10 @@ package com.woocommerce.android.ui.blaze +import android.os.Parcelable import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.util.TimezoneProvider +import kotlinx.parcelize.Parcelize import org.wordpress.android.fluxc.persistence.blaze.BlazeCampaignsDao.BlazeAdSuggestionEntity import org.wordpress.android.fluxc.store.blaze.BlazeCampaignsStore import java.util.Date @@ -43,18 +45,6 @@ class BlazeRepository @Inject constructor( val product = productDetailRepository.getProduct(productId) return CampaignPreview( productId = productId, - aiSuggestions = listOf(), - budget = Budget( - totalBudget = DEFAULT_CAMPAIGN_TOTAL_BUDGET, - spentBudget = 0f, - currencyCode = BLAZE_DEFAULT_CURRENCY_CODE, - durationInDays = DEFAULT_CAMPAIGN_DURATION, - startDate = Date().apply { time += ONE_DAY_IN_MILLIS }, // By default start tomorrow - ), - languages = listOf(), - devices = listOf(), - locations = listOf(), - interests = listOf(), userTimeZone = timezoneProvider.deviceTimezone.displayName, targetUrl = product?.permalink ?: "", urlParams = null, @@ -62,50 +52,51 @@ class BlazeRepository @Inject constructor( ) } + @Parcelize data class CampaignPreview( val productId: Long, - val aiSuggestions: List, - val budget: Budget, - val languages: List, - val devices: List, - val locations: List, - val interests: List, val userTimeZone: String, val targetUrl: String, val urlParams: Pair?, val campaignImageUrl: String?, - ) + ) : Parcelable + @Parcelize data class AiSuggestionForAd( val tagLine: String, val description: String, - ) + ) : Parcelable + @Parcelize data class Budget( val totalBudget: Float, val spentBudget: Float, val currencyCode: String, val durationInDays: Int, val startDate: Date, - ) + ) : Parcelable + @Parcelize data class Location( val id: String, val name: String, - ) + ) : Parcelable + @Parcelize data class Language( val code: String, val name: String, - ) + ) : Parcelable + @Parcelize data class Device( val id: String, val name: String, - ) + ) : Parcelable + @Parcelize data class Interest( val id: String, val description: String, - ) + ) : Parcelable } From 9123be07712fe39c5d2c7283b2bbd58a71ce7714 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 26 Jan 2024 22:19:40 +0100 Subject: [PATCH 062/160] Refactor the preview ViewModel state management --- .../BlazeCampaignCreationPreviewViewModel.kt | 172 +++++++++++------- 1 file changed, 106 insertions(+), 66 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 a1b5f90b6e6..306ecc87184 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 @@ -1,68 +1,119 @@ package com.woocommerce.android.ui.blaze.creation.preview -import androidx.lifecycle.MutableLiveData +import android.os.Parcelable import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope import com.woocommerce.android.R.string +import com.woocommerce.android.extensions.combine import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.ui.blaze.BlazeRepository +import com.woocommerce.android.ui.blaze.BlazeRepository.Budget import com.woocommerce.android.ui.blaze.BlazeRepository.CampaignPreview +import com.woocommerce.android.ui.blaze.BlazeRepository.Device +import com.woocommerce.android.ui.blaze.BlazeRepository.Interest +import com.woocommerce.android.ui.blaze.BlazeRepository.Language +import com.woocommerce.android.ui.blaze.BlazeRepository.Location +import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails +import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.getStateFlow import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize +import java.util.Date import javax.inject.Inject @HiltViewModel class BlazeCampaignCreationPreviewViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - blazeRepository: BlazeRepository, + private val blazeRepository: BlazeRepository, private val resourceProvider: ResourceProvider, private val currencyFormatter: CurrencyFormatter ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationPreviewFragmentArgs by savedStateHandle.navArgs() - - private val _viewState = MutableLiveData( - blazeRepository - .getCampaignPreviewDetails(navArgs.productId) - .toCampaignPreviewUiState(isLoading = true) - ) - val viewState = _viewState + private val campaign = blazeRepository.getCampaignPreviewDetails(navArgs.productId) + + private val adDetails = savedStateHandle.getStateFlow(viewModelScope, Loading) + private val budget = savedStateHandle.getStateFlow(viewModelScope, getDefaultBudget()) + private val selectedLanguages = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val selectedDevices = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val selectedInterests = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val selectedLocations = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + + val viewState = combine( + adDetails, + budget, + selectedLanguages, + selectedDevices, + selectedInterests, + selectedLocations + ) { adDetails, budget, languages, devices, interests, locations -> + CampaignPreviewUiState( + adDetails = adDetails, + campaignDetails = campaign.toCampaignDetailsUi(budget, languages, devices, locations, interests) + ) + }.asLiveData() init { - launch { - blazeRepository.getAdSuggestions(navArgs.productId)?.let { adSuggestions -> - _viewState.value = _viewState.value?.copy( - isLoading = false, - adDetails = _viewState.value?.adDetails!!.copy( - description = adSuggestions.firstOrNull()?.description ?: "", - tagLine = adSuggestions.firstOrNull()?.tagLine ?: "", - ) - ) - } - } + loadSuggestions() } fun onBackPressed() { triggerEvent(MultiLiveEvent.Event.Exit) } - private fun CampaignPreview.toCampaignPreviewUiState(isLoading: Boolean = false) = - CampaignPreviewUiState( - isLoading = isLoading, - adDetails = AdDetailsUi( - productId = productId, - description = aiSuggestions.firstOrNull()?.description ?: "", - tagLine = aiSuggestions.firstOrNull()?.tagLine ?: "", - campaignImageUrl = campaignImageUrl ?: "", - ), - campaignDetails = toCampaignDetailsUi() - ) + fun onEditAdClicked() { + (adDetails.value as? AdDetails)?.let { + triggerEvent( + NavigateToEditAdScreen( + productId = navArgs.productId, + tagLine = it.tagLine, + description = it.description, + campaignImageUrl = it.campaignImageUrl + ) + ) + } + } + + fun onAdUpdated(tagline: String, description: String, campaignImageUrl: String?) { + adDetails.update { + AdDetails( + productId = navArgs.productId, + description = description, + tagLine = tagline, + campaignImageUrl = campaignImageUrl + ) + } + } + + private fun loadSuggestions() { + launch { + blazeRepository.getAdSuggestions(navArgs.productId).let { suggestions -> + adDetails.update { + AdDetails( + productId = navArgs.productId, + description = suggestions?.firstOrNull()?.description ?: "", + tagLine = suggestions?.firstOrNull()?.tagLine ?: "", + campaignImageUrl = campaign.campaignImageUrl + ) + } + } + } + } - private fun CampaignPreview.toCampaignDetailsUi() = - CampaignDetailsUi( + private fun CampaignPreview.toCampaignDetailsUi( + budget: Budget, + languages: List, + devices: List, + locations: List, + interests: List + ) = CampaignDetailsUi( budget = CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget), displayValue = budget.toDisplayValue(), @@ -77,13 +128,13 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_devices), - displayValue = locations.joinToString { it.name } + displayValue = devices.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, onItemSelected = { /* TODO Add devices selection */ }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location), - displayValue = devices.joinToString { it.name } + displayValue = locations.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, onItemSelected = { /* TODO Add location selection */ }, ), @@ -102,7 +153,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) ) - private fun BlazeRepository.Budget.toDisplayValue(): String { + private fun Budget.toDisplayValue(): String { val totalBudgetWithCurrency = currencyFormatter.formatCurrency( totalBudget.toBigDecimal(), currencyCode @@ -115,29 +166,13 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( return "$totalBudgetWithCurrency, $duration" } - fun onEditAdClicked() { - viewState.value?.let { campaignPreviewContent -> - triggerEvent( - NavigateToEditAdScreen( - productId = navArgs.productId, - tagLine = campaignPreviewContent.adDetails.tagLine, - description = campaignPreviewContent.adDetails.description, - campaignImageUrl = campaignPreviewContent.adDetails.campaignImageUrl - ) - ) - } - } - - fun onAdUpdated(tagline: String, description: String, campaignImageUrl: String?) { - _viewState.value = viewState.value?.copy( - adDetails = AdDetailsUi( - productId = navArgs.productId, - description = description, - tagLine = tagline, - campaignImageUrl = campaignImageUrl - ) - ) - } + private fun getDefaultBudget() = Budget( + totalBudget = BlazeRepository.DEFAULT_CAMPAIGN_TOTAL_BUDGET, + spentBudget = 0f, + currencyCode = BlazeRepository.BLAZE_DEFAULT_CURRENCY_CODE, + durationInDays = BlazeRepository.DEFAULT_CAMPAIGN_DURATION, + startDate = Date().apply { time += BlazeRepository.ONE_DAY_IN_MILLIS }, // By default start tomorrow + ) data class NavigateToEditAdScreen( val productId: Long, @@ -147,17 +182,22 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) : MultiLiveEvent.Event() data class CampaignPreviewUiState( - val isLoading: Boolean = false, val adDetails: AdDetailsUi, val campaignDetails: CampaignDetailsUi, ) - data class AdDetailsUi( - val productId: Long, - val description: String, - val tagLine: String, - val campaignImageUrl: String?, - ) + sealed interface AdDetailsUi : Parcelable { + @Parcelize + object Loading : AdDetailsUi + + @Parcelize + data class AdDetails( + val productId: Long, + val description: String, + val tagLine: String, + val campaignImageUrl: String?, + ) : AdDetailsUi + } data class CampaignDetailsUi( val budget: CampaignDetailItemUi, From 60a330a374f3b33c7abd21ab1ef848c8c2bd73e6 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 26 Jan 2024 22:21:47 +0100 Subject: [PATCH 063/160] Update the screen based on the ViewModel changes --- .../BlazeCampaignCreationPreviewScreen.kt | 20 +++--- .../BlazeCampaignCreationPreviewViewModel.kt | 70 +++++++++---------- 2 files changed, 45 insertions(+), 45 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 f97c7fd0634..83b76d497a3 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 @@ -42,7 +42,8 @@ import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest import com.woocommerce.android.R -import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi +import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails +import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading 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 @@ -87,10 +88,10 @@ private fun BlazeCampaignCreationPreviewScreen( .background(color = MaterialTheme.colors.surface) ) { - when { - previewState.isLoading -> AdDetailsLoading() + when (previewState.adDetails) { + is Loading -> AdDetailsLoading() else -> AdDetailsHeader( - state = previewState, + previewState.adDetails as AdDetails, onEditAdClicked = onEditAdClicked ) } @@ -110,7 +111,7 @@ private fun BlazeCampaignCreationPreviewScreen( .padding(bottom = 8.dp), text = stringResource(id = R.string.blaze_campaign_preview_details_confirm_details_button), onClick = { /*TODO*/ }, - enabled = !previewState.isLoading + enabled = previewState.adDetails != Loading ) } } @@ -177,12 +178,12 @@ private fun AdDetailsLoading( @Composable fun AdDetailsHeader( - state: CampaignPreviewUiState, + adDetails: AdDetails, onEditAdClicked: () -> Unit, modifier: Modifier = Modifier, ) { CampaignHeader( - adDetails = state.adDetails, + adDetails = adDetails, onEditAdClicked = onEditAdClicked, modifier = modifier .fillMaxWidth() @@ -194,7 +195,7 @@ fun AdDetailsHeader( @Composable fun CampaignHeader( - adDetails: AdDetailsUi, + adDetails: AdDetails, onEditAdClicked: () -> Unit, modifier: Modifier = Modifier ) { @@ -366,8 +367,7 @@ private fun CampaignPropertyItem( fun CampaignScreenPreview() { BlazeCampaignCreationPreviewScreen( CampaignPreviewUiState( - isLoading = false, - adDetails = AdDetailsUi( + adDetails = AdDetails( productId = 123, description = "Get the latest white t-shirts", tagLine = "From 45.00 USD", 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 306ecc87184..7f1f8503b8a 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 @@ -114,44 +114,44 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( locations: List, interests: List ) = CampaignDetailsUi( - budget = CampaignDetailItemUi( - displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget), - displayValue = budget.toDisplayValue(), - onItemSelected = { triggerEvent(NavigateToBudgetScreen) }, + budget = CampaignDetailItemUi( + displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget), + displayValue = budget.toDisplayValue(), + onItemSelected = { triggerEvent(NavigateToBudgetScreen) }, + ), + targetDetails = listOf( + CampaignDetailItemUi( + displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_language), + displayValue = languages.joinToString { it.name } + .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, + onItemSelected = { /* TODO Add language selection */ }, ), - targetDetails = listOf( - CampaignDetailItemUi( - displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_language), - displayValue = languages.joinToString { it.name } - .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add language selection */ }, - ), - CampaignDetailItemUi( - displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_devices), - displayValue = devices.joinToString { it.name } - .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add devices selection */ }, - ), - CampaignDetailItemUi( - displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location), - displayValue = locations.joinToString { it.name } - .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add location selection */ }, - ), - CampaignDetailItemUi( - displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_interests), - displayValue = interests.joinToString { it.description } - .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add interests selection */ }, - ), + CampaignDetailItemUi( + displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_devices), + displayValue = devices.joinToString { it.name } + .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, + onItemSelected = { /* TODO Add devices selection */ }, ), - destinationUrl = CampaignDetailItemUi( - displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_destination_url), - displayValue = targetUrl, - onItemSelected = { /* TODO Add destination url selection */ }, - maxLinesValue = 1, - ) + CampaignDetailItemUi( + displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location), + displayValue = locations.joinToString { it.name } + .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, + onItemSelected = { /* TODO Add location selection */ }, + ), + CampaignDetailItemUi( + displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_interests), + displayValue = interests.joinToString { it.description } + .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, + onItemSelected = { /* TODO Add interests selection */ }, + ), + ), + destinationUrl = CampaignDetailItemUi( + displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_destination_url), + displayValue = targetUrl, + onItemSelected = { /* TODO Add destination url selection */ }, + maxLinesValue = 1, ) + ) private fun Budget.toDisplayValue(): String { val totalBudgetWithCurrency = currencyFormatter.formatCurrency( From c80f22afd6eb33ca88b4ddcffabfd9e16d1e45a6 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Fri, 26 Jan 2024 22:30:51 +0000 Subject: [PATCH 064/160] Update translations --- .../src/main/res/values-ar/strings.xml | 24 +- .../src/main/res/values-de/strings.xml | 24 +- .../src/main/res/values-es/strings.xml | 24 +- .../src/main/res/values-fr/strings.xml | 24 +- .../src/main/res/values-he/strings.xml | 24 +- .../src/main/res/values-id/strings.xml | 24 +- .../src/main/res/values-it/strings.xml | 24 +- .../src/main/res/values-ja/strings.xml | 556 ++--- .../src/main/res/values-ko/strings.xml | 24 +- .../src/main/res/values-nl/strings.xml | 24 +- .../src/main/res/values-pt-rBR/strings.xml | 24 +- .../src/main/res/values-ru/strings.xml | 24 +- .../src/main/res/values-sv/strings.xml | 1868 +++++++++-------- .../src/main/res/values-tr/strings.xml | 24 +- .../src/main/res/values-zh-rCN/strings.xml | 24 +- .../src/main/res/values-zh-rTW/strings.xml | 24 +- 16 files changed, 1553 insertions(+), 1207 deletions(-) diff --git a/WooCommerce/src/main/res/values-ar/strings.xml b/WooCommerce/src/main/res/values-ar/strings.xml index f464c841528..8a954ca2cea 100644 --- a/WooCommerce/src/main/res/values-ar/strings.xml +++ b/WooCommerce/src/main/res/values-ar/strings.xml @@ -1,12 +1,33 @@ + تأكيد التفاصيل + وجهة الإعلان + مجالات الاهتمام + الموقع + الأجهزة + اللغة + الميزانية + تفاصيل + التسوق الآن + تحرير الإعلان + معاينة تعطيل المزامنة + تحديد المنتج + تحديد المنتج %s + <b>الانتقال إلى الوضع المباشر:</b> راقب كيف يبدأ عرضك الترويجي وتتبَّع نجاحه. + <b>مراجعة سريعة:</b> أرسل إعلانك لإجراء فحص سريع من المشرف. + <b>تعيين ميزانيتك:</b> حدد إنفاقك ومدة الحملة. + <b>تخصيص العناصر المستهدفة:</b> حدد الجمهور حسب الموقع أو مجالات الاهتمامات، وشاهد مدى الوصول المحتمل. + <b>اختيار منتج:</b> اختر ما تريد الترويج له باستخدام Blaze. + إدارة المخزون + لم تتم إدارة المخزون + التعرُّف على كيفية عمل Blaze بدء الحملة الخاصة بك إعلاناتك على ملايين المواقع داخل شبكات ووردبريس.كوم وTumblr. الوصول إلى عدد ضخم من الجمهور @@ -126,6 +147,7 @@ Language: ar يتراوح بين ⁦%1$s⁩ و⁦%2$s⁩ من العناصر %d من العناصر %d من العناصر + تغيير كمية المنتج من %1$.2f إلى %2$.2f حفظ التكوين التكوين المنتج %s diff --git a/WooCommerce/src/main/res/values-de/strings.xml b/WooCommerce/src/main/res/values-de/strings.xml index e8bd93fff0b..4b15994a505 100644 --- a/WooCommerce/src/main/res/values-de/strings.xml +++ b/WooCommerce/src/main/res/values-de/strings.xml @@ -1,12 +1,33 @@ + Details bestätigen + Ziel der Werbung + Interessen + Ort + Geräte + Sprache + Budget + Details + Jetzt einkaufen + Werbung bearbeiten + Vorschau Deaktiviert + Produktauswahl + Produkt %s auswählen + <b>Go-live:</b> Beobachte, wie deine Werbeaktion startet, und verfolge, wie erfolgreich sie ist. + <b>Schnelle Überprüfung:</b> Reiche deine Werbung zur schnellen Überprüfung durch den Moderator ein. + <b>Festlegen des Budgets:</b> Lege deine Ausgaben und die Länge deiner Kampagne fest. + <b>Anpassen der Zielgruppe:</b> Wähle die Zielgruppe nach Ort oder Interessen aus und sieh dir die potenzielle Reichweite an. + <b>Auswahl des Produkts:</b> Wähle, was du mit Blaze bewerben möchtest. + Bestand verwalten + Bestand nicht verwaltet + So funktioniert Blaze Deine Kampagne starten Deine Werbung auf Millionen Websites in den WordPress.com- und Tumblr-Netzwerken. Erziele eine große Reichweite @@ -126,6 +147,7 @@ Language: de zwischen %1$s und %2$s Artikeln %d Artikel %d Artikel + Ändere die Produktmenge von %1$.2f zu %2$.2f Konfiguration speichern Konfiguration Produkt %s diff --git a/WooCommerce/src/main/res/values-es/strings.xml b/WooCommerce/src/main/res/values-es/strings.xml index 07962085748..33213c6bd48 100644 --- a/WooCommerce/src/main/res/values-es/strings.xml +++ b/WooCommerce/src/main/res/values-es/strings.xml @@ -1,12 +1,33 @@ + Confirmar detalles + Destino del anuncio + Intereses + Ubicación + Dispositivos + Idioma + Presupuesto + Detalles + Comprar ahora + Editar anuncio + Vista previa Desactivado + Elección de producto + Elegir el producto %s + <b>Publica contenido:</b> observa cómo comienza tu promoción y haz un seguimiento de su progreso. + <b>Reseña rápida:</b> envía tu anuncio para que lo compruebe rápidamente un moderador. + <b>Define tu presupuesto:</b> decide el gasto y la duración de la campaña. + <b>Segmentación personalizada:</b> elige la audiencia por ubicación o intereses, y observa el alcance potencial. + <b>Elige un producto:</b> elige qué quieres promocionar con Blaze. + Gestionar inventario + Inventario no gestionado + Echa un vistazo a cómo funciona Blaze Empieza tu campaña Tus anuncios en millones de sitios de la red de WordPress.com y Tumblr. Accede a una gran audiencia @@ -126,6 +147,7 @@ Language: es entre %1$s y %2$s elementos %d elementos %d elemento + Cambiar la cantidad de producto de %1$.2f a %2$.2f Guardar configuración Configuración Producto %s diff --git a/WooCommerce/src/main/res/values-fr/strings.xml b/WooCommerce/src/main/res/values-fr/strings.xml index 7483dd3ddc2..6b1bb70ba2f 100644 --- a/WooCommerce/src/main/res/values-fr/strings.xml +++ b/WooCommerce/src/main/res/values-fr/strings.xml @@ -1,12 +1,33 @@ + Confirmer les détails + Destination de la publicité + Intérêts + Emplacement + Appareils + Langue + Budget + Détails + Acheter maintenant + Modifier la publicité + Aperçu Désactivé + Sélection de produits + Sélectionner le produit %s + <b>Mise en ligne :</b> observez les débuts de votre campagne promotionnelle et suivez son succès. + <b>Examen rapide :</b> envoyez votre publicité pour une vérification rapide par un modérateur. + <b>Fixer votre budget :</b> établissez vos dépenses et la durée de votre campagne. + <b>Personnaliser la cible :</b> sélectionnez le public par emplacement ou intérêts et découvrez la portée potentielle. + <b>Choisir un produit :</b> choisissez ce que vous voulez promouvoir avec Blaze. + Gérer le stock + Stock non géré + Voir comment fonctionne Blaze Lancer votre campagne Vos publicités sur des millions de sites des réseaux WordPress.com et Tumblr. Accéder à un large public @@ -126,6 +147,7 @@ Language: fr entre %1$s et %2$s éléments %d éléments %d élément + Modifier la quantité de produit de %1$.2f à %2$.2f Enregistrer la configuration Configuration Produit %s diff --git a/WooCommerce/src/main/res/values-he/strings.xml b/WooCommerce/src/main/res/values-he/strings.xml index 3577d0a63dc..c740a8de4d4 100644 --- a/WooCommerce/src/main/res/values-he/strings.xml +++ b/WooCommerce/src/main/res/values-he/strings.xml @@ -1,12 +1,33 @@ + לאשר את הפרטים + היעד של הפרסומת + תחומי עניין + מיקום + מכשירים + שפה + תקציב + פרטים + לרכישה כעת + לערוך את הפרסומת + תצוגה מקדימה נָכֶה + בחירת מוצרים + יש לבחור מוצר %s + <b>לפרסם באינטרנט:</b> לראות את הביצועים כאשר מבצע הקידום מתחיל ולעקוב אחר הצלחתו. + <b>סקירה מהירה:</b> לשלוח את הפרסומת לבדיקה מהירה של המנהלים. + <b>להגדיר את התקציב שלך:</b> להחליט לגבי ההוצאה ומשך הקמפיין. + <b>להתאים אישית את מיקוד הקהל:</b> לבחור קהל לפי מיקום או תחום עניין ולראות את הפוטנציאל לחשיפה. + <b>לבחור מוצר:</b> לבחור מה לקדם עם Blaze. + ניהול מלאי + המלאי לא מנוהל + ללמוד איך Blaze פועל להתחיל קמפיין המודעות שלך יוצגו במיליוני אתרים ברשת WordPress.com וברשת Tumblr. לגשת לקהל עצום @@ -126,6 +147,7 @@ Language: he_IL ⁦%1$s⁩ עד ⁦%2$s⁩ פריטים %d פריטים פריט %d + לשנות את כמות המוצר מ-%1$.2f ל-%2$.2f לשמור הגדרות תצורה הגדרות תצורה מוצר %s diff --git a/WooCommerce/src/main/res/values-id/strings.xml b/WooCommerce/src/main/res/values-id/strings.xml index e959a4bd838..27fa7e4d6a0 100644 --- a/WooCommerce/src/main/res/values-id/strings.xml +++ b/WooCommerce/src/main/res/values-id/strings.xml @@ -1,12 +1,33 @@ + Konfirmasi Detail + Tujuan iklan + Minat + Lokasi + Perangkat + Bahasa + Anggaran + Detail + Belanja sekarang + Edit iklan + Pratinjau Non-aktif + Pilihan produk + Pilih produk %s + <b>Luncurkan:</b> Pantau progres dan keberhasilan promosi Anda. + <b>Tinjau cepat:</b> Kirimkan iklan untuk pemeriksaan cepat oleh moderator. + <b>Tetapkan anggaran:</b> Tetapkan biaya dan durasi kampanye. + <b>Sesuaikan sasaran:</b> Pilih audiens berdasarkan lokasi atau minat, dan lihat potensi jangkauannya. + <b>Pilih produk:</b> Pilih produk yang ingin dipromosikan dengan Blaze. + Kelola Stok + Stok tidak dikelola + Ketahui cara kerja Blaze Buat kampanye Anda Jutaan situs dalam jaringan WordPress.com dan Tumblr jadi ruang iklan Anda. Jangkau audiens yang besar @@ -126,6 +147,7 @@ Language: id antara %1$s dan %2$s item %d item %d item + Ubah jumlah produk dari %1$.2f menjadi %2$.2f Simpan konfigurasi Konfigurasi Produk %s diff --git a/WooCommerce/src/main/res/values-it/strings.xml b/WooCommerce/src/main/res/values-it/strings.xml index ef85c27e620..26aee814737 100644 --- a/WooCommerce/src/main/res/values-it/strings.xml +++ b/WooCommerce/src/main/res/values-it/strings.xml @@ -1,12 +1,33 @@ + Conferma dettagli + Destinazione annuncio + Interessi + Posizione + Dispositivi + Lingua + Budget + Dettagli + Acquista ora + Modifica annuncio + Anteprima Disabilitato + Selezione prodotto + Seleziona prodotto %s + <b>Pubblica</b>: guarda come inizia la tua promozione e tieni traccia del suo successo. + <b>Breve riassunto:</b> invia il tuo annuncio per un controllo veloce da parte del moderatore. + <b>Imposta il tuo budget:</b> decidi i costi e la durata della tua campagna. + <b>Personalizza il targeting:</b> seleziona il pubblico in base alla posizione o agli interessi e visualizza la portata potenziale. + <b>Scegli un prodotto:</b> scegli cosa promuovere con Blaze. + Gestisci magazzino + Magazzino non gestito + Scopri come funziona Blaze Avvia la tua campagna I tuoi annunci su milioni di siti web nelle reti WordPress.com e Tumblr. Raggiungi un ampio pubblico @@ -126,6 +147,7 @@ Language: it tra %1$s e %2$s elementi %d elementi %d elemento + Cambia la quantità del prodotto da %1$.2f a %2$.2f Salva configurazione Configurazione Prodotto %s diff --git a/WooCommerce/src/main/res/values-ja/strings.xml b/WooCommerce/src/main/res/values-ja/strings.xml index 876dd9138bb..84c872384f2 100644 --- a/WooCommerce/src/main/res/values-ja/strings.xml +++ b/WooCommerce/src/main/res/values-ja/strings.xml @@ -1,18 +1,39 @@ + 詳細を確認 + 広告の掲載先 + 興味 + 場所 + デバイス + 言語 + 予算 + 詳細 + 今すぐ購入 + 広告を編集 + プレビュー 無効 + 商品の選択 + 商品を選択 %s + <b>実行:</b> プロモーションの開始を監視し、その成果を追跡します。 + <b>クイックレビュー:</b> 広告を送信してモデレーターによる迅速なチェックを受けてください。 + <b>予算を設定:</b> 費用とキャンペーン期間を決めます。 + <b>ターゲティングをカスタマイズ:</b> 場所や興味に基づいて閲覧者を選択し、潜在的なリーチを確認します。 + <b>商品を選択:</b> Blaze で宣伝するものを選択します。 + 在庫を管理 + 在庫が管理されていません キャンペーンを開始 広告は WordPress.com と Tumblr ネットワーク内の何百万ものサイトに表示されます。 多数の読者にアクセス 「ツールを使えば、関心を持っている買い物客の目に入る場所に商品を表示できます」 世界規模のリーチをシンプルに 数分で広告を開始 – 経験も多額の予算も必要ありません。わずか5ドルから始められます。 + Blaze のしくみを知る すばやいスタートで大きなインパクト 当社のツールは、マーチャントがシンプルですばやい広告キャンペーン設定でトラフィックを最大化できるよう支援するために設計されています。 宣伝 @@ -114,7 +135,6 @@ Language: ja_JP 利用可能な資金は毎日自動で入金されます。 資金は%d日間保留された後に利用可能になります。 バリエーションを選択する - バリエーションを選択 「%1$s」-> %2$s バリエーションを選択してください %1$s個のアイテムを選択済み @@ -126,6 +146,8 @@ Language: ja_JP %1$s~%2$s個のアイテム %d個のアイテム %d個のアイテム + バリエーションを選択 + 商品数量を%1$.2fから%2$.2fに変更する 設定を保存する 設定 商品 %s @@ -159,12 +181,12 @@ Language: ja_JP 予定 デポジットの概要を折りたたむ / 展開する 資金の受け取りタイミングについて詳しくはこちら - 利用可能な資金は毎月 %s 上で自動で入金されます。 - 資金は%d日間保留された後に利用可能になります。 保留中の資金 利用可能な資金 商品 + 利用可能な資金は毎月 %s 上で自動で入金されます。 + 資金は%d日間保留された後に利用可能になります。 支払総額 メールアドレスまたはユーザー名 カスタム金額の注文を作成できません @@ -262,8 +284,8 @@ Language: ja_JP ご記入ください 商品の概要とその特徴的な点を教えてください。 AI が魅力的なタイトルを生成します - 商品名 すべての商品レビューを表示するには、未読フィルターを無効にしてみてください + 商品名 未読の商品レビューはありません トーンが選択されました 説得力がある @@ -310,8 +332,8 @@ Language: ja_JP 税率を設定する 有効化 新しい税率を設定 - WooPayments セットアップ + WooPayments 管理画面で税率を編集 これにより顧客の住所が選択した税率の場所に変更されます。 税率情報ダイアログを開くボタン @@ -360,8 +382,8 @@ Language: ja_JP 注文合計 計算されたパーセンテージ 算出額 - ストア名 ストア名をカスタマイズすると、ストアの検索エンジンの最適化にも役立ちます。 + ストア名 ストアに名前を付ける その他 幅広いプラグインや拡張機能へのアクセス @@ -382,8 +404,8 @@ Language: ja_JP もう少しです ! ストアを\n構築中です NFC を有効化 少量提供パッケージ (ラベル必須) - 数量限定の陸送便パッケージ - エアゾール、スプレー消毒剤、スプレー塗料、ヘアスプレー、プロパン、ブタン、洗浄剤など - フレグランス、マニキュア、除光液、溶剤、手指消毒剤、消毒用アルコール、エタノールベース製品など - その他数量限定の陸送物質 (化粧品、洗浄剤、塗料など) ライターパッケージ - 認可されたライター + 数量限定の陸送便パッケージ - エアゾール、スプレー消毒剤、スプレー塗料、ヘアスプレー、プロパン、ブタン、洗浄剤など - フレグランス、マニキュア、除光液、溶剤、手指消毒剤、消毒用アルコール、エタノールベース製品など - その他数量限定の陸送物質 (化粧品、洗浄剤、塗料など) ID8000日用品パッケージ - 航空便適合 ID8000日用品 (不燃性エアゾール、可燃性液体、有毒物質、その他の危険物) 陸送便専用危険物 (リストに記載されていないが、陸送のみに制限される品目) 例外数量提供パッケージ (例: 少量の可燃性液体、腐食性物質、毒性物質、環境に有害な物質 - ラベル必須) @@ -436,8 +458,8 @@ Language: ja_JP ストアでは何を\n販売する予定ですか ? ストアについて 15日間のお試し期間を始めましょう。クレジットカードは不要です。 最初の売上から数百万に至るまで、Woo で繁盛する340万店舗になりましょう。 - ともに成長する eコマースプラットフォーム バリエーションのある定期購入 + ともに成長する eコマースプラットフォーム クーポンを削除 誰もがお得な情報を求めています クーポンはまだ作成されていません。 クーポンを作成してこの注文に適用します。 @@ -486,9 +508,9 @@ Language: ja_JP 🌟ビジネスを推し進めていきましょう ! メールアドレスなし 名前 - 既存の顧客を検索する、または 最終更新 %s (30分ごとに更新) 最終更新 %s + 既存の顧客を検索する、または Android の Tap To Pay による支払いの受け取りについて<a href=\'\'>詳しくはこちら</a>をご覧ください 支払いを受け取る 価格が指定されていない商品は追加できません @@ -558,12 +580,12 @@ Language: ja_JP 共有メッセージを生成できません。 もう一度お試しください。 AI 機能について詳しく学ぶ オプションメッセージを追加する - 入力しています… AI で執筆 Blaze を使って商品を宣伝する Blaze AI によるコンテンツ生成を利用できます Blaze を使って宣伝する + 入力しています… 商品を共有する おめでとうございます ! 新しいストアの準備完了にまた一歩近づいています。 最初の商品が作成されました 🎉 @@ -596,7 +618,6 @@ Language: ja_JP プライバシー ストアに関して当社が収集しているデータと、このデータ共有をコントロールするオプションについて、詳細をご確認ください。 使用状況の追跡 - woo.com ユーザー向けのその他のプライバシーオプションを利用できます。 こちらをクリックして詳細をご確認ください。 ウェブオプション その他のプライバシーオプション プライバシー設定の更新でエラーが発生しました @@ -609,21 +630,22 @@ Language: ja_JP 直接バリエーション商品を追加できません。 特定のバリエーションを選択してください スキャンに失敗しました。 後でもう一度お試しください。 SKU「%s」の商品が見つかりません。 注文に追加できません + woo.com ユーザー向けのその他のプライバシーオプションを利用できます。 こちらをクリックして詳細をご確認ください。 スキャンに失敗しました。 後でもう一度お試しください バーコードのスキャン 欧州連合 (EU) の関税法規則に従う国への発送は、すべての品目について明確な説明が求められるようになりました。 たとえば衣類を送る場合、どのようなタイプの衣類であるか (例: 男性用シャツ、女児用ベスト、男児用上着など) を明記しなければ認められません。 これを怠ると、配送が税関で遅延または中断されることがあります。 サポートに連絡 + アカウントを閉鎖しています… 有効になっているストアがあるため、このアカウントを閉鎖できません。 アカウントの閉鎖を試行中にエラーが発生しました。 アカウントを閉鎖できませんでした - アカウントを閉鎖しています… アカウントを完全に閉鎖 - 確認するには、閉鎖する前にユーザー名をもう一度入力してください アカウントの閉鎖を確認 アカウントを閉鎖 + 注文からクーポンを削除 + 確認するには、閉鎖する前にユーザー名をもう一度入力してください QR コードをスキャンして指示に従ってください スキャンして支払う - 注文からクーポンを削除 クーポン (%1$s) -%1$s クーポンを追加 @@ -632,9 +654,9 @@ Language: ja_JP スキャナーで商品を追加 閉じる さらに詳しく - 欧州連合 (EU) の関税法規則に従う国に発送する場合、すべての品目について明確で具体的な説明を記載する必要があります。 これを怠ると、配送が税関で遅延または中断されることがあります。 常に最新の情報を入手して、ストアのセキュリティ向上を図りましょう。 Jetpack を今すぐお試しください。 注文通知などを受け取る + 欧州連合 (EU) の関税法規則に従う国に発送する場合、すべての品目について明確で具体的な説明を記載する必要があります。 これを怠ると、配送が税関で遅延または中断されることがあります。 ストア設定リストを表示または非表示 ストア設定リスト 必要に応じて「メニュー」>「設定」>「ストア」から再度復元できます。 @@ -661,9 +683,9 @@ Language: ja_JP 住宅、家具、庭 食品・飲料 健康・美容 + お使いのデバイスに届く新しい注文やレビューなどのプッシュ通知を送信するには権限が必要です。 扉を開いています 明かりを点けています - お使いのデバイスに届く新しい注文やレビューなどのプッシュ通知を送信するには権限が必要です。 通知 混合商品 端末メディアライブラリ @@ -703,7 +725,6 @@ Language: ja_JP プレミアムテーマ クレジットカードは不要です。 無料でお試し - 無料で14日間お試しください。 WooCommerce を使用し WordPress.com でホスティングされるオンラインストアの構築と拡大に必要なすべてを提供します。 数日で公開、長期的に成長 商品 @@ -711,6 +732,7 @@ Language: ja_JP 人気 権限グループと権限の詳細 権限グループでは Jetpack のインストールが許可されていないようです。 \nサポートをご希望の場合は管理者にお問い合わせください。 + 無料で14日間お試しください。 Tap to Pay を試す 無料お試し 登録手数料 @@ -747,14 +769,14 @@ Language: ja_JP サブスクリプション カードリーダーは、デビットカードとクレジットカードのタップ決済、チップ決済、スワイプ決済に対応しています。 お使いのスマートフォンから非接触型決済を安全に直接受け付けることができます。 - スマートフォンを使用してカードによる支払いを\n受け付けることができます。 今すぐお試しください。 - フィードバックを共有 アプリケーションパスワードの作成が承認されていないため、ログインできません。 サイトを取得中… + スマートフォンを使用してカードによる支払いを\n受け付けることができます。 今すぐお試しください。 + フィードバックを共有 + 読込中… ウェブサイトの取得中にエラーが発生しました WP 管理画面ページでもう一度お試しください ログイン - 読込中… %sが終了しました サブスクリプションが終了したため、ご利用いただける機能に制限がかかります。 %1$d日 @@ -770,7 +792,6 @@ Language: ja_JP プラン詳細を取得中にエラーが発生しました %1$s を購読しています。 %2$s まですべての機能をご利用いただけます。 お試し期間が終了したため、ご利用いただける機能に制限がかかります。 今すぐ %1$s を購読しましょう。 - %1$d日間の無料お試しが開始しました。 お試し期間は%2$s日後に終了します。 アップグレードして新しい機能を利用し、ストアの運営を継続しましょう。 定期購入ステータス アップグレード トラブルシューティング @@ -778,7 +799,6 @@ Language: ja_JP 今すぐアップグレード 購読に関する問題を報告 今すぐアップグレード - お試し期間はあと%1$sです。 お試し期間終了 お試し期間が終了しました。 予期しないエラーが発生しました。 @@ -794,6 +814,8 @@ Language: ja_JP ストアを公開 ストアを公開するには、プランのアップグレードが必要です。 <u>アップグレード</u> ドメインを検索 + %1$d日間の無料お試しが開始しました。 お試し期間は%2$s日後に終了します。 アップグレードして新しい機能を利用し、ストアの運営を継続しましょう。 + お試し期間はあと%1$sです。 ステータスコード%1$sのためログインに失敗しました ストアの管理 URL を識別できないためログインできません ストアのログイン URL を識別できないためログインできません @@ -842,31 +864,31 @@ Language: ja_JP Jetpack ステータスを取得しています エラーが発生しました。 後でもう一度お試しください。 支払いを試す + ドメイン名を登録しています… + 国を選択 + 都道府県を選択 スマートフォンでカードでの\n支払いを受け取る Tap To Pay アクション ドメイン登録中にエラーが発生しました - 都道府県を選択 - 国を選択 - ドメイン名を登録しています… - ドメインを登録 - 郵便番号 - 状態 (使用不可) - 都道府県 - 市区町村 - 住所2 - 住所 - - 国コード 電話 - 組織 (任意) + 国コード + + 住所 + 住所2 + 市区町村 + 都道府県 + 状態 (使用不可) + 郵便番号 + ドメインを登録 WordPress.com 連絡先情報にはデータが事前に\n入力されています。このドメインに使用する情報として正しいことをご確認ください。 - ドメインの連絡先情報 - 公開として登録 - プライバシー保護付きで非公開として登録 - 有効な%sを入力してください - このドメインを登録することで、当社の%1$s利用規約%2$sに合意したことになります + 組織 (任意) ドメイン所有者は、すべてのドメインのパブリックデータベースで連絡先情報を共有する必要があります。\nプライバシー保護として、お客様に関する情報の代わりに弊社に関する情報を公開し、お客様には個別でコミュニケーションを転送します。 + このドメインを登録することで、当社の%1$s利用規約%2$sに合意したことになります + 有効な%sを入力してください + プライバシー保護付きで非公開として登録 + 公開として登録 + ドメインの連絡先情報 プライバシー保護 ストア管理者のみがドメイン設定にアクセスできます またはマジックリンクを使用して続行します @@ -924,7 +946,6 @@ Language: ja_JP すでに販売しているが、オンラインではない ビジネスを始めたばかりである 服飾雑貨 - その他 この検索の結果はありません カテゴリーを入力してください %1$sのコピー @@ -933,28 +954,29 @@ Language: ja_JP 商品を複製できません 複製 お支払いの準備をしています + その他 長くはかかりません 内蔵リーダーを準備しています… 内蔵リーダーの準備が整いました - カードリーダー タップして支払う コンバージョン率 セッション この期間にはセッションがありません 次との比較: ドメイン + カードリーダー アプリケーションパスワードとは 現在の位置 この情報を使用して、支払い、配送、税金を設定します。 複数選択できます。 現在どのプラットフォームを\n使用していますか ? - あなたのビジネスに相応しい\nカテゴリーを選択してください。 サイト %1$s でアプリケーションパスワード機能が無効になっているようです。\n WooCommerce アプリを使用するには、アプリケーションパスワードを有効にしてください。 インストールページを開く + あなたのビジネスに相応しい\nカテゴリーを選択してください。 スキャンされた QR コードは WooCommerce コードではありません - 現在お住まいの国ではモバイルアプリでストアを作成することはできません。 ストアを作成できません + 現在お住まいの国ではモバイルアプリでストアを作成することはできません。 連携を維持してください。 ストア作成の準備を進めています 返信の送信時にエラーが発生しました @@ -982,10 +1004,10 @@ Language: ja_JP 現在、作成がサポートされているのは、最大%1$d件のバリエーションです。 この商品のバリエーションを生成すると、%2$d件のバリエーションが作成されます。 生成の上限を超えました 属性のすべての組み合わせについてバリエーションを作成します。 - すべてのバリエーションを生成 新しいバリエーションを1つ作成します。 バリエーションのある商品の属性を手動で設定します。 新しいバリエーションを追加 バリエーションを追加 + すべてのバリエーションを生成 連携せずに終了 連携を続ける ストアにアクセスするには、もう一度連携してみてください。 @@ -997,7 +1019,6 @@ Language: ja_JP 有効化を再試行 インストールを再試行 サポートを受ける - もう一度やり直してみてください。エラーが続く場合は、サポートにお問い合わせください。 サイトとの通信でエラーが発生しました。 このストアのプラグインを管理する権限がありません Jetpack との連携を認証する際にエラーが発生しました @@ -1021,6 +1042,7 @@ Language: ja_JP Jetpack をインストールしています Jetpack を連携するには、ストアのログイン情報で <b>%1$s</b> にログインしてください。 Jetpack をインストールするには、ストアのログイン情報で <b>%1$s</b> にログインしてください。 + もう一度やり直してみてください。エラーが続く場合は、サポートにお問い合わせください。 ストアのログイン情報を用意してください。 このアプリでアクセスするには、ストアを Jetpack に連携してください。 このアプリでストアにアクセスするには、無料の Jetpack プラグインをインストールしてください。 @@ -1109,12 +1131,12 @@ Language: ja_JP 別のアドレスを試す カスタムの日付の範囲 カスタム - WordPress.com とは何ですか ? - 新しいアカウントの作成中 パスワードを選択 あなたのメールアドレス + 新しいアカウントの作成中 数分で\n新規登録 「Jetpack に接続」ボタンをタップすると、利用規約および WordPress.com と詳細情報を共有することに同意したものとみなされます。<a href=\'terms\'></a><a href=\'sync\'></a> + WordPress.com とは何ですか ? シミュレートされたカードリーダーを有効にする アプリを使用するには、サイトの所有者に連絡し、ショップ運営者または管理者としてサイトへの招待を受けてください。 WordPress.com サイトと連携中 @@ -1155,44 +1177,43 @@ Language: ja_JP 初めて WooCommerce をお使いになる場合 エラーが生じました。サポートにお問い合わせください サイトアドレスを入力 - ログインリンクをメールで取得 パスワードを忘れた場合 + ログインリンクをメールで取得 「オフラインでの支払い」の設定が完了していないようです。 <a href=\'\'>セットアップを続行</a> - 支払い - 分かりました。 - 「オフラインでの支払い」やその他の機能に簡単に素早くアクセスできるようになりました + WC Admin + ストアアドレスでログイン + その他のサイト 「メニュー」タブの「支払い」 + 「オフラインでの支払い」やその他の機能に簡単に素早くアクセスできるようになりました + 分かりました。 + 支払い メールアドレス は WordPress.com アカウントで使用されていません - その他のサイト - ストアアドレスでログイン - WC Admin - マジックリンクを次のアドレスに送信しました: - この端末でメールアドレスを確認してください。 - パスワードを使用してログイン - マジックリンクでログイン - マジックリンクをメールアドレスに送信しました。 ログインするには、メールに記載されているリンクをタップしてください。 サイトのログイン情報でログイン - アップセルとクロスセルを追加して、顧客に有益で関連性の高い商品のおすすめを提案します - リンク付き商品で売り上げを伸ばす - カードリーダーを使用すれば、20分で直接販売を開始できます。 - 注文番号%1$dの更新中にエラーが発生しました - 注文番号%1$dが完了としてマークされました - マーク\n完了 - WooCommerce をインストール - %1$s は WooCommerce サイトではないようです。 - 複数のストアを切り替える - 注文を管理する - 商品を作成または更新する - アナリティクスを確認する - ストアを立ち上げようとしています - 探っています - WooCommerce を使うのはなぜですか ? - ヒント + マジックリンクをメールアドレスに送信しました。 ログインするには、メールに記載されているリンクをタップしてください。 + マジックリンクでログイン + パスワードを使用してログイン + この端末でメールアドレスを確認してください。 + マジックリンクを次のアドレスに送信しました: 今すぐ設定 + ヒント + WooCommerce を使うのはなぜですか ? + 探っています + ストアを立ち上げようとしています + アナリティクスを確認する + 商品を作成または更新する + 注文を管理する + 複数のストアを切り替える + %1$s は WooCommerce サイトではないようです。 + WooCommerce をインストール + マーク\n完了 + 注文番号%1$dが完了としてマークされました + 注文番号%1$dの更新中にエラーが発生しました + カードリーダーを使用すれば、20分で直接販売を開始できます。 + リンク付き商品で売り上げを伸ばす + アップセルとクロスセルを追加して、顧客に有益で関連性の高い商品のおすすめを提案します ここからスタート。 WordPress.com にログイン サポートに問い合わせる - WordPress.com アカウントでログイン サポートを利用する ログインで問題がある場合 SKU @@ -1214,18 +1235,19 @@ Language: ja_JP スピーディかつ簡単に管理できます ビジネスに欠かせないものであることはわかっています 初めて WooCommerce をお使いになる場合 + WordPress.com アカウントでログイン WooCommerce ストアに $50の新しい注文があります 新しい注文があります。🎉 詳細 - すべての %1$s を編集するには、WooCommerce ストア管理で注文を閲覧します %1$s が未完了です - システムステータスレポートを共有 - システムステータスレポートをクリップボードにコピー + すべての %1$s を編集するには、WooCommerce ストア管理で注文を閲覧します 検索を続行 - %2$s blog_id %3$s への注文 #%1$sのオフラインでの支払い。 決済サービス業者を変更 返金済み: %1$s + システムステータスレポートを共有 + システムステータスレポートをクリップボードにコピー 支払い待ち + %2$s blog_id %3$s への注文 #%1$sのオフラインでの支払い。 インストールを進める インストール前に 拡張機能をインストール @@ -1247,8 +1269,8 @@ Language: ja_JP ロック中 「商品」または「支払いの詳細」を編集するには、ステータスを「支払い待ち」に変更します。 この注文の一部は現在編集できません - 顧客を検索 お客様が見つかりません + 顧客を検索 後で ストアに拡張機能を追加 WooCommerce Shipping とは @@ -1283,8 +1305,6 @@ Language: ja_JP 現在、さまざまな価格が混在しています 現在の価格は %sです。 %d件のバリエーションの価格が更新されます。 - 混合 - なし セール価格 標準価格 価格 @@ -1292,6 +1312,8 @@ Language: ja_JP 一括更新 OK 一括更新… + 混合 + なし バリエーションを取得しています… 商品カテゴリーを検索できませんでした 商品カテゴリーを読み込めませんでした @@ -1306,11 +1328,10 @@ Language: ja_JP WooCommerce Shipping を入手 WooCommerce Shipping を使用してスマートフォンからラベルを印刷します。 配送ラベルが必要な場合 - 商品数量を%1$dから%2$dに変更 通常価格を更新 販売価格を更新 + 商品数量を%1$dから%2$dに変更 %1$s では WooCommerce Stripe 拡張機能がサポートされていません - フィルター 選択を解除 %d件の商品を選択 %d件の商品を選択 @@ -1322,6 +1343,7 @@ Language: ja_JP セール価格商品にクーポンを適用しない場合は、この機能をオンにします。 商品単位で適用されるクーポンは、その商品がセール価格商品でない場合にのみ有効になります。 お買い物カゴ単位で適用されるクーポンは、お買い物カゴにセール価格商品ではない商品が含まれる場合にのみ有効になります。 セール品を除外 他のクーポンと併用できないクーポンの場合は、この機能をオンにします。 + フィルター 併用不可 ユーザー毎の利用数制限 X件の商品に利用を制限 @@ -1396,12 +1418,12 @@ Language: ja_JP 別の返金方法をお試しください 不明な理由により返金が拒否されました 返金を処理できませんでした + コピー 正常に返金されました 返金を処理しています 支払いを返金 返金に失敗しました 支払いを返金する準備をしています - コピー クーポンを検索 クーポンコードの共有メッセージを生成できません クーポンコードを共有する際にエラーが発生しました。 @@ -1426,18 +1448,9 @@ Language: ja_JP 購入手続き - %s 支払いリンクを共有する 合計 - 合計 - 割引注文 - パフォーマンス - %sの利用上限額 - %sの利用最小額 - クーポンの概要 クーポンの概要を表示 - デバイスからクーポンを表示、編集できる機能を開発中です ! クーポンを表示して編集する クーポンが見つかりません - %1$s (%2$s を除く) - %1$s、%2$s すべて 期限切れ 有効 @@ -1455,6 +1468,15 @@ Language: ja_JP \u2022 1件の承認済みのレビュー \u2022 %d件の承認済みのレビュー %1$s (%2$s%%) + 合計 + 割引注文 + パフォーマンス + %sの利用上限額 + %sの利用最小額 + クーポンの概要 + デバイスからクーポンを表示、編集できる機能を開発中です ! + %1$s (%2$s を除く) + %1$s、%2$s デバイスから注文を作成できるようにする機能を開発中です。 「+」ボタンをタップしてこの機能を試すことができます ストアの成長に役立つその他のヒントやインサイトは今後も増えていきますので、ぜひまた訪問してください すべて読み終えました ! @@ -1469,10 +1491,10 @@ Language: ja_JP このサイトでは XML-RPC サービスが無効になっています。 Automattic 以外のメールを使用してサポートチケットを送信してください %1$s で登録された Stripe アカウントはサポートされていません - %1$s では WooCommerce Payments がサポートされていません リーダーの電源ボタンを押してください 領収書を <strong>%s</strong> に送信しました パーセンテージ (%) + %1$s では WooCommerce Payments がサポートされていません 注文から手数料を削除 注文から配送料を削除 配送 @@ -1502,8 +1524,8 @@ Language: ja_JP 「オフラインでの支払い」は、次のプラグインのいずれかが有効化済みの場合にのみ機能します。 続行するには、サイト管理者に連絡して、次のプラグインのいずれかを無効化してください。 「オフラインでの支払い」は、次のプラグインのいずれかが有効化済みの場合にのみ機能します。 続行するには、次のプラグインのいずれかを無効化してください。 支払いプラグインが複数検出されました - 税合計 または + 税合計 Jetpack をインストール オフラインでの支払いは現在利用できません 注文作成済み @@ -1545,25 +1567,24 @@ Language: ja_JP 法的情報およびその他 %1$s について サポートに問い合わせる - 有効化 インストール - WP 管理画面で Jetpack を%s 有効化 インストール - または、WP 管理画面で Jetpack を%sできます。 + 有効化 + WP 管理画面で Jetpack を%s もう一度お試しください。 連携中 有効化 インストール %s中にエラーが発生しました - 注目! このリンクから WooCommerce アプリをダウンロードできます。とても楽しく、どなたにも気に入ってもらえると思います。 %1$s WooCommerce product_card_detail product_card_%1$s review_card_detail review_card_%1$s + または、WP 管理画面で Jetpack を%sできます。 + 注目! このリンクから WooCommerce アプリをダウンロードできます。とても楽しく、どなたにも気に入ってもらえると思います。 %1$s Stripe を更新 - あともう少しです。 カードを使用した支払いを開始するには、Stripe の設定を完了してください。 ストア管理で Stripe の設定を完了する 料金を返金 料金の返金 @@ -1571,12 +1592,11 @@ Language: ja_JP 注文から商品を削除 割引を追加 商品 + あともう少しです。 カードを使用した支払いを開始するには、Stripe の設定を完了してください。 別の配送先住所を追加 在庫あり 在庫%s件 - 商品追加 商品 - お客様の詳細を追加 お客様 支払い済みとしてマーク 注文が作成され、WooCommerce 外部で支払いを受け取った場合は支払い済みとしてマークされます @@ -1584,6 +1604,8 @@ Language: ja_JP 支払い方法を選択 税金は店舗の住所に基づいて自動的に計算されます 税金 (%s%%) + お客様の詳細を追加 + 商品追加 支払いを受け取る (%s) 税金を請求 カスタム金額 @@ -1647,8 +1669,8 @@ Language: ja_JP 金額を入力 支払いを受け取る シンプルペイメント - デバイスから注文を作成しましょう。 アナリティクス + デバイスから注文を作成しましょう。 すべて完了 ストアを接続しています 有効化中 @@ -1732,8 +1754,8 @@ Language: ja_JP システムステータスレポート WooCommerce Payments を使用してデビットカードやクレジットカードでの支払いを受け取ることができるようになりました。 カードリーダーで支払いを受け取る - 金額は %1$s以上にしてください OK + 金額は %1$s以上にしてください 新機能アイコンの画像 ストアを切り替える 商品 %1$s の更新に失敗しました。 @@ -1774,10 +1796,10 @@ Language: ja_JP 自動的に次の配送先住所を確認できませんでした: %s 自動的に配送元住所を確認できませんでした。 Google マップで表示して住所が正しいことを確認してください。 お使いの端末から Product Add-ons をより簡単に確認できるように取り組んでいます。 当面は、注文のアドオンを表示できるようにする予定です。 ウェブダッシュボードでこれらのアドオンを作成および編集できます。 + 保存 お使いの端末でアドオンを表示します。 ウェブダッシュボードにアドオンが残っている場合に、以前の注文がアプリ内に表示されなくなることにご注意ください。 アドオンを表示 - 保存 詳細のアップロード (%d) %d 個のファイルをアップロードできませんでした %d 個のファイルをアップロードできませんでした @@ -1809,10 +1831,10 @@ Language: ja_JP テストモードではオフラインの支払いを利用できません。 電源をオフにして続行します。 オフラインでの支払いは現在利用できません 口座に保留中の要件があります。 引き続きオフラインでのお支払いを受け取る場合は、%1$sまでに要件を完了させてください。 - お使いのアカウントに保留中の要件があります ご使用のアカウントに期限切れの要件があります。 オフラインでの支払いを再開するには、この要件を処理してください オフラインでの支払いは現在利用できません アカウントのレビューが完了すると、すぐにオフラインでの支払いを受け取れるようになります。 + お使いのアカウントに保留中の要件があります オフラインでの支払いは現在利用できません このストアはオフラインでの支払いに対応していません。 更新後に再読み込みする @@ -1829,7 +1851,6 @@ Language: ja_JP モバイルデバイスや注文用カードリーダーでの支払いの受領方法について<a href=\'\'>さらに詳しく</a> ヘルプが必要ですか ? <a href=\'\'>サポートに問い合わせる</a> ストアで「代金引換」の支払い方法を有効にすれば、オフラインでの現金支払いを受け取ることもできます。 - %1$s ではカードによるオフラインでの支払いがサポートされていません アカウントへの接続 オフラインでの支払い 荷物の寸法と重量を再度ご確認いただくか、「荷物の詳細」で別の荷物のご利用をお試しください。 @@ -1837,6 +1858,7 @@ Language: ja_JP 利用可能なすべての荷物が有効化されました 荷物の有効化 有効化する荷物を選択します。 + %1$s ではカードによるオフラインでの支払いがサポートされていません 必須項目 閉じる バリエーションを作成しました @@ -1845,11 +1867,11 @@ Language: ja_JP バリエーションを生成 属性の追加が完了しました。最初のバリエーションを作成してください。 属性を作成しました - %1$s%% 完了 実行中のソフトウェア更新をキャンセルすることは推奨されません 支払いを処理できませんでした サーバーに接続していません インターネットに接続していません + %1$s%% 完了 初期の梱包で配送する 新しい荷物を追加 この項目は現在%sにあります。 どこに移動しますか ? @@ -1860,7 +1882,6 @@ Language: ja_JP 荷物を作成できませんでした。 もう一度お試しください。 荷物を作成できませんでした。不明な API エラーが発生しました。 荷物を作成できませんでした。%1$s - しばらくお待ちください… 新しい荷物を作成しています 無効な値。 この欄は入力必須です。 @@ -1874,10 +1895,11 @@ Language: ja_JP ボックス 荷物の種類を選択する 荷物の種類 - 商品の配送に使用する荷物を設定します。 今後の注文用に保存されます。 新しい荷物を追加する 新しい荷物を作成する 荷物の寸法は0以上にする必要があります。 商品ページの「配送」セクションで商品の寸法を更新して続行します。 + しばらくお待ちください… + 商品の配送に使用する荷物を設定します。 今後の注文用に保存されます。 初期の梱包 商品の寸法 個別に配送した商品 @@ -1890,11 +1912,11 @@ Language: ja_JP ソフトウェアのバージョンの更新チェックができませんでした。 モバイルでの支払いと注文用カードリーダーの承認について<a href=\'\'>さらに詳しく</a> Bluetooth をオンにする - Reader が接続されていません Reader に接続できませんでした 連携 複数の Reader が見つかりました この注文のお支払いは完了しています + Reader が接続されていません ご購入ありがとうございます。 以下のリンクをクリックしてお支払いのレシートをご確認ください。\n\n%s 税関申告書をダウンロードする際にエラーが発生しました 税関送り状を印刷 @@ -1910,13 +1932,12 @@ Language: ja_JP 商品を追加 バリエーション属性 モバイルデバイスの Bluetooth をオンにする - 注文を取得する際にエラーが発生しました。 アプリの注文状態が古い可能性があります。 メールクライアントアプリを検出できません %s のレシート 注文を更新する アプリの状態を更新する お客様が %1$s を選択しました - カスタムフォームには、10桁の電話番号を入力してください + 注文を取得する際にエラーが発生しました。 アプリの注文状態が古い可能性があります。 カスタムフォームの入力が完了しました デバイスからの印刷で問題が生じている場合は、プリンターのカスタマーサポートにお問い合わせください。 印刷できない場合はレシートを PDF として保存し、メールで送信して別のデバイスから印刷できます。 @@ -1929,6 +1950,7 @@ Language: ja_JP バリエーションを作るには、最初にその属性 (「色」、「サイズ」など) を設定する必要があります 1個のバリエーション %1$s個のバリエーション + カスタムフォームには、10桁の電話番号を入力してください USPS の追跡 Reader のソフトウェアを更新しています ソフトウェア更新 @@ -1988,9 +2010,9 @@ Language: ja_JP 権限グループと権限の詳細 このアプリでサポートされているユーザー権限グループは、管理者とショップマネージャーのみです。 自分の権限グループを更新するには、ストア所有者に連絡してください。 どこからでも新しい商品を編集、追加 + スキップ 外出先でも注文を管理・編集 セールスと売れ行きが好調な商品を追跡する - スキップ 外部商品 グループ化された商品 バリエーションのある商品 @@ -1999,9 +2021,6 @@ Language: ja_JP シンプルな物理的製品 設定を開く 設定を開く - Bluetooth が無効になっています - 位置情報が無効になっています - 正確な位置情報に必要な権限がありません Reader に接続できませんでした。 Reader に接続しています Reader に接続する @@ -2009,9 +2028,10 @@ Language: ja_JP Reader をスキャンしています 項目数 新しい配送ラベルを作成 - シンプルな仮装製品 + Bluetooth が無効になっています + 位置情報が無効になっています + 正確な位置情報に必要な権限がありません このバリエーションを削除しますか ? - バリエーションを生成しています 製品を削除しています レシートを送信 レシートを印刷 @@ -2025,6 +2045,8 @@ Language: ja_JP 配送ラベルをプレビューできません。 PDF ビューアのアプリをインストールし、再試行してください。 入力されたアドレスでは、WordPress サイトを検出できませんでした。 WordPress がインストールされていて、入手可能な最新のバージョンで実行されていることを確認してください。 複数の配送ライン + シンプルな仮装製品 + バリエーションを生成しています 注文を完了としてマークできませんでした ラベルの購入時にエラーが発生しました しばらくお待ちください… @@ -2053,18 +2075,17 @@ Language: ja_JP 配送ラベルのお支払い方法を管理できるのは、サイトの所有者のみです。 お支払い方法を管理するには、ストアの所有者 %1$s (%2$s) にお問い合わせください。 バリエーションを追加 バリエーションを追加 - 最初のバリエーションを作成 %s 合計 %s件の評価が選択済み 無料の署名要件を利用可能 無料の受け取りを利用可能 - 保険 (%s) - 追跡 含む %s 成年の署名が必要 (%s) 署名が必要 (%s) + 保険 (%s) + 追跡 配送料としてお客様から %2$s の %1$s の支払いがありました - WooCommerce で配送ラベルを購入すると、郵便局の価格と比べて5%~40% お得です。 + 最初のバリエーションを作成 WooCommerce サービスの割引はどのくらいですか ? 配送オプションの読み込みでエラーが発生しました 配送業者と金額 @@ -2082,6 +2103,7 @@ Language: ja_JP 各オプションの名前を追加して Enter を押します またはタップして既存の属性を選択します オプション名 + WooCommerce で配送ラベルを購入すると、郵便局の価格と比べて5%~40% お得です。 設定の保存中にエラーが発生しました しばらくお待ちください… 設定を保存しています @@ -2104,18 +2126,15 @@ Language: ja_JP 属性を追加 属性 属性を編集 - 荷物の合計重量: %1$s %2$s %2$d個の荷物内に%1$d個のアイテム 荷物の総重量: %1$s %2$s カスタム荷物 商品を取得できません - いくつかの必須フィールドが空白になっています。 無効な重量 選択済みの荷物 しばらくお待ちください… 荷物を積み込んでいます。 荷物 %1$d - %d 項目 荷物の定義を読み込めません 荷物の重量を含む 荷物の総重量 (%1$s) @@ -2128,16 +2147,17 @@ Language: ja_JP 入力された住所が一部修正されました。 正しい場合は、正確に配送できるように提案された住所を使用してください。 選択した住所を編集 選択した住所を使用 - 住所データを読み込んでいます + いくつかの必須フィールドが空白になっています。 + 荷物の合計重量: %1$s %2$s + %d 項目 + 住所データを読み込んでいます 新機能を利用できます。 - 地図で検索 顧客に連絡 無効な住所 番地が見つかりません 住所が見つかりませんでした 自動的に配送先住所を確認できませんでした。 Google マップで表示するか、顧客に連絡して住所が正しいことを確認してください。 住所の確認に失敗しました - しばらくお待ちください… 住所の確認中です 住所データを読み込むことができませんでした 入力した住所を使用 @@ -2148,6 +2168,8 @@ Language: ja_JP 電話 会社 名前 + しばらくお待ちください… + 地図で検索 Google マップアプリが見つかりました しばらくお待ちください… 商品バリエーションの画像削除は、WooCommerce 4.7以上が対象となっています。 @@ -2163,31 +2185,30 @@ Language: ja_JP 梱包の詳細 配送ラベルを作成 さらに詳しく - 自宅でモバイルデバイスを使い、割引料金で配送ラベルを印刷して、郵便局での待ち時間をカット ! WooCommerce Shipping を利用して、時間とお金を節約 WooCommerce の送料 注文を完了としてマーク - モバイルデバイスでのラベル作成に関する詳細はこちら 配送ラベルを作成 - 無料の WooCommerce Shipping プラグインを使用して、お使いの端末から直接、すべての物理的な注文の配送ラベルを作成できるようになりました。 「配送ラベルを作成」をタップしてベータ機能をお試しください ! スマートフォンを使って配送ラベルを作成 ! + 無料の WooCommerce Shipping プラグインを使用して、お使いの端末から直接、すべての物理的な注文の配送ラベルを作成できるようになりました。 「配送ラベルを作成」をタップしてベータ機能をお試しください ! + 自宅でモバイルデバイスを使い、割引料金で配送ラベルを印刷して、郵便局での待ち時間をカット ! + モバイルデバイスでのラベル作成に関する詳細はこちら + 編集 手数料 支払額 (正味) 支払済み Jetpack の連携について詳しく読む - 編集 認証 写真の順序を変更するにはドラッグ & ドロップしてください + 削除 ダウンロード設定 有効な名前を入力してください ファイル URL を入力 - WordPress メディアライブラリ 入力した URL が正しいかどうかを確認してください しばらくお待ちください… ファイルをアップロードしています ファイルのアップロードの際にエラーが発生しました ダウンロード可能なファイルを追加 - ダウンロード可能なファイルを追加する ダウンロード可能なファイルを購入に含める キャンセル 変更する @@ -2196,7 +2217,6 @@ Language: ja_JP ファイル このファイルを削除してもよろしいですか ? ダウンロード可能な商品 - 削除 ダウンロードの有効期限 ダウンロード制限 ダウンロードリンクの期限が切れるまでの日数を入力するか、期限がない場合は空欄のままにします @@ -2211,11 +2231,13 @@ Language: ja_JP <b>プリンター自体で WiFi 印刷を直接設定する必要があるようです。</b>プリンターのファームウェアが最新であることを確認し、プリンターの説明書をお読みになることをおすすめします。 端末の<b>デフォルトの印刷サービス</b>を選択するか、<b>プリンターのブランドのアプリ</b>をインストールできます (これは推奨オプションとして表示) お使いのプリンターと端末が<b>同じ WiFi ネットワーク</b>に接続されていることを確認してください - リリースの準備ができ次第、新しいリンクおよびグループ化された商品の作成機能をテストしてください + WordPress メディアライブラリ + ダウンロード可能なファイルを追加する アップセルとクロスセルで販売を伸ばしましょう 商品を編集 商品追加 現在の商品が選択された際にカートで宣伝される商品です + リリースの準備ができ次第、新しいリンクおよびグループ化された商品の作成機能をテストしてください クロスセル 現在表示中の商品の代わりに宣伝される商品です (より安価な商品など) アップセル @@ -2226,7 +2248,6 @@ Language: ja_JP ストアのアナリティクスを表示できません ログインリンクをメールで取得 このメールアドレスに紐付けられている WordPress.com アカウントが見つかりません - 起動の準備ができたら、注文アドオンの表示をテストします 商品を作成中 設定 商品をゴミ箱へ移動中にエラーが発生しました @@ -2238,24 +2259,25 @@ Language: ja_JP サイズ、色といったオプションの追加は、現在 Web でのみ可能です。 サイトの商品ページにオプションとして表示されます。 アプリから商品を作成しましょう ! 商品が見つかりません - 端末からの印刷で引き続き問題が発生している場合は、<b>ラベルを PDF として保存</b>し、メールで別の端末に送信して印刷できます。 - <b>「配送ラベルを印刷」</b>を選択後、この端末からの印刷が初めての場合は、プリンターを選択して追加する必要がある可能性があります。 ラベルの書式設定オプション - 端末から印刷 ラベル (4 x 6インチ) レター (8.5 x 11インチ) リーガル (8.5 x 14インチ) 配送ラベルのプレビューでエラーが発生しました - モバイルデバイスから印刷する方法をご存知ですか ? ラベルのレイアウトと用紙サイズのオプションを見る 配送ラベルを印刷 用紙サイズを選択 用紙サイズ - 荷物に一度使用したラベルを印刷して再度使用すると、弊社の利用規約違反となります。 購入したラベルで印刷エラーが発生した場合は、そのラベルをもう一度印刷できます。 お使いの端末から配送ラベルをより簡単に直接印刷できるよう、改善を重ねているところです ! 現時点では、WooCommerce Shipping のストア管理からこの注文用に配送ラベルを作成した場合、こちらの「注文詳細」から印刷できます。 お手元の端末から配送ラベルを印刷しましょう ! + 端末から印刷 + 荷物に一度使用したラベルを印刷して再度使用すると、弊社の利用規約違反となります。 + 端末からの印刷で引き続き問題が発生している場合は、<b>ラベルを PDF として保存</b>し、メールで別の端末に送信して印刷できます。 + <b>「配送ラベルを印刷」</b>を選択後、この端末からの印刷が初めての場合は、プリンターを選択して追加する必要がある可能性があります。 配送ラベルを印刷 + 起動の準備ができたら、注文アドオンの表示をテストします + モバイルデバイスから印刷する方法をご存知ですか ? \u0022%1$s\u0022 商品の下書きを保存しました 商品の下書きの保存中にエラーが発生しました @@ -2298,17 +2320,17 @@ Language: ja_JP 写真を置換 写真を追加 バリエーション画像を追加 - Jetpack のインストールと連携の方法を調べる - %1$sにこのアプリを使用するには、Jetpack プラグインをストアと連携する必要があります。 別のアカウントでログイン 連携するストアを選択 WordPress.com で続ける - 色やサイズのバリエーションがある商品 + Jetpack のインストールと連携の方法を調べる + %1$sにこのアプリを使用するには、Jetpack プラグインをストアと連携する必要があります。 %d 選択済みの商品 %d 選択済みの商品 商品をグループに追加 商品を追加 パスワードを入力 + 色やサイズのバリエーションがある商品 ストアに戻る こちらから問い合わせ これはサポートチケットではありませんので、個別のフィードバックには対応いたしかねます\n\nヘルプが必要ですか ? %1$s @@ -2333,8 +2355,8 @@ Language: ja_JP 価格未設定 有効 セールを予定している場合はセール価格を設定する必要があります - セット販売商品・外部商品・バリエーションの編集と、商品タイプの変更、カテゴリーおよびタグの更新ができるようになりました。 %1$s レビューを書きました + セット販売商品・外部商品・バリエーションの編集と、商品タイプの変更、カテゴリーおよびタグの更新ができるようになりました。 気に入りました 改善が必要 WooCommerce アプリの感想をお聞かせください @@ -2343,24 +2365,24 @@ Language: ja_JP タグを追加する際にエラーが発生しました タグを追加しています 返金処理を実行中です。 しばらくお待ちください… - 返金処理リクエストを正常に送信しました 返金ラベル (-%1$s) 返金対象額 購入日 - 荷物の配送に使用されていない配送ラベルの返金をリクエストできます。 処理には少なくとも14日かかります。 返金をリクエスト 配送ラベルを返金 + 荷物の配送に使用されていない配送ラベルの返金をリクエストできます。 処理には少なくとも14日かかります。 + 返金処理リクエストを正常に送信しました 物理 商品に関する簡単な抜粋 タグを使用して商品を見つけやすくする 商品を関連グループに整理する + 無効化済み 重さとサイズを追加 詳細を追加 タグで商品を整理する 1つ目のタグを追加 タグ タグを追加 - 無効化済み 仮想商品 詳細を追加 %1$s 商品 @@ -2368,9 +2390,7 @@ Language: ja_JP %s 商品 残りの商品 %1$s \u2022 %2$s - %1$s ラベルの返金がリクエストされました 配送状況を追跡 - %1$s\n%2$s 配送状況の詳細を非表示 配送状況の詳細を表示 クレジットカード @@ -2380,6 +2400,8 @@ Language: ja_JP 配送先 出荷元 荷物 %d + %1$s\n%2$s + %1$s ラベルの返金がリクエストされました 商品コード : %1$s %1$s (%2$s 個のオプション) 配送ラベル @@ -2400,8 +2422,8 @@ Language: ja_JP カリフォルニア州のユーザーへのプライバシー通知 変更を保存 %1$sまで - 商品に編集機能が加わりました。 画像のアップデート、商品のプレビューと共有が可能になりました。 新しい編集オプションが利用可能 + 商品に編集機能が加わりました。 画像のアップデート、商品のプレビューと共有が可能になりました。 一部の編集機能が利用可能 商品 %1$s x %2$s @@ -2469,8 +2491,8 @@ Language: ja_JP 外観 商品についての簡単な概要 簡単な説明 - アナリティクスの改善を提供開始しました。 2020年9月1日以降も統計情報を引き続き表示するには、WooCommerce 4.0以降にアップグレードするか、WooCommerce Admin プラグインをインストールしてください。 統計情報を引き続き表示するにはアップグレードしてください + アナリティクスの改善を提供開始しました。 2020年9月1日以降も統計情報を引き続き表示するには、WooCommerce 4.0以降にアップグレードするか、WooCommerce Admin プラグインをインストールしてください。 セール価格は標準価格未満である必要があります 終了日を削除 配送を追加 @@ -2497,11 +2519,11 @@ Language: ja_JP 横幅 長さ 返金済み商品 - %1$s (%2$s x %3$d) %2$s により %1$s 返金を受けますか ?この操作は元に戻すことができません。 返金済み商品 返金 + %1$s (%2$s x %3$d) WordPress.com に登録 「%s」の検索結果が見つかりませんでした。 ストアに対する高品質な製品レビューを取得する @@ -2522,33 +2544,32 @@ Language: ja_JP 在庫を追加 注文を検索しています… テキストを入力 - 商品名を入力 - 商品を保存しました 商品の更新中にエラーが発生しました。 しばらくお待ちください… 商品を説明 説明 説明を編集 - 変更を破棄しますか ? - 更新 + 商品名を入力 + 商品を保存しました 終了 + 更新 + 変更を破棄しますか ? 返金の処理中です。しばらくお待ちください… 配送料を返金 数量を選択 配送料の返金 商品の返金 - 各 %1$s x %2$s %d項目を選択済み 選択しない すべて選択 返金の確認を待っています… + 各 %1$s x %2$s 画像のサイズ変更と圧縮を行いアップロードを高速化 画像を最適化 写真を撮る デバイスから選択 アップロード方法を選択 アップロード - 画像をアップロードしています… %1$d/%2$d 画像をアップロードしています… カメラにアクセスできません 画像を削除してもよいですか ? @@ -2563,6 +2584,7 @@ Language: ja_JP 画像を追加 次回 削除 + 画像をアップロードしています… %1$d/%2$d サイトにアクセスできませんでした。ホストに問い合わせてこの問題を解決してください。 <b>SSL 証明書</b>の問題により、サイトにアクセスできませんでした。ホストに問い合わせてこの問題を解決してください。 <b>HTTP 認証</b>の問題により、サイトにアクセスできませんでした。ホストに問い合わせてこの問題を解決してください。 @@ -2571,8 +2593,8 @@ Language: ja_JP サイトのログイン情報でログインします。 %1$s サイトのログイン情報でログインします。 確認メールを送信 - リリースの準備ができ次第、新商品の編集機能をテストしてください 商品編集 + リリースの準備ができ次第、新商品の編集機能をテストしてください アカウントの取得時に問題が発生しました。今すぐ再試行するか、閉じてから後で再試行できます。 エラーが発生しました。ログインして続行してください サイトに接続しています… @@ -2607,15 +2629,12 @@ Language: ja_JP 一致する商品はありません まだ商品がありません 在庫%s個 - 在庫 \u2022 %d個のバリエーション 商品画像 %1$sさんが%2$sに関するレビューを書きました 非承認 新規の商品レビュー取得中にエラーが発生しました 商品レビュー取得中にエラーが発生しました - 返金処理でエラーが発生しました。もう一度お試しください。 - 返金処理を正常に送信しました。 - %sの返金処理を実行中です。しばらくお待ちください… + 在庫 \u2022 %d個のバリエーション 見積もりアイコン 手動返金 返金の詳細 @@ -2633,6 +2652,9 @@ Language: ja_JP %sを返金 %sが返金可能 返金を実行 + 返金処理でエラーが発生しました。もう一度お試しください。 + 返金処理を正常に送信しました。 + %sの返金処理を実行中です。しばらくお待ちください… %2$s により %1$s 強化された統計 ベータ版の機能 @@ -2646,12 +2668,12 @@ Language: ja_JP 本日の統計情報 サインイン Jetpack はインストール済みですか ?%1$s - Jetpack でログインを試行しています… 続行するにはアプリを再読み込みします - %1$s にこのアプリを使用するには、Jetpack プラグインを設定し、このアカウントと連携する必要があります。 \n\n設定したら、アプリを再読み込みします ほかのストアでお試しください データベースのダウングレードに伴い、テーブルを再作成し、ストアを読み込んでいます ストアを読み込んでいます + Jetpack でログインを試行しています… + %1$s にこのアプリを使用するには、Jetpack プラグインを設定し、このアカウントと連携する必要があります。 \n\n設定したら、アプリを再読み込みします 配送業者が見つかりませんでした ウェブサイトの完全なアドレス (例 : example.com) を入力してください。 まだレビューがありません。 @@ -2662,12 +2684,11 @@ Language: ja_JP 設定を取得できませんでした。一部の API ではこの OAuth のアプリ ID とアカウントの組み合わせを使用できません。 人材募集中です。 追跡番号をコピー - WooCommerce をチェックしています… アプリを再読み込み + WooCommerce をチェックしています… 住所が指定されていません 関連付けられているメールアドレスをお忘れですか ? このアドレスのサイトは WordPress のサイトではありません。接続するには、接続先のサイトに WordPress がインストールされている必要があります。 - <b>%1$s</b> に接続するには WordPress.com にログインします ジンバブエ ザンビア イエメン @@ -2804,6 +2825,7 @@ Language: ja_JP ジャマイカ コートジボワール イタリア + <b>%1$s</b> に接続するには WordPress.com にログインします イスラエル マン島 アイルランド @@ -2910,24 +2932,15 @@ Language: ja_JP アフガニスタン オーランド諸島 レビュー - カスタムの配送業者 カスタム - 配送業者名を入力してください 追跡番号を入力してください - 配送業者を選択してください この追跡を破棄してもよいですか ? 追跡を追加できません 配送状況の追跡が追加されました - 配送業者を取得する際にエラーが発生しました - 選択した配送業者 - 配送業者 出荷日 追跡リンクを入力 - 配送業者名を入力 追跡番号を入力 - 配送業者を選択 追跡リンク (オプション) - 配送業者名 追跡番号 通信事業者 追跡を追加 @@ -2940,19 +2953,25 @@ Language: ja_JP 配送状況を追跡 サイトの管理では、WordPress.com への接続に使用するメールアドレスは %1$sJetpack のダッシュボード%2$sの%3$s「接続 → アカウントの接続」%4$sで確認できます サインインにはどのメールアドレスを使用すればよいですか ? - 必要なメールアドレスをお忘れですか ? Jetpack は WordPress の無料のプラグインであり、ストアをモバイルでの最適な操作に必要なツール (プッシュ通知および統計を含む) と接続します。 Jetpack とは ? 連携ストアを表示 - %1$s は別の WordPress.com アカウントと関連付けられているようです。 編集を続行 + カスタムの配送業者 + 配送業者名を入力してください + 配送業者を選択してください + 配送業者を取得する際にエラーが発生しました + 選択した配送業者 + 配送業者 + 配送業者名を入力 + 配送業者を選択 + 配送業者名 + %1$s は別の WordPress.com アカウントと関連付けられているようです。 + 必要なメールアドレスをお忘れですか ? ユーザー名とパスワードでログインしてください。 メールアドレスではなく WordPress.com ユーザー名を使用してログインしてください。 このアドレスのサイトは WordPress のサイトではありません。接続するには、接続先のサイトが WordPress を使用している必要があります。 ヘルプセンター - バーチャル - グループ化 - 変数 許可し、顧客に通知する 許可する 許可しない @@ -2960,6 +2979,9 @@ Language: ja_JP 在庫なし 在庫あり 続きを読む + グループ化 + 変数 + バーチャル 画像を読み込めませんでした 下書き 非公開 @@ -3002,14 +3024,14 @@ Language: ja_JP クリップボードへのコピー中にエラーが発生しました %s に切り替えました。このストアの通知のみ受信します。 このストアは古いバージョンの WooCommerce を使用しています。このアプリで使用するには、ストアの WooCommerce を 3.5以上にアップグレードしてください。 + 適用 今すぐお試しください 了解 タップするとストアの切り替えができます - ストアを選択 ログアウト 注文ステータスを変更 クリックすると注文ステータスを変更できます - 適用 + ストアを選択 結構です 後で評価する 今すぐ評価する @@ -3023,11 +3045,11 @@ Language: ja_JP ストアを WooCommerce 3.5 に更新 %s に接続できません 非表示にする - すべてのレビューが既読になるエラー すべてを既読にする メッセージ 通話 カスタマーにコールまたはメッセージを送信 + すべてのレビューが既読になるエラー 製品レビューステータスの更新の際にエラーが発生しました 製品レビューの詳細を読み込む際にエラーが発生しました ゴミ箱 @@ -3040,16 +3062,16 @@ Language: ja_JP 通知の管理 通知 アカウントからログアウトしてもよいですか%s? - レビューを%1$sとしてマークする 無効にするとメモは公開されません + レビューを%1$sとしてマークする 注文を取得する際にエラーが発生しました 戻る 製品レビューアラート 新規注文アラート 顧客向け - サイトを認証中です… 手順を更新 検索 + サイトを認証中です… 再読み込み さらに%d件表示。 %d件の新しい通知 @@ -3081,9 +3103,9 @@ Language: ja_JP クラッシュレポート 共有 バージョン%s - HTTP パスワード - HTTP ユーザー名 - 認証が必要です + SMS 認証コードの送信試行回数が多すぎます。しばらくしてから、新しい認証コードをリクエストしてください。 + この Google アカウントに一致する WordPress.com アカウントはありません。 + Jetpack を接続するのに使用した WordPress.com アカウントにログインします。 マジックリンクが送信されました メール登録 コードの認証 @@ -3092,32 +3114,9 @@ Language: ja_JP マジックリンクでのログイン サイトアドレスでのログイン メールアドレスでのログイン - エラーが発生しました。 - 続行するには認証コードを入力してください。 - 続行するにはパスワードをダブルチェックしてください。 - ログインが停止されました - ログインするまでしばらくお待ちください。 - ログイン中… - 続行するにはタップしてください。 - ログインしました。 - ネットワークエラーが発生しました。接続を確認して、もう一度お試しください。 - WordPress.com または Jetpack と連携したインストール型 WordPress サイトを入力してください - 接続できませんでした。サイトの\nXMLRPC エンドポイントにアクセスしようとして、403エラーを受け取りました。アプリがサイトと通信するためにはアクセスが必要です。ホストに連絡して\nこの問題を解決してください。 - 接続できませんでした。ご使用のホストは POST リクエストをブロックしていますが、サイトと通信するためにアプリはこのリクエストを必要とします。ホストに連絡して、この問題を解決してください。 - 接続できませんでした。必要な XML-RPC メソッドがサーバーにありません。 - 入力したサイト URL が正しいか確認してください - エラーが発生しました - パスワードを忘れた場合 - 有効なメールアドレスを入力してください - メールをチェック - 続けるにはログインしてください。 - Jetpack を接続するのに使用した WordPress.com アカウントにログインします。 - プロフィールを読み込めませんでした - 重複サイトが検出されました。 - このサイトはすでにアプリに存在します。新たに追加することはできません。 - 入力されたユーザー名またはパスワードが間違っています - Google が応答するのに時間がかかりすぎました。インターネット接続が安定するまで時間がかかる場合があります。 + アカウントをまだお持ちではありませんか ?%1$s登録%2$s Google アカウントを作成… + Google が応答するのに時間がかかりすぎました。インターネット接続が安定するまで時間がかかる場合があります。 Google に登録 メールに登録 登録することで、%1$s利用規約%2$sに合意したことになります。 @@ -3127,20 +3126,54 @@ Language: ja_JP メールの送信中にエラーが発生しました。今すぐ再試行するか、しばらくしてからもう一度お試しください。 新しい WordPress.com アカウントを作成するには、メールアドレスを入力してください。 メールアドレスの確認中にエラーが発生しました。 - \n別のアカウントを試してみますか ? + エラーが発生しました。 + 続行するには認証コードを入力してください。 + 続行するにはパスワードをダブルチェックしてください。 + ログインが停止されました + ログインするまでしばらくお待ちください。 + ログイン中… + 続行するにはタップしてください。 + ログインしました。 Google ログインを開始できませんでした。 - SMS 認証コードの送信試行回数が多すぎます。しばらくしてから、新しい認証コードをリクエストしてください。 + パスワードを入力してください + \n別のアカウントを試してみますか ? Google アカウントへの接続で何らかの問題が発生しました。 - この Google アカウントに一致する WordPress.com アカウントはありません。 閉じる Google でログインする。 + ネットワークエラーが発生しました。接続を確認して、もう一度お試しください。 現在のユーザーアカウント: メールクライアントアプリを検出できません - アカウントをまだお持ちではありませんか ?%1$s登録%2$s 認証コードを入力してください - パスワードを入力してください - ユーザー名を入力してください + 重複サイトが検出されました。 + このサイトはすでにアプリに存在します。新たに追加することはできません。 + 接続できませんでした。ご使用のホストは POST リクエストをブロックしていますが、サイトと通信するためにアプリはこのリクエストを必要とします。ホストに連絡して、この問題を解決してください。 + メールをチェック + 接続できませんでした。必要な XML-RPC メソッドがサーバーにありません。 + プロフィールを読み込めませんでした + 続けるにはログインしてください。 + パスワードを忘れた場合 + 入力されたユーザー名またはパスワードが間違っています + 有効なメールアドレスを入力してください + エラーが発生しました + 認証が必要です + 入力したサイト URL が正しいか確認してください + HTTP パスワード + HTTP ユーザー名 + WordPress.com または Jetpack と連携したインストール型 WordPress サイトを入力してください + 接続できませんでした。サイトの\nXMLRPC エンドポイントにアクセスしようとして、403エラーを受け取りました。アプリがサイトと通信するためにはアクセスが必要です。ホストに連絡して\nこの問題を解決してください。 + もしくは、 + 一般 + \@%s + ユーザー名でログインします。 + サイトのアドレスを入力してログインする。 + 代わりに別のコードを送信する。 + 末尾が%sで終わる電話番号にテキストメッセージを送信しました。SMS の認証コードを入力してください。 + この Google アカウントで続行するには、一致する WordPress.com パスワードを入力してください。この操作が必要になるのは一度だけです。 WordPress.com にログインしてコンテンツを共有します。 + コンテンツを共有する WordPress サイトのアドレスを入力します。 + デフォルトのブラウザーを開く際にエラーが発生しました。別のアプリを選択してください: + リンクを開けません + ユーザー名を入力してください WordPress.com にログインして投稿にアクセスしてください。 サイトを追加する際にエラーが発生しました。エラーコード: %s サイトアドレスの確認 @@ -3149,25 +3182,15 @@ Language: ja_JP 自分のサイトアドレス: 自分のサイトアドレスを忘れましたか ? サイトアドレス - コンテンツを共有する WordPress サイトのアドレスを入力します。 \@%s すでに WordPress.com にログインしています 次へ - サイトと連携 別のサイトと連携 - この Google アカウントで続行するには、一致する WordPress.com パスワードを入力してください。この操作が必要になるのは一度だけです。 WordPress.com パスワードを入力してください。 - 現在ご利用いただけません。パスワードを入力してください ログインメールのリクエスト このパスワードは間違っているようです。入力した情報をもう一度確認してから、やり直してください。 SMS 経由で認証コードをリクエスト。 - 代わりに別のコードを送信する。 代わりに SMS でコードを送信する。 - 末尾が%sで終わる電話番号にテキストメッセージを送信しました。SMS の認証コードを入力してください。 - もう少しです ! 認証アプリから WordPress.com の認証コードを入力してください。 - ユーザー名でログインします。 - サイトのアドレスを入力してログインする。 - もしくは、 メールを開く 次へ Jetpack 搭載のサイトを外出先で管理 — WordPress をどこにでも持ち歩けます。 @@ -3175,29 +3198,39 @@ Language: ja_JP お気に入りのサイトを常時チェックして、好きな時に好きな場所で会話に加わることができます。 あなたのサイトを読んで、やり取りしている世界中の読者の動向をリアルタイムで確認できます。 公園から投稿を公開。バスからブログを投稿。カフェからコメントを送信。WordPress をどこでも使えます。 - ログイン - ヘルプ - パスワード - ユーザー名 - 代わりにパスワードを入力 + すでに WordPress.com アカウントにログインしています。別のアカウントに結び付いている WordPress.com サイトを追加することはできません。 + 再試行 + ログアウト リンクを送信 + 現在ご利用いただけません。パスワードを入力してください + ログインしています + 代わりにパスワードを入力 + メールアドレス + 詳細 + 元に戻す 無効な認証コード 認証コード - メールアドレス + ヘルプ + 削除 + ログイン + ユーザー名 + パスワード + 名称未設定 + 設定 + 今日 + キャンセル WooCommerce Android %s サポート オプションがオンになっていません オプションはオンになっています サードパーティのポリシー Cookie ポリシー 個人情報保護方針 - Automattic が愛情込めて作成。%1$s サードパーティのものを含め、他の追跡ツールを使用します。それらについての詳細と設定方法についてお読みください。 個人情報保護方針を読む この情報は、製品を改善し、マーケティングをより関連性の高いものとし、WooCommerce の利用をパーソナライズするのに役立ちます。詳しくは、個人情報保護方針をご覧ください WordPress アカウントへのログイン中のサービス利用に関する情報を分析ツールと共有します 情報を収集 プライバシー設定 - 設定 注文ステータス 返金済み キャンセル済み @@ -3211,7 +3244,6 @@ Language: ja_JP 追加 顧客にメールでメモを送信 注文を変更する際にエラーが発生しました - メモを取得できませんでした 注文が完了としてマークされました 注文を完了としてマーク 注文メモを追加します @@ -3220,7 +3252,6 @@ Language: ja_JP 課金を表示 決済済み 注文メモ - 非公開 注文メモを作成します 顧客プロフィール画像 顧客提供メモ @@ -3245,12 +3276,8 @@ Language: ja_JP 注文はありません 注文を表示 注文を表示 - この期間のアクティビティはありません - 注文合計: %s - 成績優秀者 エラー画像 データを取得する際にエラーが発生しました - 収益 注文 訪問者 @@ -3261,17 +3288,11 @@ Language: ja_JP WooCommerce ストアがありません 自分のプロフィール写真 連携ストア - %1$s設定に関する指示%2$sを参照してください。 このアプリを使用するには、Jetpack をストアに連携させる必要があります。 - \@%s - 連携させる WooCommerce ストアのアドレスを入力してください。 WooCommerce ストアを管理するには、WordPress.com アカウントのメールアドレスを使用してログインします。 - すでに WordPress.com アカウントにログインしています。別のアカウントに結び付いている WordPress.com サイトを追加することはできません。 - リンクを開けません SMS アプリが見つかりませんでした メールアプリが見つかりませんでした 写真アプリが見つかりませんでした - デフォルトのブラウザーを開く際にエラーが発生しました。別のアプリを選択してください: リンクを開けません %1$sの%2$s 1か月以上前 @@ -3280,22 +3301,15 @@ Language: ja_JP 昨日 今日 商品 - 削除 今年 今月 今週 - 今日 商品 お客様のネットワークが使用できません。データまたは Wi-Fi 接続を確認してください。 オフライン u2014 キャッシュされたデータを使用します さらに詳しく - キャンセル - 名称未設定 次へ - 元に戻す - 再試行 詳細を非表示 - 詳細 割引 小計 @@ -3305,11 +3319,19 @@ Language: ja_JP %1$s%2$s 注文 自分のストア - ログアウト - ログインしています すべて - 一般 WooCommerce + 収益 + Automattic が愛情込めて作成。%1$s + 連携させる WooCommerce ストアのアドレスを入力してください。 + 非公開 + サイトと連携 + この期間のアクティビティはありません + 成績優秀者 + 注文合計: %s + メモを取得できませんでした + %1$s設定に関する指示%2$sを参照してください。 + もう少しです ! 認証アプリから WordPress.com の認証コードを入力してください。 \@string/date_timeframe_custom \@string/date_timeframe_today diff --git a/WooCommerce/src/main/res/values-ko/strings.xml b/WooCommerce/src/main/res/values-ko/strings.xml index 76c9ea58a37..039bc85fec3 100644 --- a/WooCommerce/src/main/res/values-ko/strings.xml +++ b/WooCommerce/src/main/res/values-ko/strings.xml @@ -1,12 +1,33 @@ + 상세 정보 확인 + 광고 대상 + 관심 분야 + 위치 + 기기 + 언어 + 예산 + 상세 정보 + 지금 쇼핑하기 + 광고 편집 + 미리보기 + 상품 선택 + 상품 %s 선택 + <b>적용:</b> 프로모션이 시작되는 것을 보고 성공을 추적합니다. + <b>빠른 리뷰</b>: 검토자가 빠르게 확인할 광고를 제출합니다. + <b>예산 설정:</b> 지출과 캠페인 기간을 결정합니다. + <b>대상 지정 사용자 정의:</b> 위치 또는 관심 분야를 기준으로 잠재 고객을 선택하고 잠재적 도달 범위를 확인합니다. + <b>상품 선택:</b> Blaze로 홍보할 상품을 선택합니다. + 재고 관리 + 재고가 관리되지 않음 + Blaze 작동 방식 알아보기 캠페인 시작하기 워드프레스닷컴과 Tumblr 네트워크 내 수백만 개 사이트에 실리는 광고입니다. 방대한 잠재 고객에게 접근 @@ -126,6 +147,7 @@ Language: ko_KR %1$s~%2$s개 아이템 %d개 아이템 %d개 아이템 + %1$.2f개에서 %2$.2f개로 상품 수량 변경 구성 저장 구성 %s 상품 diff --git a/WooCommerce/src/main/res/values-nl/strings.xml b/WooCommerce/src/main/res/values-nl/strings.xml index bd8a14e0b12..d3f7dfff86f 100644 --- a/WooCommerce/src/main/res/values-nl/strings.xml +++ b/WooCommerce/src/main/res/values-nl/strings.xml @@ -1,12 +1,33 @@ + Details bevestigen + Advertentiebestemming + Interesses + Locatie + Apparaten + Taal + Budget: + Details + Nu winkelen + Advertentie bewerken + Voorbeeldweergave Uitgeschakeld + product geselecteerd + Product %s selecteren + Kijk toe wanneer je promotie begint en volg het succes ervan.<b></b> + Dien je advertentie in voor een snelle beoordeling door een moderator.<b></b> + Bepaal de duur van je uitgaven en campagne.<b></b> + Selecteer je publiek op basis van locatie of interesses en bekijk je potentiële bereik.<b></b> + Kies wat je met Blaze gaat promoten.<b></b> + Voorraad beheren + Voorraad niet beheerd + Ontdek hoe Blaze werkt Start je campagne Je advertenties op miljoenen sites binnen de WordPress.com- en Tumblr-netwerken. Bereik een groot publiek @@ -120,6 +141,7 @@ Language: nl tussen de %1$s en %2$s items %d items %d items + Verander de producthoeveelheid van %1$.2f naar %2$.2f Configuratie opslaan Configuratie Product %s diff --git a/WooCommerce/src/main/res/values-pt-rBR/strings.xml b/WooCommerce/src/main/res/values-pt-rBR/strings.xml index a1d4ca57588..3e4f575a3a8 100644 --- a/WooCommerce/src/main/res/values-pt-rBR/strings.xml +++ b/WooCommerce/src/main/res/values-pt-rBR/strings.xml @@ -1,12 +1,33 @@ + Confirmar detalhes + Destino do anúncio + Interesses + Localização + Dispositivos + Idioma + Orçamento + Detalhes + Comprar agora + Editar anúncio + Visualizar Desativada + produto selecionado + Selecionar produto %s + Observe o início de sua campanha e acompanhe o sucesso dela.<b></b> + Envie seu anúncio para uma verificação rápida do moderador.<b></b> + Decida-se sobre seu orçamento e a duração da campanha.<b></b> + Selecione o público por localização ou interesses e observe o alcance em potencial.<b></b> + Escolha o que deseja promover com o Blaze.<b></b> + Gerenciar estoque + Estoque não gerenciado + Descobrir como o Blaze funciona Comece sua campanha Seus anúncios em milhões de sites nas redes do WordPress.com e do Tumblr. Acesso a um vasto público @@ -126,6 +147,7 @@ Language: pt_BR entre %1$s e %2$s itens %d itens %d item + Mude a quantidade de produtos de %1$.2f para %2$.2f Salvar configuração Configuração Produto %s diff --git a/WooCommerce/src/main/res/values-ru/strings.xml b/WooCommerce/src/main/res/values-ru/strings.xml index 2a1cca43eda..9d586948b8e 100644 --- a/WooCommerce/src/main/res/values-ru/strings.xml +++ b/WooCommerce/src/main/res/values-ru/strings.xml @@ -1,12 +1,33 @@ + Подтвердить сведения + Размещение рекламного объявления + Интересы + Расположение + Устройства + Язык + Бюджет + Сведения + Купить сейчас + Редактировать рекламное объявление + Предварительный просмотр Отключено + Выбор товара + Выбрать товар %s + <b>В прямом эфире:</b> следите за началом вашей кампании и её успехами. + <b>Быстрый просмотр:</b> отправьте свою рекламу на оперативную модераторскую проверку. + <b>Создание бюджета:</b> определите продолжительность кампании и затраты на неё. + <b>Настройка таргетинга:</b> выбирайте аудиторию по местоположению или интересам и просматривайте потенциальный охват. + <b>Выбор товара:</b> выберите, что рекламировать при помощи Blaze. + Управление запасами + Запасы не управляются + Как работает Blaze Запустите кампанию Ваша реклама появится на миллионах сайтов в сетях WordPress.com и Tumblr. Охватите широкую аудиторию @@ -126,6 +147,7 @@ Language: ru от %1$s до %2$s элементов %d элементов %d элемент + Измените количество товара с %1$.2f на %2$.2f Сохранить конфигурацию Конфигурация Товар %s diff --git a/WooCommerce/src/main/res/values-sv/strings.xml b/WooCommerce/src/main/res/values-sv/strings.xml index f50d10853e5..06c92fc45c3 100644 --- a/WooCommerce/src/main/res/values-sv/strings.xml +++ b/WooCommerce/src/main/res/values-sv/strings.xml @@ -1,13 +1,29 @@ + Bekräfta detaljer + Annonsdestination + Intressen + Plats + Enheter + Språk + Budget + Detaljer + Handla nu + Redigera annons + Förhandsgranska Inaktiverad (originalstorlek) + Produktval Välj produkt %s + <b>Gå live:</b> Se din kampanj starta och följ dess resultat. + <b>Snabbgranskning:</b> Skicka in din annons för en snabb moderatorkontroll. + <b>Ange din budget:</b> Bestäm hur mycket du vill spendera och kampanjens längd. + <b>Anpassa målgrupp:</b> Välj målgrupp baserat på plats eller intressen och se potentiell räckvidd. <b>Välj en produkt:</b> Välj vad som ska marknadsföras med Blaze. Hantera lager Lager inte hanterat @@ -19,13 +35,11 @@ Language: sv_SE Global räckvidd på ett enkelt sätt Lansera annonser på några minuter – varken erfarenhet eller en stor budget behövs, och det kostar bara från 5 USD och uppåt. Snabb start, stor påverkan - Rabattkod - Ange kod - Använd %1$s Vårt verktyg är utformat för att ge handlare möjlighet att snabbt och enkelt skapa annonskampanjer för maximal trafikökning. Marknadsför Redo att marknadsföra Visa upp dina produkter för miljontals människor + Använd %1$s Starta med %1$s Växla butik Vill du börja hantera den nu? @@ -34,44 +48,42 @@ Language: sv_SE Expandera/minimera beställningssummor Ta emot betalning Koden bör vara i formatet XXXX-XXXX-XXXX-XXXX + Ange kod + Rabattkod Misslyckades att ladda teman. Konfiguration slutförd + Uppdatering av kvantitet är ångrad Kunde inte ladda ditt nuvarande tema Stationär dator Läsplatta Mobil tryck här + Tyvärr verkar det vara ett problem med hämtningen av mallen. %1$s för en live-demo. + Du hittar ditt perfekta tema i WooCommerce-temabutiken. Nuvarande tema Prova ett nytt utseende Hoppa över Börja undersökningen - Vi värdesätter dina åsikter! - Behöver du hjälp? <a href=\'\'>Kontakta oss</a> - Uppdatering av kvantitet är ångrad - Tyvärr verkar det vara ett problem med hämtningen av mallen. %1$s för en live-demo. - Du hittar ditt perfekta tema i WooCommerce-temabutiken. Du har använt vår AI-assisterade funktion för att lägga till produkter flera gånger nu. Vi vill gärna ta del av dina tankar om hur vi kan göra den ännu bättre. + Vi värdesätter dina åsikter! Om du aktiverar alternativet Betala personligen kan kunden betala kontant eller med kort för onlinebeställningar vid leverans.\n\nBeställningar kan fortfarande skapas manuellt utan att aktivera denna funktion. Vill du lägga till alternativet Betala personligen i din kassa? + Behöver du hjälp? <a href=\'\'>Kontakta oss</a> Återbetala anpassat belopp + Återbetalning av anpassade belopp Skanna produktens streckkod Lägg till presentkort - Återbetalning av anpassade belopp Produkt Antal Ursprungligt antal Något gick fel. Försök igen + Antal uppdaterat: %s Visa produktdetaljer Uppdatera antal - AKTIVERA LJUD - % - 0 - Antal uppdaterat: %s Antal + 1 - Temaaktivering misslyckades, försök igen! - BEHÅLL TYST Produkt med SKU: %s är inte lagerhanterad. Försök igen. Produkt med SKU: %s hittades inte. Försök igen. + Temaaktivering misslyckades, försök igen! Ljudet för beställningsnotiser har inaktiverats. Slå på det igen för att höra \"ka-ching\" vid varje ny försäljning. Aktivera ka-ching-ljud Skanna streckkoden för att uppdatera lagret @@ -79,27 +91,31 @@ Language: sv_SE Testnotis TESTA LJUD Klart! \"Ka-ching\"-ljudet kommer nu att höras för varje ny beställning. + BEHÅLL TYST + AKTIVERA LJUD Aktivera det igen för att höra \"ka-ching\" vid varje ny försäljning. Håll koll på dina kunders beställningar. Ka-ching-ljud av Antal beställningar + % + 0 En procentandel av beställningens totala belopp Ett fast belopp Hur vill du lägga till ditt anpassade belopp? - Ta bort anpassat belopp Procentandel av beställningens totala belopp %1$s + Ta bort anpassat belopp + Temat har aktiverats Hem Tryck för att visa Sidor på denna mall Förhandsgranska - Temat har aktiverats När din butik har konfigurerats kan du hitta ditt perfekta tema i WooCommerce-temabutiken. Letar du efter fler? Du kan alltid ändra det senare i inställningar Välj ett tema - Teman - Utforska! Ett fel uppstod när du aktiverade det tema du valt, försök igen! Aktiverar din butiks tema … + Teman + Utforska! Konfiguration krävs Dölj Blaze AI-genererat tackbrev @@ -110,99 +126,99 @@ Language: sv_SE Tackbrev Obs! För att den här inställningen ska kunna aktiveras får prenumerationen inte ha en gratis provperiod eller ett synkroniserat förnyelsedatum. Aktivera detta för att endast debitera frakt en gång vid den första ordern. - Dokument och andra filer på enhet Aktiverad Engångsfrakt + Dokument och andra filer på enhet ✨ Skapa tackbrev Debitera moms Tillgängliga medel sätts in automatiskt, varje %s. Tillgängliga medel sätts in automatiskt, varje dag. Medlen blir tillgängliga efter att ha inväntat granskning i %d dagar. Välj en variant + Välj variant ”%1$s” -> %2$s välj en variant - Välj %1$s %1$s objekt valda %1$s objekt valda + Välj %1$s fler än %1$s objekt fler än %1$s objekt färre än %1$s objekt mellan %1$s och %2$s objekt %d objekt %d objekt - Välj variant Ändra produktantalet från %1$.2f till %2$.2f Spara konfiguration Konfiguration + Produkt %s Konfigurera + Alternativt kommer registreringsavgiften att debiteras omedelbart, även om produkten har en gratis provperiod eller synkroniserade betalningsdatum. En produktprenumeration med varianter Variabel prenumerationsprodukt - Enkel prenumerationsprodukt - Produkt %s - Alternativt kommer registreringsavgiften att debiteras omedelbart, även om produkten har en gratis provperiod eller synkroniserade betalningsdatum. En unik produktprenumeration som möjliggör återkommande betalningar + Enkel prenumerationsprodukt En valfri tidsperiod att vänta innan den första återkommande betalningen debiteras. Eventuella registreringsavgifter kommer fortfarande att debiteras vid prenumerationens start. Provperioden får inte överstiga: 90 dagar, 52 veckor, 24 månader eller 5 år. Gratis provperiod på prenumeration Prenumerationens utgångsdatum - PRODUKTER - PRODUKT - KUND ANPASSADE BELOPP BETALNING TOTALT ORDERANTECKNINGAR - Använd en säkerhetsnyckel - Period + PRODUKTER + PRODUKT + KUND Ange din säkerhetsnyckel för att fortsätta. Det uppstod vissa problem med säkerhetsnyckelsinloggningen - Misslyckades - Avbruten - Betald - Beräknad + Använd en säkerhetsnyckel + Period Faktureringsintervall Rea Okänd + Misslyckades + Avbruten På väg Väntande + Betald + Beräknad Minimera/maximera insättningssammanfattningen Läs mer om när du får dina medel - Momser - Produkter - Tillgängliga medel - Väntande medel Tillgängliga medel sätts in automatiskt, varje månad den %s. Medlen blir tillgängliga efter att ha inväntat granskning i %d dag. + Väntande medel + Tillgängliga medel + Momser + Produkter Betalning totalt E-postadress eller användarnamn + Det går inte att skapa en order med anpassat belopp Ange anpassat namn Lägg till anpassat belopp Namn Belopp Anpassat belopp - Det går inte att skapa en order med anpassat belopp Anslut Jetpack via din adminsida i en webbläsare eller kontakta supporten. Ett fel uppstod vid kommunikationen med din webbplats. Anpassade belopp Marknadsför med Blaze Jag förstår Annonsen har skickats in för godkännande. Vi skickar en bekräftelse via e-post när den är godkänd och aktiverad. - Starta Blaze-kampanj nu - Nå miljoner på WordPress- och Tumblr-webbplatser. Klart! + Starta Blaze-kampanj nu Följ upp resultat samt starta och stoppa din Blaze-kampanj när som helst. + Nå miljoner på WordPress- och Tumblr-webbplatser. Ta kontrollen för bara några dollar om dagen. Det är budgetvänligt. - Minska produktantal - Skanna streckkod - Minimera/expandera produktkort - Få mer försäljning i din butik med Blaze Marknadsför din produkt på bara några minuter. + Få mer försäljning i din butik med Blaze + Det uppstod ett fel vid uppdateringen av listan över kampanjer. Försök igen senare. Välj mediekälla Ett fel uppstod vid generering av produktnamn och beskrivning. Ingen text upptäckt. Välj ett annat förpackningsfoto eller ange produktinformation manuellt. Lägg till produkt - Det uppstod ett fel vid uppdateringen av listan över kampanjer. Försök igen senare. + Skanna streckkod + Minimera/expandera produktkort + Minska produktantal + Öka produkantal Lägg till anpassat belopp Pris efter rabatt - Öka produkantal Föregående beställning Nästa beställning Kampanjinformation @@ -218,13 +234,13 @@ Language: sv_SE Ta emot alla typer av personliga betalningar, direkt\ni din telefon. Ingen extra hårdvara behövs. Budget Klick + Visningar + Avvisad + Har slutförts Aktiv Under granskning Visa alla Skapa kampanj - Visningar - Avvisad - Har slutförts Öka synligheten och få dina produkter sålda snabbt. Blaze-kampanj Contactless Symbol är ett varumärke som ägs av och används med tillåtelse av EMVCo, LLC. @@ -232,11 +248,11 @@ Language: sv_SE 4. Din kund håller sitt kort horisontellt högst upp på din telefon, över symbolen för kontaktlös betalning. 3. Håll fram din telefon mot kunden. 2. Tryck på \"Ta emot betalning\" och välj \"Tryck för att betala\". + ta emot en betalning Skapa en beställning 1. %1$s eller %2$s Hur det fungerar Lär dig mer om kortläsare - ta emot en betalning För att ta emot betalningar över denna gräns, överväg att köpa en kortläsare som accepterar PIN-inmatning. Vi stöder inte PIN-inmatning med Tryck för att betala på Android. I %1$s kräver vissa kort en PIN för kontaktlösa transaktioner över %2$s. @@ -260,49 +276,48 @@ Language: sv_SE Det gick inte att kopiera produktbeskrivningen till urklipp. Produktbeskrivning kopierad till urklipp. Produktgenereringen misslyckades Försök igen - Återskapa - Skriv det åt mig - Berätta vad din produkt är och vad som gör den unik! - Låt AI generera fängslande rubriker åt dig Rensa adressen och sluta använda den här momssatsen Ange en ny momssats för den här beställningen Lägg till momssats automatiskt Det uppstod ett problem när produktnamnet skulle genereras. Försök igen. - Prova att inaktivera filtret för olästa produktrecensioner för att se alla dina produktrecensioner + Återskapa + Skriv det åt mig + Berätta vad din produkt är och vad som gör den unik! + Låt AI generera fängslande rubriker åt dig Produktnamn - Övertygande - Formell - Detaljer - Produktbeskrivning - Produktnamn + Prova att inaktivera filtret för olästa produktrecensioner för att se alla dina produktrecensioner Inga olästa produktrecensioner Ton vald + Övertygande Blommig + Formell Avslappnad Ställ in ton och röst för att forma presentationen av din produkt så att den passar ditt varumärke. Ton och röst + Detaljer + Produktbeskrivning + Produktnamn Du kan alltid ändra detaljerna nedan senare. Förhandsgranska Skapa produktdetaljer Ställ in ton och röst Lägg till viktiga funktioner, fördelar eller information som hjälper kunder att hitta din produkt online. Exempelvis mjukt tyg, slitstarka sömmar, unik design + Framhäv vad som gör din produkt unik och låt AI utöva sin magi. Om din produkt Föreslå ett namn + Exempelvis mjukt tyg, slitstarka sömmar, unik design. Produktnamn + Alternativt kan du utöka dina alternativ genom att trycka för fler namnförslag. Lägg till ditt produktnamn Drivs med AI. <a href=\'guidelines\'><u>Lär dig mer</u></a>. Lägg till en produkt och detaljerna manuellt Lägg till manuellt + Snabbgenerera information åt dig Skapa en produkt med AI Lägg till en produkt Endast olästa recensioner - Framhäv vad som gör din produkt unik och låt AI utöva sin magi. - Exempelvis mjukt tyg, slitstarka sömmar, unik design. - Alternativt kan du utöka dina alternativ genom att trycka för fler namnförslag. - Snabbgenerera information åt dig Redigera momssatsinställning - Betalningsmetoder Detta kommer inte att påverka onlinebeställningar Lägg till den här momssatsen till alla skapade beställningar Redigera momssatser @@ -310,14 +325,15 @@ Language: sv_SE Lägg till momssatser i adminpanelen. Endast momssatser med platsinformation kommer att visas här. Vi kunde inte hitta några momssatser Upptäck andra betalningsleverantörer och \nvälj en betalningsleverantör. + Betalningsmetoder Bilder och videoklipp på enhet Lös nu Slutför inställning Ange momssats Aktivera Ange ny momssats - Ställ in WooPayments + Ställ in Redigera momssatser i adminpanelen Detta kommer att ändra kundens adress till platsen för den momssats du väljer. Knapp som öppnar dialogrutan för information om momssatser @@ -336,10 +352,10 @@ Language: sv_SE <a href=\'learnMore\'><u>Lär dig mer</u></a> om att verifiera din information med WooPayments. Börja installationen Vi har slagit oss ihop med Stripe för WooPayments. Du kommer att bli omdirigerad till Stripes webbplats för registrering. Vi kommer att be dig att kontrollera dina företagsuppgifter och din betalningsinformation. + Dina WooPayments-aviseringar kommer att skickas till e-postadressen som är kopplad till ditt WordPress.com-konto. Föredrar du att använda ett nytt konto? <a href=\'learnMore\'><u>Mer information finns här.</u></a> + Innan du startar konfigurationen 4–6 minuter Beräknad installationstid - Innan du startar konfigurationen - Dina WooPayments-aviseringar kommer att skickas till e-postadressen som är kopplad till ditt WordPress.com-konto. Föredrar du att använda ett nytt konto? <a href=\'learnMore\'><u>Mer information finns här.</u></a> Hantera betalningar utan ansträngning med WooPayments, allt på en och samma plats. Ta emot kortbetalningar, Apple Pay-betalningar, personliga betalningar och över 135 olika valutor, helt utan installationskostnader eller månadsavgifter. Det gick inte att spara butiksnamnet. Försök igen. Sparar nytt butiksnamn… @@ -366,8 +382,8 @@ Language: sv_SE Beställningssumma Beräknad procentandel Beräknat belopp - Att anpassa ditt butiksnamn kan också förbättra din butiks sökmotoroptimering. Butiksnamn + Att anpassa ditt butiksnamn kan också förbättra din butiks sökmotoroptimering. Namnge din butik Annat Tillgång till en mängd olika tillägg och utökningar @@ -378,9 +394,9 @@ Language: sv_SE Heltäckande försäljnings- och analysrapporter Enkel produkthantering och lagerspårning Skapa min butik - Butiksnamn Berätta för oss vilka funktioner\ndu ser fram emot att använda i vår app. Vilka funktioner är du\nmest intresserad av? + Butiksnamn <b>#Ta emot betalningar personligen:</b> Öka försäljningen med personliga betalningar. Säkra korttransaktioner med vår app eller våra kompatibla läsare. <b>#Hantera och skapa beställningar:</b> Sök, uppdatera eller skapa nya beställningar direkt. Förenkla din beställningsprocess. <b>#Spåra försäljning och populära produkter:</b> Håll koll på försäljningen i realtid och populära produkter för att öka din butiks lönsamhet. @@ -412,8 +428,8 @@ Language: sv_SE Klass 4 – Paket (Brandfarliga fasta ämnen) Klass 3 – Paket (Handdesinfektionsmedel, tvättsprit, etanolbaserade produkter, brandfarliga vätskor, osv.) Klass 1 – Paket med leksaksdrivmedel/säkerhetssäkringar - OK Luftkvalificerat etanolpaket – (godkända försändelser av parfymer och handsprit) + OK Välj ett land Vi har identifierat din plats automatiskt. Ändra den här om den är felaktig. Var finns ditt företag? @@ -428,63 +444,63 @@ Language: sv_SE Innehåller farliga material Ange produktrubrik. Prioriterad support dygnet runt - Inbyggd SEO - Designa och redigera utan kod - Börja sälja snabbt Automatiska webbplatssäkerhetskopieringar Marknadens bästa webbhotell Automatiska e-postmeddelanden till kunder + Inbyggd SEO + Designa och redigera utan kod Butikshanteringsverktyg Problemfritt butiksägarskap Få ditt företag att växa + Börja sälja snabbt Låt oss veta var du är på din\nhandelsresa, så att vi kan skräddarsy\ndin Woo-upplevelse åt dig. Vilken av dessa\nbeskriver dig bäst? Vad planerar du att sälja\ni din butik? Om din butik - Variabel prenumeration Börja med vår kostnadsfria provperiod på 15 dagar. Inget kreditkort behövs. Bli en av 3,4 miljoner butiker som växer med Woo, från din första försäljning till miljoner i intäkter. E-handelsplattformen som växer tillsammans med dig + Variabel prenumeration Ta bort rabattkod Alla gillar ett erbjudande Du har inte skapat några rabattkoder ännu. Skapa en rabattkod för att tillämpa den på denna beställning. Gå till rabattkoder Välj en rabattkod Skapa en ny butik + Misslyckades skapa rabattkod Rabattkod skapad Skapa Skapa rabattkod Skapa %1$s Redigera rabattkod Skapa en fast totalrabatt för valda produkter - Misslyckades skapa rabattkod Skapa en fast totalrabatt för hela varukorgen Skapa en procentuell rabatt för valda produkter Fast produktrabatt Fast varukorgsrabatt Procentuell rabatt - Skapa rabattkod - Lägg till rabattkod - Starta testbeställning - Sök efter kunder efter - Lägg till detaljer manuellt - Prova en testbeställning - Prova en testbeställning Kupongtyp – fast produkt Kupongtyp – fast kundvagn Kupongtyp – procentuell rabatt + Skapa rabattkod + Lägg till rabattkod + Starta testbeställning Använd appen för att behandla återbetalningen för testbeställningen Slutför betalningen och vänta på en push-avisering om beställningen i din WooCommerce-app. Välj din testprodukt, lägg till i kundvagn och slutför i kassan på den webbutiken som en riktig kund. Tryck på knappen nedan för att bli vidarebefordrad till din onlinebutik via en webbläsare. + Prova en testbeställning + Prova en testbeställning Kör en testbeställning för att kontrollera att din WooCommerce-process levererar en sömlös kundupplevelse + Lägg till detaljer manuellt + Sök efter kunder efter Annat skäl (vänligen specificera) Jag är en del av ett team och vi måste fatta beslutet kollektivt. - 🧭 Utforskar du fortfarande WooCommerce? + Jag anser att priset på tjänsten är en viktig faktor i mitt beslut. Jag utvärderar och jämför din tjänst med andra på marknaden. Jag utforskar och utvärderar fortfarande funktionerna och fördelarna med appen. Hjälp oss att förstå dina prenumerationsbeslut. Din feedback är viktig. - Jag anser att priset på tjänsten är en viktig faktor i mitt beslut. Det är ingen brådska, ta den tid du behöver! Vi finns alltid här om du har några frågor eller behöver hjälp. Trevligt utforskande! + 🧭 Utforskar du fortfarande WooCommerce? Hjälp oss att förstå ditt prenumerationsbeslut. Vi är intresserade av din beslutsresa. Kan du vänligen berätta för oss om din nuvarande status? 💡Hjälp oss att förstå ditt prenumerationsbeslut. @@ -492,9 +508,9 @@ Language: sv_SE 🌟 Håll igång din verksamhet! Ingen e-postadress Inget namn + Sök efter en befintlig kund eller Senast uppdaterat %s (Uppdateras var 30:e minut) Senast uppdaterat %s - Sök efter en befintlig kund eller <a href=\'\'>Läs mer</a> om att ta emot betalningar med Tryck för att betala på Android Ta emot betalning Du kan inte lägga till produkter som inte har något specificerat pris @@ -502,145 +518,145 @@ Language: sv_SE lägg till kund Gå till inställningar Avbryt - Belopp (%1$s) Bevilja - Vi kunde inte hitta en rabattkod med den koden. Försök igen Du har nekat kameraåtkomst permanent. Det krävs för streckkodsskanning. Aktivera det i appinställningarna Kameraåtkomst krävs för streckkodsskanning. Bevilja kameraåtkomst Något gick fel när din rabattkod skulle valideras. Försök igen + Vi kunde inte hitta en rabattkod med den koden. Försök igen + Belopp (%1$s) Rabatt %1$s – %1$s - Belopp (%1$s) + Totala rabatter Rabatt + Belopp (%1$s) + Manuell rabatt kunde inte tillämpas. Ta bort rabattkoder först Rabatt är inte ett giltigt nummer Rabatt kan inte vara större än priset Ta bort rabatt - Totala rabatter - Manuell rabatt kunde inte tillämpas. Ta bort rabattkoder först Knapptext Verktygstipsmeddelande. \n Detta kan innehålla flera rader. Verktygstipsrubrik - ✨ Skriv med AI Jag förstår - Generera en beskrivning med AI Använd vårt AI-drivna verktyg för att snabbt generera produktbeskrivningar. Det är bara att ange nyckelord så gör vi resten. + ✨ Skriv med AI + Generera en beskrivning med AI + Det uppstod ett problem när produktbeskrivningen skulle genereras. Försök igen senare. Beskrivning genererad av AI Drivs med AI. <a href=\'\'><u>Lär dig mer</u></a>. - Det uppstod ett problem när produktbeskrivningen skulle genereras. Försök igen senare. Tryck för att betala på Android är inte tillgängligt i ditt land än. Håll ögonen öppna. Din enhet behöver ha tjänsten Google Play för att du ska kunna använda Tryck för att betala på Android. För att ta emot personliga betalningar behöver du installera tjänsten Google Play eller köpa en Bluetooth-kortläsare. För att använda Tryck för att betala på Android behöver du Android 10 eller senare. För att ta emot personliga betalningar behöver du uppdatera Android eller köpa en Bluetooth-kortläsare. Din enhet behöver ha ett NFC-chip för att du ska kunna använda Tryck för att betala på Android. För att ta emot personliga betalningar behöver du köpa en Bluetooth-kortläsare. - Vi kunde inte ladda dina data. - Felsökning - Kontrollera kraven Tryck för att betala är inte tillgängligt + Kontrollera kraven + 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 - Lägg till beskrivningar på nolltid med AI. Prova vår funktion idag. 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. - Skanna streckkod - Användarnamn - Namn - E-post - Fler inställningar - Kanske senare Bra start! Är den genererade\nbeskrivningen beskrivning användbar? Generera igen - Ange ditt produktnamn - Skriv en beskrivning Framhäv din produkts unika funktioner och målgrupp med nyckelord för en skräddarsydd beskrivning. Exempel: krukväxt, kaktus, växt, dekorativ, lättskött + Ange ditt produktnamn + Skriv en beskrivning Kameraåtkomst krävs för streckkodsskanning. + Skanna streckkod + Användarnamn + Namn + E-post Tillämpade rabattkoder + Fler inställningar + Kanske senare Skriv igen Du kommer att få ett meddelande när butiken är klar. Luta dig tillbaka, slappna av och låt oss utöva vår magi samtidigt som vi delar med oss av användbara tips. 🔮 + En PIN-kod krävs, men Tryck för att betala stöder inte detta än. Överväg att använda en extern kortläsare Köp en kortläsare Rabattkod kunde inte tillämpas och togs bort från beställningen - En PIN-kod krävs, men Tryck för att betala stöder inte detta än. Överväg att använda en extern kortläsare + Det gick inte att generera meddelandet för delning. Försök igen. Lär dig mer om vår AI-funktion Lägg till ett valfritt meddelande + Skriver … Skriv med AI Marknadsför produkter med Blaze Blaze - Marknadsför med Blaze - Det gick inte att generera meddelandet för delning. Försök igen. AI-innehållsgenerator tillgänglig - Skriver … + Marknadsför med Blaze Dela produkt Grattis. Du är ett steg närmare att kunna lansera din nya butik. Den första produkten har skapats 🎉 Systemet avslutade Woo-appen medan den kördes i bakgrunden. Du kan prova att använda den igen. Systemet avslutade Woo-appen medan den kördes i bakgrunden. Du kan prova att använda den igen. Kortet togs bort för tidigt - 🌟 Håll igång ditt företag med vårt paket! %1$s, vi har pausat din butik, men du kan fortsätta genom att välja ett paket som passar dig bäst. + 🌟 Håll igång ditt företag med vårt paket! Din kostnadsfria Woo Express-provperiod löper ut imorgon (%1$s). Det är dags att ta kontroll över din framtid – välj ett paket och gör dig redo att börja växa. ⏰ Tiden med din kostnadsfria provperiod håller på att löpa ut. - 🎉 Din butik är redo! Hej %1$s. Välkommen till din kostnadsfria Woo Express-provperiod på 14 dagar – allt du behöver för att starta en framgångsrik onlineverksamhet, på en och samma plats. Är du redo att utforska? + 🎉 Din butik är redo! + Variationsprodukt + Vår cookiepolicy förklarar hur vi och andra använder cookies och hur du kan hantera dem. + Cookiepolicy + Din information hjälper oss att förbättra våra produkter, marknadsföring och personifiera din upplevelse på WooCommerce. Integritetspolicy + Det uppstod ett fel när dina integritetsval skulle sparas. Spara Inställningar Tillåt oss att optimera prestandan genom att samla in information om hur användare interagerar med våra mobilappar. Analys Hantera integritet - Din information hjälper oss att förbättra våra produkter, marknadsföring och personifiera din upplevelse på WooCommerce. - Variationsprodukt - Vår cookiepolicy förklarar hur vi och andra använder cookies och hur du kan hantera dem. - Cookiepolicy - Det uppstod ett fel när dina integritetsval skulle sparas. Din integritet är och har alltid varit avgörande för oss. Vi använder, lagrar och behandlar dina personuppgifter för att optimera vår app (och din upplevelse) på en rad olika sätt. Vissa användningsområden för dina data är absolut nödvändiga för att få saker att fungera, andra kan du anpassa i dina inställningar. För att hjälpa oss att förbättra appens prestanda och åtgärda eventuella fel, aktivera automatiska kraschrapporter. - Skanning misslyckades. Försök igen senare Rapportera krascher Rapporter + Läs mer om vår integritetspolicy och vår cookiepolicy. + Integritets- och cookiepolicyer Integritet + Läs mer om vilka data vi samlar in om din butik och dina möjligheter att styra vilka data som delas. + Användningsspårning + Fler integritetsalternativ tillgängliga för woo.com-användare. Kolla in här för att lära dig mer. + Webbalternativ Fler integritetsalternativ Det uppstod ett fel vid uppdateringen av dina integritetsinställningar - Webbalternativ - Spårning Det uppstod ett fel vid hämtningen av dina integritetsinställningar Tillåt oss att optimera prestandan genom att samla in information om hur användare interagerar med våra mobilappar. Analys - Du kan inte lägga till en variabel produkt direkt. Välj en specifik variant - Systemet avslutade Woo-appen medan den kördes i bakgrunden. Du kan försöka använda den igen. + Spårning Vi värdesätter din integritet. Dina personuppgifter används för att optimera våra mobilappar, förbättra säkerheten, genomföra analyser och förbättra din användarupplevelse. - Läs mer om vår integritetspolicy och vår cookiepolicy. - Integritets- och cookiepolicyer - Läs mer om vilka data vi samlar in om din butik och dina möjligheter att styra vilka data som delas. - Användningsspårning + Systemet avslutade Woo-appen medan den kördes i bakgrunden. Du kan försöka använda den igen. + Du kan inte lägga till en variabel produkt direkt. Välj en specifik variant + Skanning misslyckades. Försök igen senare Produkten med SKU %s hittades inte. Det gick inte att lägga till i beställningen - Fler integritetsalternativ tillgängliga för woo.com-användare. Kolla in här för att lära dig mer. - Skanna streckkod Skanning misslyckades. Försök igen senare + Skanna streckkod Leverans till länder som följer EU:s tullregler kräver nu att du tydligt beskriver varje vara. Om du till exempel skickar kläder måste du ange vilken typ av kläder det är (t.ex. herrskjortor, flickvästar, pojkjackor) för att beskrivningen ska vara godtagbar. Annars kan leveranser försenas eller avbrytas i tullen. Kontakta support Detta konto kan inte avslutas eftersom det har aktiva butiker. Ett fel inträffade vid försöket att avsluta ditt konto. Det gick inte att avsluta kontot Avslutar kontot … - Rabattkod (%1$s) - -%1$s - Ta bort rabattkod från beställning Avsluta kontot permanent Bekräfta genom att skriva in ditt användarnamn innan kontot avslutas Bekräfta kontoavslut Avsluta kontot Skanna QR-koden och följ instruktionerna Skanna för att betala + Ta bort rabattkod från beställning + Rabattkod (%1$s) + -%1$s Lägg till rabattkod - Lägg till produkter via skanner - Du måste ge en tydlig och specifik beskrivning av varje artikel. Otillräckligt lager + Du måste ge en tydlig och specifik beskrivning av varje artikel. + Lägg till produkter via skanner Avfärda Lär dig mer - Få beställningsaviseringar och mer - Håll dig uppdaterad och öka butikssäkerheten. Utforska Jetpack nu. Vid frakt till länder som följer EU:s tullregler måste du ange en tydlig, specifik beskrivning för varje vara. Annars kan leveranser försenas eller avbrytas i tullen. + Håll dig uppdaterad och öka butikssäkerheten. Utforska Jetpack nu. + Få beställningsaviseringar och mer Visa eller dölj listan för butikskonfiguration Lista för butikskonfiguration Du kan få tillbaka den vid behov från Meny > Inställningar > Butik @@ -668,55 +684,55 @@ Language: sv_SE Mat och dryck Hälsa och skönhet Öppnar dörrarna - Vi behöver din tillåtelse för att skicka push-notiser för nya beställningar, recensioner med mera till din enhet. Tänder lamporna + Vi behöver din tillåtelse för att skicka push-notiser för nya beställningar, recensioner med mera till din enhet. Aviseringar Sammansatt produkt Enhetens mediabibliotek - Tillåt Testa Tryck för att betala med automatisk återbetalning Testbetalning med Tryck för att betala + Tillåt + Uppdatera dina preferenser Inställningar Hantera dina paket - Visa din butik - Håll dig uppdaterad - Uppdatera dina preferenser Ta emot produktrecensioner för din butik Öka försäljningen med specialerbjudanden + Visa din butik + Håll dig uppdaterad + Skaffa mobila betalningsmöjligheter + Hantera mer på admin Allmänt Inställningar + Du kan redigera paketprodukter i webbadminpanelen. %d produkter 1 produkt - Något gick fel - Paket - Vi kan inte skapa din butik just nu, försök igen senare. - Skaffa mobila betalningsmöjligheter - Hantera mer på admin - Du kan redigera paketprodukter i webbadminpanelen. Paketprodukter Ej grupperade Inget maximum Inget minimum Jag fattar! - Prova gratis - Produkter - Inget kreditkort krävs. - Populär - Presentkort - Presentkort - Lär dig mer om roller och behörighet - Det verkar som att din roll inte tillåter dig att installera Jetpack.\nKontakta din administratör för hjälp. - Premiumteman + Vi kan inte skapa din butik just nu, försök igen senare. + Något gick fel + Paket Du kan redigera produktkvantitetsregler i webbadminpanelen. Grupp om Största kvantitet Minsta kvantitet Kvantitetsregler + Presentkort + Presentkort Social annonsering + Premiumteman + Inget kreditkort krävs. + Prova gratis + Prova det gratis i 14 dagar. Vi tillhandahåller allt du behöver för att bygga och utveckla en onlinebutik som drivs med WooCommerce och har WordPress.com som webbhotell. Lansering på några dagar, med åratal av tillväxtmöjligheter + Produkter Senast sålda - Prova det gratis i 14 dagar. + Populär + Lär dig mer om roller och behörighet + Det verkar som att din roll inte tillåter dig att installera Jetpack.\nKontakta din administratör för hjälp. Prova Tryck för att betala Gratis provperiod Registreringsavgift @@ -731,48 +747,52 @@ Language: sv_SE vecka dag Anpassad - Löpt ut - Aktiv Väntar på att avslutas + Löpt ut Avslutat Pausad + Aktiv Du kan redigera produktprenumerationer i webbadminpanelen. Ingen gratis provperiod Ingen registreringsavgift Löper aldrig ut + %1$s varje %2$s %3$s + Varje %1$d %2$s + Varje %1$s Prenumeration #%1$d Prenumeration OK Prenumeration - Prenumeration - %1$s varje %2$s %3$s - Varje %1$d %2$s - Varje %1$s OTP-koden är felaktig. Dubbelkontrollera din information och försök igen. SMS-begäran misslyckades. Försök igen. SMS begärt, kolla dina meddelanden för att se koden. + Prenumeration Kortläsaren accepterar blipp, chipp och magnetremsa vid betalning med betal- och kreditkort. Ta emot säkra kontaktlösa betalningsmetoder direkt från din telefon. - Hämtar webbplats … - Det går inte att logga in eftersom lösenordsskapandet i appen inte godkänts. - Dela feedback Använd din telefon för att ta emot kort\nbetalningar Prova nu. - Laddar… - Logga in + Dela feedback + Det går inte att logga in eftersom lösenordsskapandet i appen inte godkänts. + Hämtar webbplats … Ett fel inträffade när webbsidan skulle hämtas Testa igen med sidan med Adminpanelen + Logga in + Laddar… %s har avslutats Din prenumeration har avslutats och du har begränsad åtkomst till alla funktionerna. %1$d dagar 1 dag Laddar in … - Om din butik - Ett telefonnummer är obligatoriskt Konfiguration av Payments + Om din butik Renderar förhandsgranskning… + Ett telefonnummer är obligatoriskt %1$s, %2$s, %3$s, %4$s har sålts poster artikel + Det gick inte att hämta paketinformationen + Du är %1$s-prenumerant. Du har tillgång till alla våra funktioner fram till %2$s. + Din kostnadsfria provperiod har avslutats och du har begränsad åtkomst till alla funktionerna. Prenumerera på %1$s nu. + Du har en kostnadsfri provperiod på %1$d dagar. Din kostnadsfria provperiod löper ut om %2$s. Uppgradera för att låsa upp nya funktioner och hålla igång din butik. Prenumerationsstatus Uppgraderingar Felsökning @@ -780,148 +800,144 @@ Language: sv_SE Uppgradera nu Rapportera prenumerationsproblem Uppgradera nu - Oväntat fel - Privat - Din butik är live! - Förhandsgranska - Tillbaka till min butik - Sök domäner - Publicera min butik - Det gick inte att hämta paketinformationen - Du är %1$s-prenumerant. Du har tillgång till alla våra funktioner fram till %2$s. - Din kostnadsfria provperiod har avslutats och du har begränsad åtkomst till alla funktionerna. Prenumerera på %1$s nu. + %1$s kvar på din utvärdering. Provperioden avslutades Din provperiod har avslutats. Hoppsan, det uppstod några oväntade fel. + Oväntat fel + Vi upptäckte att butiken redan har lanserats. Det gick inte att lansera din butik Det gick inte att dela butiks-URL:en + Privat + Din butik är live! + Förhandsgranska + Tillbaka till min butik Dela URL + Publicera min butik För att lansera din butik behöver du uppgradera till vårt paket. <u>Uppgradera</u> - Vi upptäckte att butiken redan har lanserats. - Du har en kostnadsfri provperiod på %1$d dagar. Din kostnadsfria provperiod löper ut om %2$s. Uppgradera för att låsa upp nya funktioner och hålla igång din butik. - %1$s kvar på din utvärdering. - Något gick fel. Försök igen senare. - Uppgraderingar + Sök domäner Inloggning misslyckades med statuskod %1$s Det gick inte att logga in, eftersom vi inte kan identifiera admin-URL:en för din butik Det gick inte att logga in, eftersom vi inte kan identifiera inloggnings-URL:en för din butik Inloggningen misslyckades med ett oväntat svar från din webbplats. Vi jobbar på att lösa det här problemet. + Uppgraderingar + Något gick fel. Försök igen senare. Det finns krav som inväntar granskning i ditt konto. Slutför dessa krav för att fortsätta ta emot personliga betalningar. Betygsätt gärna din analysupplevelse Gillar du analysen? Vi har jobbat på att göra det möjligt att visa viktig butiksinformation från din enhet. Skulle den kunna vara bättre? Hjälp oss att förbättra den här funktionen genom att dela din feedback med oss Se din statistik, dina intäkter med mera från din enhet. Ett fel uppstod vid hämtningen av din webbplats. Försök igen. + Vi kan tyvärr inte skapa supportförfrågningar för tillfället. Försök igen senare. Något gick fel Jag förstår! - Vi kan tyvärr inte skapa supportförfrågningar för tillfället. Försök igen senare. Din supportförfrågan har landat säkert i vår inkorg. Vi kommer att svara via e-post så snart vi kan. + Förfrågan skickad. Vänta … - Jag behöver hjälp med - Ämne - Supportförfrågan - Dela feedback - Meddelande - Skriv något - Visa alla (%1$d) - Ge dina kunder ett enkelt och bekvämt sätt att betala! - Få betalt + Skickar din förfrågan Annan utökning/tillägg WooCommerce-tillägg - Förfrågan skickad. - Skickar din förfrågan WooCommerce-betalningar Kortläsare/personliga betalningar Mobilapp + Skriv något + Meddelande Skicka supportförfrågan + Ämne Meddela oss din webbplatsadress (URL) och berätta så mycket du kan om problemet, så kommer vi att kontakta dig snart. Låt oss ordna detta + Jag behöver hjälp med + Supportförfrågan + Dela feedback Onboarding, minimerad lista Onboarding, fullskärm + Visa alla (%1$d) %1$d av %2$d uppgifter har slutförts + Ge dina kunder ett enkelt och bekvämt sätt att betala! + Få betalt Vi använder den här informationen för att snabbare kunna konfigurera dina frakt-, moms- och betalningsinställningar. - Logga in för att fortsätta Berätta mer om din butik - Något blev fel. Försök igen senare. - Prova en betalning + Vi har precis skickat en magisk länk till e-postadressen för ditt konto + Logga in för att fortsätta + Få åtkomst till alla dina WooCommerce-butiker. Flera butiker Hämtar Jetpack-status - Få åtkomst till alla dina WooCommerce-butiker. - Vi har precis skickat en magisk länk till e-postadressen för ditt konto - Registrerar domännamn… - Välj land - Välj delstat - ÅTGÄRDER - Ett fel uppstod under domänregistrering + Något blev fel. Försök igen senare. + Prova en betalning Ta emot kortbetalningar\nmed din telefon Tryck för att betala - Telefon - Landskod - Land - Adress - Adress 2 - Ort - Delstat - Delstat (Inte tillgänglig) - Postnummer + ÅTGÄRDER + Ett fel uppstod under domänregistrering + Välj delstat + Välj land + Registrerar domännamn… Registrera domän - För din bekvämlighet har vi förfyllt din WordPress.com\n kontaktinformation. Granska det för att vara säker på att det är rätt information du vill använda för denna domän. + Postnummer + Delstat (Inte tillgänglig) + Delstat + Ort + Adress 2 + Adress + Land + Landskod + Telefon Organisation (valfritt) - Domänägare måste uppge kontaktinformation i en publik databas som omfattar alla domäner. Med integritetsskydd publicerar vi våra uppgifter istället för dina, och vidarebefordrar sedan privat eventuella meddelanden till dig. - I och med att du registrerar denna domän accepterar du våra %1$savtalsvillkor%2$s - Ange en giltig %s - Registrera privat med integritetsskydd - Registrera publikt + För din bekvämlighet har vi förfyllt din WordPress.com\n kontaktinformation. Granska det för att vara säker på att det är rätt information du vill använda för denna domän. Kontaktinformation för domän + Registrera publikt + Registrera privat med integritetsskydd + Ange en giltig %s + I och med att du registrerar denna domän accepterar du våra %1$savtalsvillkor%2$s + Domänägare måste uppge kontaktinformation i en publik databas som omfattar alla domäner. Med integritetsskydd publicerar vi våra uppgifter istället för dina, och vidarebefordrar sedan privat eventuella meddelanden till dig. Integritetsskydd Endast butiksadministratörer kan komma åt domäninställningar - Logga in på ditt WordPress.com-konto för att installera Jetpack - Logga in på ditt WordPress.com-konto för att ansluta Jetpack + Eller fortsätt med magisk länk Ange lösenordet för ditt WordPress.com-konto för att installera Jetpack Ange lösenordet för ditt WordPress.com-konto för att ansluta till Jetpack - Eller fortsätt med magisk länk - Gratis första året + Logga in på ditt WordPress.com-konto för att installera Jetpack + Logga in på ditt WordPress.com-konto för att ansluta Jetpack + Du hittar domäninställningarna via Inställningar > Domäner Din webbplatsadress håller på att konfigureras. Det kan dröja upp till 30 minuter innan din domän börjar fungera. Grattis till dina köp - Du hittar domäninställningarna via Inställningar > Domäner + Gratis första året Är du säker på att du vill logga ut från ditt konto? Kan inte ladda webbplatsdomäner + %1$d/%2$d har slutförts + Få en anpassad URL för din butik. Anpassa din domän Publicera din webbplats till världen när du vill! Lansera din butik - %1$d/%2$d har slutförts - Få en anpassad URL för din butik. Börja sälja genom att lägga till produkter eller tjänster i din butik. Lägg till din första produkt - Ett nätverksfel uppstod. Kontrollera din anslutning och försök igen. Konfigurera din butik Du har redan en aktiv eCommerce-prenumeration. Kontakta supporten om du stöter på problem med att din nuvarande prenumeration inte är kopplad till din nya webbplats. + Ett nätverksfel uppstod. Kontrollera din anslutning och försök igen. Butiksskapande är inte tillgängligt för tillfället. Försök igen senare. Du kan bara köpa eCommerce-paketet från mobilappen en gång. Besök vår webbplats om du vill skapa ytterligare webbplatser. - Välj domän - Din enhet stöds inte. Kontakta support för mer detaljer Något gick fel med appkonfigurationen. Kontakta supporten för mer information + Din enhet stöds inte. Kontakta support för mer detaljer Appen kunde inte aktivera kortläsaren, eftersom NFC-chippet är inaktiverat Transaktionen avbröts Den köpta domänen kommer att omdirigera användare till + Välj domän Lägg till en domän - Din gratis butiksadress + Domänerna för din webbplats Primär webbplatsadress + <a href=\'\'><u>Lär dig mer</u></a> om domäner och hur man vidtar domänrelaterade åtgärder. Sök efter en domän Den köpta domänen kommer omdirigera användare till din primära adress. Gör anspråk på domän - <a href=\'\'><u>Lär dig mer</u></a> om domäner och hur man vidtar domänrelaterade åtgärder. - Domänerna för din webbplats Ditt paket inkluderar en gratis domännamnsregistrering i ett år. Gör anspråk på din gratisdomän + Din gratis butiksadress Domäner Visa inte igen Påminn mig senare + Inga problem! Du kan alltid gå till Inställningar i menyn för att skicka feedback. Dela feedback Dela feedback - Berätta vad du tycker - Inga problem! Du kan alltid gå till Inställningar i menyn för att skicka feedback. Berätta för oss om din upplevelse av personliga betalningar. + Berätta vad du tycker Betygsätt din första upplevelse av personliga betalningar. Gillar du att använda personliga betalningar? Dela din egen upplevelse av att ta emot personliga betalningar. @@ -930,6 +946,7 @@ Language: sv_SE Jag säljer redan men inte online Jag har precis startat mitt företag Kläder och accessoarer + Annat Det finns inga tillgängliga resultat för den här sökningen Ange en kategori %1$s Kopiera @@ -938,41 +955,40 @@ Language: sv_SE Det går inte att duplicera produkten Duplicera Förbereder för betalning - Annat - Domän Det går snabbt Förbereder den inbyggda läsaren… Den inbyggda läsaren är redo + Kortläsare Tryck för att betala Omvandlingsfrekvens Sessioner Inga sessioner denna period Jämfört med - Kortläsare + Domän + Vad är applikationslösenord? Länder Nuvarande plats - Du kan välja flera stycken. - Vad är applikationslösenord? Vi använder den här informationen för att konfigurera betalningar, frakt och moms. + Du kan välja flera stycken. Vilken eller vilka plattformar\nanvänder du för närvarande? + Välj en kategori som definierar\nditt företag bäst. Det verkar som att funktionen Applikationslösenord är inaktiverad på din webbplats %1$s.\n Aktivera den om du vill använda WooCommerce-appen. Öppna installationssida - Välj en kategori som definierar\nditt företag bäst. Skannad QR-kod är inte en WooCommerce-kod - Skapande av butik är inte tillgängligt Att skapa butik från mobilappen är för närvarande inte tillgängligt för ditt land. - Svar skickat! - Det uppstod ett fel när svaret skulle skickas - Vi gör oss redo för att skapa din butik + Skapande av butik är inte tillgängligt Förbli ansluten. + Vi gör oss redo för att skapa din butik + Det uppstod ett fel när svaret skulle skickas + Svar skickat! Svara + Välj alla Uppdatera pris Uppdatera status Status uppdaterad! Uppdatera status Pris uppdaterat! Uppdatera ordinarie pris - Välj alla 2 år Något gick fel under butiksinstallationen … Alla variationer har redan genererats. @@ -982,93 +998,93 @@ Language: sv_SE Kunde inte slutföra köpet … Det gick inte att ladda domänförslag Inga tillgängliga domäner för denna sökning - Generera alla varianter? Genererar varianter Detta kommer skapa en ny variation för varje möjlig kombination av variationsattribut (%1$d variationer). + Generera alla varianter? Skapande stöds för närvarande för högst %1$d variationer. Att generera variationer för den här produkten skulle skapa %2$d variationer. Gräns för generering överskriden Skapar variationer för alla kombinationer av dina attribut. + Generera alla variationer Skapa en ny variation. Ange manuellt vilka attribut som tillhör den variabla produkten. Lägg till ny variation Lägg till variation - Generera alla variationer - Försök att ansluta igen för att komma åt din butik. Lämna utan att ansluta Fortsätt ansluta + Försök att ansluta igen för att komma åt din butik. Jetpack är installerat men inte anslutet. Du har inte behörighet att ansluta Jetpack till den här butiken Kontakta din butikshanterare eller -administratör för att få hjälp. - Fel - Felkod %1$s - Din butik <b>%1$s</b> är nu ansluten till Jetpack. - Vänta medan vi ansluter din butik <b>%1$s</b> med Jetpack. - Ansluter Jetpack - Installerar Jetpack - Allt klart - Validerar - Anslut butik till Jetpack - Aktiverar - Installerar Jetpack Avbryt installation Försök auktorisera igen Försök att aktivera igen Försök installera igen Skaffa support Försök igen och kontakta supporten om detta fel fortsätter. - Du har inte behörighet att hantera tillägg på denna butik - Anslut Jetpack - Gå till butik Ett fel uppstod vid kommunikationen med din webbplats. + Du har inte behörighet att hantera tillägg på denna butik Det gick inte att auktorisera anslutningen till Jetpack Det gick inte att aktivera Jetpack Det gick inte att installera Jetpack + Anslut Jetpack + Gå till butik + Fel + Felkod %1$s + Din butik <b>%1$s</b> är nu ansluten till Jetpack. + Vänta medan vi ansluter din butik <b>%1$s</b> med Jetpack. Installera Jetpack Jetpack har anslutits + Ansluter Jetpack + Installerar Jetpack + Allt klart Ansluten + Validerar + Anslut butik till Jetpack + Aktiverar + Installerar Jetpack Logga in på <b>%1$s</b> med autentiseringsuppgifterna för din butik för att ansluta Jetpack. Logga in på <b>%1$s</b> med autentiseringsuppgifterna för din butik för att installera Jetpack. - Skapa din första butik - Anslut din butik till Jetpack för att komma åt den på denna app. Ha autentiseringsuppgifterna för din butik redo. + Anslut din butik till Jetpack för att komma åt den på denna app. Installera det kostnadsfria Jetpack-tillägget för att komma åt din butik med den här appen. Kom igång och börja sälj snabbt med en vacker onlinebutik. - Om din butik - Din butik har skapats! + Skapa din första butik Butiksnamn - Hantera min butik Oroa dig inte, du kan alltid ändra det senare. Vilket är ditt butiksnamn? + Om din butik Butiksförhandsgranskning + Hantera min butik + Din butik har skapats! Ett fel uppstod under installationen. Försök igen … - år - månad - Fortsätt till betalning Försäljningskanaler - Min butik - Prenumerationer och presentkort - Obegränsat antal produkter - Flera betalningsalternativ - Aldrig - Alltid - Alla funktioner du behöver, redan inbyggda. Fraktsedlar + Flera betalningsalternativ E-handelsrapporter + Prenumerationer och presentkort + Obegränsat antal produkter Premium-teman + år + månad Det finns ingen risk. Om du avbryter inom 30 dagar får du en fullständig återbetalning. Skapa butik för %1$s Drivs med + Alla funktioner du behöver, redan inbyggda. Det gick inte att läsa in paketinformationen … + Fortsätt till betalning Din butik kommer att skapas utifrån de alternativ du väljer. + Min butik Slumpmässigt + Aldrig + Alltid Uppdatera simulerad läsarnyckel Uppdatera simulerad kortläsare Anslut Jetpack Anslut butik + Förslag Skriv en domän + Det är här människor kommer hitta dig på Internet. Oroa dig inte, du kan ändra detta senare. Välj en domän Besökare - Förslag - Det är här människor kommer hitta dig på Internet. Oroa dig inte, du kan ändra detta senare. Bekräfta och lämna Du kommer att förlora all din butiksinformation. Vill du lämna? @@ -1077,19 +1093,20 @@ Language: sv_SE Skapa en ny butik Eller logga in med lösenord Skanna QR-kod för att logga in - Simulerad läsarnyckel - Nuvarande lagerantal är %d Den simulerade kortläsaren har inaktiverats + Simulerad läsarnyckel Lagerkvantiteten har uppdaterats Den aktuella lagerkvantiteten är blandad + Nuvarande lagerantal är %d Uppdaterar lagerkvantiteten Lagerkvantiteten kommer att uppdateras för %d variationer Lagersaldo - Lägg till en butik - Skapa en ny butik - Anslut en befintligt butik Sök filtrerade produkter Sök filtrerade beställningar + Anslut en befintligt butik + Skapa en ny butik + Lägg till en butik + Nettoförsäljning: %1$s Sålda artiklar Produkter Produkter @@ -1105,104 +1122,106 @@ Language: sv_SE Intäkt %1$s – %2$s Se mer + Vi kunde inte skapa ett konto med de angivna autentiseringsuppgifterna. Prova med en annan e-postadress. + Ditt lösenord uppfyller inte våra säkerhetsriktlinjer. Försök med ett mer komplext lösenord. Ditt lösenord är för kort. Välj ett lösenord som har minst 6 tecken. Ange en giltig e-postadress. Ett konto med denna e-post finns redan. - Ditt lösenord uppfyller inte våra säkerhetsriktlinjer. Försök med ett mer komplext lösenord. - Nettoförsäljning: %1$s - Vi kunde inte skapa ett konto med de angivna autentiseringsuppgifterna. Prova med en annan e-postadress. Skapa ett konto Försök med en annan adress Anpassat datumintervall Anpassat + Vad är WordPress.com? Skapar nytt konto Välj ett lösenord Din e-postadress Kom igång \npå några minuter Genom att klicka på knappen Anslut Jetpack godkänner du våra <a href=\'terms\'>användarvillkor</a> och samtycker till att <a href=\'sync\'>dela information</a> med WordPress.com. - Vad är WordPress.com? + Aktivera simulerad kortläsare + Kontakta webbplatsens ägare för en inbjudan till webbplatsen som butikschef eller administratör för att använda appen. Ansluter till en WordPress.com-webbplats Anslut till webbplatsen Anslut Jetpack till ditt konto - Kontakta webbplatsens ägare för en inbjudan till webbplatsen som butikschef eller administratör för att använda appen. - Aktivera simulerad kortläsare - Skanna QR-kod för att logga in - Visa lösenord - Dölj lösenord Redigera behörigheter För att använda den här funktionen, tillåt att din kamera används. Kameraåtkomst krävs Kamerastreckkodsskanner Utvecklaralternativ 2FA stöds inte för webbplatser som drivs på egen server. Använd ett applösenord. + Skanna QR-kod för att logga in + Visa lösenord + Dölj lösenord Per den %1$s Det gick inte att ladda data + WooCommerce-statistik idag Dagens butiksstatistik Butiksanalys inte tillgänglig! Uppgradera till den senaste versionen av WooCommerce för att visa din butiksanalys. Ditt nätverk är inte tillgängligt.\nKontrollera din data eller WiFi-anslutning. Logga in på WooCommerce-appen - WooCommerce-statistik idag - Verifierar Jetpack-anslutning … Det gick inte att hämta anslutningsdata … + Verifierar Jetpack-anslutning … Kan inte verifiera din Jetpack-anslutning. Försök igen. Webbplatsen %1$s har för närvarande ett WordPress.com-paket som inte stöder installation av tillägg. Uppgradera ditt paket för att använda WooCommerce. Det verkar som att ditt konto inte är anslutet till Jetpack för %1$s KORTLÄSARE BETALNINGSALTERNATIV - Vi kunde inte ansluta till din webbplats. Kontakta supporten för att felsöka problemet. Kassaalternativet Betala personligen gör det möjligt att ta emot betalningar för webbplatsbeställningar vid upphämtning eller leverans. <a href=\'\'>Läs mer</a> Betala personligen + Vi kunde inte ansluta till din webbplats. Kontakta supporten för att felsöka problemet. Anslutningsfel Det finns ett problem som kräver din uppmärksamhet. <a href=\'\'>Ta en titt på detta</a> Försök med en annan adress Det gick inte att aktivera Postförskott. Försök igen senare. Aktivera Betala personligen - Ett fel uppstod, kontakta support - Ange en webbplatsadress <a href=\'\'>Läs mer</a> om Personliga betalningar Är WooCommerce nytt för dig? + Ett fel uppstod, kontakta support + Ange en webbplatsadress Få en inloggningslänk via e-post Kommer du inte ihåg ditt lösenord? Vi har noterat att du inte har slutfört installationen av Personliga betalningar. <a href=\'\'>Fortsätt installationen</a> Betalningar Jag förstår! + Now you can quickly access In-Person Payments and other features with ease Betalningar från menyfliken + Din e-post används inte med ett WordPress.com-konto. Andra webbplatser Logga in med din butiksadress WC-admin - Now you can quickly access In-Person Payments and other features with ease - Din e-post används inte med ett WordPress.com-konto. + Vi har precis skickat en magisk länk till Kontrollera din e-post på denna enhet! Använd lösenord för att logga in Logga in med magisk länk - Logga in med dina webbplatsuppgifter Vi har precis skickat en magisk länk till din e-postadress. Tryck på länken i e-postmeddelandet för att logga in. - Vi har precis skickat en magisk länk till + Logga in med dina webbplatsuppgifter + Ge dina kunder användbara och relevanta produktrekommendationer genom att lägga till merförsäljning och korsförsäljning + Öka din försäljning med länkade produkter + Börja sälja personligen på under 20 minuter med vår kortläsare. + Det gick inte att uppdatera beställning #%1$d + Beställning #%1$d har markerats som slutförd + Markera\nslutförd Installera WooCommerce + Det verkar som att %1$s inte är en WooCommerce-webbplats. + Växla mellan flera olika butiker Hantera mina beställningar Skapa eller uppdatera mina produkter + Kolla min analys + Försöker skapa en butik + Utforskar bara + Vad för dig till WooCommerce? Tips Ställ in nu - Öka din försäljning med länkade produkter - Börja sälja personligen på under 20 minuter med vår kortläsare. - Vad för dig till WooCommerce? - Utforskar bara - Försöker skapa en butik - Kolla min analys - Växla mellan flera olika butiker - Det verkar som att %1$s inte är en WooCommerce-webbplats. - Markera\nslutförd - Beställning #%1$d har markerats som slutförd - Det gick inte att uppdatera beställning #%1$d - Ge dina kunder användbara och relevanta produktrekommendationer genom att lägga till merförsäljning och korsförsäljning Då sätter vi igång! Logga in med WordPress.com Kontakta supporten + Logga in med ditt WordPress.com-konto Skaffa lite hjälp! Har du problem med att logga in? + Artikelnr Alla produkter VISA INTE IGEN PÅMINN MIG SENARE + Inga problem! Du kan alltid komma igång med In-Person Payments via Inställningar. Personliga betalningar Köp kortläsare Ta emot betalningar enkelt @@ -1210,29 +1229,27 @@ Language: sv_SE Avfärda Visa anpassade fält Anpassade fält - Installera Jetpack - Du kan hantera dem snabbt och enkelt - Vi vet att det är viktigt för ditt företag Det gick inte att spara ändringar Detta kommer att markera denna beställning som betald om du fått betalning utanför WooCommerce - Artikelnr - Inga problem! Du kan alltid komma igång med In-Person Payments via Inställningar. + Installera Jetpack Vi gör det möjligt för dig att bearbeta betalningar på ett enkelt och smidigt sätt + Du kan hantera dem snabbt och enkelt + Vi vet att det är viktigt för ditt företag Är WooCommerce nytt för dig? - Logga in med ditt WordPress.com-konto - Du har en ny beställning! 🎉 Ny beställning för 50 USD i din WooCommerce-butik + Du har en ny beställning! 🎉 detaljerna Redigera alla %1$s genom att gå igenom beställningen i din WooCommerce-butiksadmin %1$s är ofullständiga - Fortsätt söka - Inväntar betalning Dela systemstatusrapport Kopiera systemstatusrapport till urklipp + Fortsätt söka + Personlig betalning för beställning #%1$s för %2$s blog_id %3$s. Ändra betalningsleverantör Återbetalat: %1$s - Personlig betalning för beställning #%1$s för %2$s blog_id %3$s. + Inväntar betalning Fortsätt med installation + Saker du bör veta innan du installerar Installera utökning WooCommerce Shipping Rensa filter @@ -1241,26 +1258,30 @@ Language: sv_SE Det gick inte att ladda in produkter Sök produkter Filter (%d) - Saker du bör veta innan du installerar Vissa e-postadresser är ogiltiga. Åtgärda den angivna e-postadressen/de angivna e-postadresserna. Lista över tillåtna fakturerings-e-postadresser att kontrollera mot när en beställning görs. Separera e-postadresser med kommatecken. Du kan också använda en asterisk (*) för att matcha delar av en e-postadress. Exempelvis skulle \"*gmail.com\" matcha alla gmail-adresser. + Inga produkter matchar de valda filtren ” Bekräfta betalningsmetod Stripe WooCommerce Payments - Inga kunder hittades. + Personliga betalningar kan behandlas via båda dessa betalningsleverantörer. Vilken leverantör vill du använda? Välj din betalningsleverantör låst - Delar av denna beställning är för närvarande inte redigerbara - Inga produkter matchar de valda filtren ” - Personliga betalningar kan behandlas via båda dessa betalningsleverantörer. Vilken leverantör vill du använda? För att redigera produkter eller betalningsinformation, ändra statusen till Inväntar betalning. + Delar av denna beställning är för närvarande inte redigerbara Sök efter kunder + Inga kunder hittades. Inte nu Lägg till utökningar till butik Vad är WooCommerce Shipping? Kom åt rabatterade fraktavgifter. För närvarande endast tillgängligt med DHL och USPS, men fler alternativ kommer snart. + Rabatterade avgifter + Hämta en beställning och sedan är det bara att betala, skriva ut, paketera och skicka. Skriv ut från din telefon + Du behöver inte undra var den frimärkshäftet tog vägen. + Köp porto när du behöver det Spara tid och pengar + Fullfölj dina beställningar med WooCommerce Shipping Visa detaljer Välj variant %s Exkludera produktkategorier @@ -1269,10 +1290,13 @@ Language: sv_SE Inga begränsningar Tillåtna e-postadresser Obegränsat + Samtliga kvalificerande artiklar Obegränsat Ingen Redigera produktkategorier (%1$d) Välj produktkategorier + För närvarande stöds massuppdatering för maximalt 100 varianter. + Gräns för massuppdatering har överskridits Uppdatera ordinarie priser Uppdaterar reapriser Uppdaterade reapriser. @@ -1290,14 +1314,9 @@ Language: sv_SE Massuppdatera OK Massuppdatera … - Du behöver inte undra var den frimärkshäftet tog vägen. - Köp porto när du behöver det - För närvarande stöds massuppdatering för maximalt 100 varianter. - Gräns för massuppdatering har överskridits - Hämta en beställning och sedan är det bara att betala, skriva ut, paketera och skicka. - Fullfölj dina beställningar med WooCommerce Shipping - Rabatterade avgifter - Samtliga kvalificerande artiklar + Hämtar varianter … + Det gick inte att söka efter produktkategorier + Det gick inte att läsa in produktkategorier Sök kategorier Rensa val Klicka för att avmarkera @@ -1305,17 +1324,15 @@ Language: sv_SE Välj %1$d kategorier Inga produktkategorier hittades Välj kategorier - Behöver du en fraktetikett? + Avfärda banner för att installera WC Shipping Skaffa WooCommerce Shipping Skriv ut etiketter från din telefon, med WooCommerce Shipping. - Hämtar varianter … - Det gick inte att söka efter produktkategorier - Det gick inte att läsa in produktkategorier - Avfärda banner för att installera WC Shipping + Behöver du en fraktetikett? + Ändra produktantalet från %1$d till %2$d Uppdatera ordinarie pris Uppdatera reapris - Ändra produktantalet från %1$d till %2$d Vi stöder inte WooCommerce Stripe-utökningen i %1$s + Filter Rensa val Välj %d produkt Välj %d produkter @@ -1324,31 +1341,32 @@ Language: sv_SE Redigera produkter (%d) Alla produkter Välj produkter - Exkludera artiklar på rea Aktivera det här alternativet om rabattkoden inte ska gå att tillämpa på reavaror. Per-artikel-rabattkoder fungerar bara om varan inte är en reavara. Per-varukorg-rabattkoder fungerar bara om varorna i varukorgen inte är reavaror. + Exkludera artiklar på rea Aktivera det här alternativet om rabattkoden inte ska kunna användas tillsammans med andra rabattkoder. - Filter - Vänta … - Sparar rabattkod - Rabattkod uppdaterad - Användningsbegränsningar - Ingen - Tillämpa denna rabattkod på - Välj butik att ansluta - %s i lager - Det gick inte att hämta butiker - Inkludera gratis frakt? - Rensa Kan inte kombineras Användningsbegränsning per kund Begränsa användningen till X artiklar Användningsbegränsning per rabattkod Maximalt beställningsbelopp (%1$s) Minsta beställningsbelopp (%1$s) + Vänta … + Sparar rabattkod Det gick inte att uppdatera rabattkoden + Rabattkod uppdaterad + Användningsbegränsningar Användningsinformation + Inkludera gratis frakt? + Rensa + Ingen + Tillämpa denna rabattkod på Det gick inte att söka efter rabattkoder Det gick inte att hämta rabattkoder + Det gick inte att hämta butiker + Välj butik att ansluta + %s i lager + Lägg till beskrivningen för rabattkoden. + Rabattkodsbeskrivning Redigera beskrivning Lägg till beskrivning (valfritt) Rabattkodens utgångsdatum @@ -1359,90 +1377,93 @@ Language: sv_SE Ställ in beloppet på rabatten du vill erbjuda. Ställ in procenten för rabatten du vill erbjuda. Belopp (%1$s) + Rabattkodsinformation Redigera %1$s Redigera rabattkod Kan inte uppdatera produkt - Lägg till beskrivningen för rabattkoden. - Rabattkodsbeskrivning - Rabattkodsinformation Något gick fel när återbetalningen skulle tillämpas Tillämpar återbetalning för beställning - Kopierat till urklipp + Kortläsarbild Beräknat belopp: %s Beräkna som procent - Kortläsarbild - Kan användas %1$d gång - Kan användas %1$d gånger - Rabattkod borttagen - Misslyckades att ta bort rabattkod - Är du säker på att du vill ta bort denna rabattkod? - Ta bort rabattkod - Avfärda alla - Inkorg + Kopierat till urklipp Begränsad till kunder med följande e-postadresser: %1$s Gäller inte reavaror Tillåter gratis frakt Kan inte kombineras Begränsad till %1$d artiklar i varukorgen Begränsad till %1$d artiklar i varukorgen + Kan användas %1$d gång + Kan användas %1$d gånger %1$d användning per användare %1$d användningar per användare + Rabattkod borttagen + Misslyckades att ta bort rabattkod + Är du säker på att du vill ta bort denna rabattkod? + Ta bort rabattkod Det gick inte att synkronisera inkorgen + Avfärda alla + Inkorg Kortläsarmanualer Tryck eller infoga för att återbetala Beställningen är redan återbetald - OK Återbetalning avbruten - Denna återbetalning kunde inte behandlas - Kortet stöder inte denna typ av återbetalning - Prova ett annat sätt att återbetala + OK Systemtestkort är inte tillåtna för återbetalningen Återbetalningsbeloppet är inte tillåtet för det aktuella kortet Återbetalningen avvisades på grund av otillräckliga medel En identisk återbetalning skickades nyligen + Kortet stöder inte denna typ av återbetalning Återbetalningen avvisades av ospecificerad anledning + Prova ett annat sätt att återbetala Återbetalningen avvisades av okänd anledning - Kopiera + Denna återbetalning kunde inte behandlas Återbetalning lyckades - Återbetalning misslyckades Behandlar återbetalning Återbetala betalning + Återbetalning misslyckades Förbereder återbetalning av betalning + Kopiera Sök rabattkoder - Rabattkod - Löper ut %1$s - Anpassad rabatt (%1$s) - Fast produktrabatt - Fast varukorgsrabatt - Dela rabattkod - Kopiera rabattkod - Rabattkod kopierad till urklipp. Det gick inte att generera meddelandet för delning av rabattkoden Det gick inte att dela rabattkoden. Tillämpa %1$s rabatt på utvalda produkter med kampanjkoden %2$s Tillämpa %1$s rabatt på alla produkter med kampanjkoden %2$s Det gick inte att kopiera rabattkoden till urklipp. + Rabattkod kopierad till urklipp. + Rabattkod Det gick inte att ladda rabattkodssammanfattningen + Löper ut %1$s %1$s rabatt på %2$s - Procentuell rabatt + Anpassad rabatt (%1$s) + Fast produktrabatt + Fast varukorgsrabatt + Procentuell rabatt Det gick inte att ladda rabattkodsprestanda + Dela rabattkod + Kopiera rabattkod Tack för din feedback! - Dela betalningslänk Kortläsarbetalningar kräver exakt platsbehörighet Åtkomst till plats krävs Kassa – %s + Dela betalningslänk Belopp Belopp Rabatterade beställningar + Prestanda + Maximalt beställningsbelopp på %s + Minsta beställningsbelopp på %s + Rabattkodssammanfattning + Visa rabattkodssammanfattning + Vi har arbetat med att göra det möjligt att visa och redigera rabattkoder från din enhet! Visa och redigera rabattkoder Inga rabattkoder hittades - Rabattkoder - %d kategorier - %d kategori - Visa rabattkodssammanfattning + %1$s exkl. %2$s + %1$s och %2$s allt Har löpt ut Aktiva + Rabattkoder Skapades den %s För %d dagar sedan För en dag sedan @@ -1450,17 +1471,12 @@ Language: sv_SE För en timme sedan För %d minuter sedan För en stund sedan + %d kategorier + %d kategori \u2022 inga godkända recensioner \u2022 en godkänd recension \u2022 %d godkända recensioner %1$s (%2$s%%) - Prestanda - Maximalt beställningsbelopp på %s - Minsta beställningsbelopp på %s - Rabattkodssammanfattning - Vi har arbetat med att göra det möjligt att visa och redigera rabattkoder från din enhet! - %1$s och %2$s - %1$s exkl. %2$s Vi har jobbat på att göra det möjligt att skapa beställningar från din enhet. Du kan prova den här funktionen genom att trycka på \"+\"-knappen Kom tillbaka snart för fler tips och insikter om hur du får din butik att växa Grattis, du har läst allt! @@ -1473,12 +1489,12 @@ Language: sv_SE Anrop med XML-RPC verkar blockerade på denna webbplats (felkod 401). Om försöket att logga in misslyckas tryck på hjälpikonen för att se vanliga frågor. Kunde inte läsa WordPress-webbplatsen på denna URL. Tryck på hjälpikonen för att se vanliga frågor. Tjänsten för XML-RPC är inaktiverad på denna webbplats. - Procent (%) Använd en e-postadress som inte är kopplad till Automattic för att skicka in ett supportärende Vi stöder inte Stripe-konton som är registrerade i %1$s + Vi stöder inte WooCommerce Payments-utökning i %1$s Tryck på strömknappen på din läsare Ett kvitto har skickats till <strong>%s</strong> - Vi stöder inte WooCommerce Payments-utökning i %1$s + Procent (%) Ta bort avgift från beställning Ta bort frakt från beställning Frakt @@ -1488,9 +1504,11 @@ Language: sv_SE Avgifter Kunddetaljer Lägg till avgift + Redigera kundanteckning Redigera kunddetaljer Redigera beställningsstatus - Redigera kundanteckning + Beställningen med enkel betalning gick inte att uppdatera + Beställningen med enkel betalning gick inte att skapa Användarens profilbild Recensioner Visa butik @@ -1498,31 +1516,29 @@ Language: sv_SE Analyser Betalningar WooCommerce-admin - Beställningen med enkel betalning gick inte att uppdatera - Beställningen med enkel betalning gick inte att skapa Meny Uppdatera efter uppdatering Hantera tillägg WooCommerce Payments - eller WooCommerce Stripe Gateway Personliga betalningar fungerar endast med ett av följande tillägg aktiverat. Kontakta en webbplatsadministratör för att inaktivera ett av dessa tillägg och fortsätta: Personliga betalningar fungerar endast med ett av följande tillägg aktiverat. Inaktivera ett av dessa tillägg för att fortsätta. Konflikt mellan betalningstillägg upptäckt Momser totalt + eller Installera Jetpack - Vänta … - Skapar din beställning - Skapa + Personliga betalningar är för närvarande inte tillgängliga Beställning skapad Misslyckades att skapa beställning - Personliga betalningar är för närvarande inte tillgängliga + Vänta … + Skapar din beställning Beställningens totalsumma Produktbelopp Betalning Nettoförsäljning: %s Sålda artiklar Konvertering + Skapa App-ikon Ikon för tillbaka Logga för Automattic @@ -1553,69 +1569,58 @@ Language: sv_SE Kontakta supporten Aktivera Installera + %s Jetpack i WP Admin aktivera installera - %s Jetpack i WP Admin + Alternativt kan du %s Jetpack i WP-admin. Försök igen. ansluter aktivering installation - WooCommerce - Alternativt kan du %s Jetpack i WP-admin. Något gick fel under %s Hej! Här är en länk för att ladda ner WooCommerce-appen. Jag tycker att den är jättebra och tänkte att du också skulle gilla den. %1$s - review_card_%1$s - review_card_detail - product_card_%1$s + WooCommerce product_card_detail + product_card_%1$s + review_card_detail + review_card_%1$s Uppdatera Stripe - Välj variant - Ta bort produkt från beställning - Lägg till rabatt - Produkt Du är nästan där! Slutför konfigurationen av Stripe för att börja ta emot kortbetalningar. Slutför konfigurationen av Stripe i adminpanelen för din butik Återbetala avgifter Avgiftsåterbetalning + Välj variant + Ta bort produkt från beställning + Lägg till rabatt + Produkt + Lägg till en annan leveransadress I lager %s i lager + Lägg till produkter Produkter + Lägg till kunddetaljer Kund Markera som betald + Detta kommer att skapa din beställning och markera den som slutförd om du har mottagit betalning utanför WooCommerce Markera som betald? Välj din betalningsmetod - Moms (%s&nbsp;%%) - Lägg till en annan leveransadress - Detta kommer att skapa din beställning och markera den som slutförd om du har mottagit betalning utanför WooCommerce Moms beräknas automatiskt baserat på din butiksadress - Lägg till kunddetaljer - Lägg till produkter - Anpassat belopp - Ange e-post - Moms - Kontant - Kort + Moms (%s&nbsp;%%) Ta emot betalning %s Debitera moms + Anpassat belopp + Ange e-post Enkel betalning - order_card_%1$s order_card_detail + order_card_%1$s + Kontant + Kort + Moms E-post - Uppdatera efter uppdatering Lägg till anteckning Kundmeddelande + Uppdatera efter uppdatering En föråldrad version av utökningen WooCommerce Stripe Gateway är installerad för din butik. Uppdatera den för att ta emot personliga betalningar. - Ny beställning - Förra veckan - År till datum - Kvartal till datum - Månad till datum - Vecka till datum - Förra året - Förra kvartalet - Förra månaden - Kortet har löpt ut - %1$s (%2$s) Betalningen avvisades av okänd anledning Ett live-kort användes på en webbplats i testläge Systemtestkort är inte tillåtna för betalning @@ -1624,6 +1629,7 @@ Language: sv_SE Betalningsbeloppet är inte tillåtet för det aktuella kortet Betalningen avvisades på grund av otillräckliga medel Transaktionens postnummer och kortets postnummer matchar inte + Kortet har löpt ut En identisk transaktion skickades nyligen Kortet stöder inte denna valuta Kortet stöder inte denna typ av köp @@ -1631,71 +1637,82 @@ Language: sv_SE Betalningen avvisades av ospecificerad anledning Prova en annan betalningsmetod Det kan fungera att prova igen + Ny beställning Väljare för datumintervallsfilter kontra föregående period (%1$s) + %1$s (%2$s) + År till datum + Kvartal till datum + Månad till datum + Vecka till datum + Förra året + Förra kvartalet + Förra månaden + Förra veckan + Enkla betalningar Visa utökningar Vi kunde inte hitta några beställningar Öppna inställningar - Enkla betalningar Saknar nödvändig behörighet för enheter i närheten + Filtrera länder + Filtrera stater + Status Slutdatum Startdatum Välj datum Anpassat intervall + Skapa en beställning med minimalt med information + Enkel betalning Skapa en ny manuell beställning Skapa beställning Skapa beställning Ange belopp - Filtrera länder - Filtrera stater - Status - Skapa en beställning med minimalt med information - Enkel betalning Ta emot betalning Enkel betalning - Analys Skapa beställningar från din enhet! + Analys + Allt klart Ansluter din butik Aktiverar Installerar Jetpack - Installera Jetpack - din webbplats - Installerar\nJetpack - Läsare är ansluten - Allt klart Vänta medan vi ansluter %s till Jetpack. + Installerar\nJetpack + din webbplats Installera det kostnadsfria Jetpack-tillägget till <strong>%s</strong> för den bästa mobilupplevelsen. + Installera Jetpack Produktrecensionerna kunde inte hämtas Läsaren är frånkopplad + Läsare är ansluten Att avbryta en pågående programvaruuppdatering är inte att rekommendera. Om du avbryter blockeras din läsaranslutning. Uppdateringen av läsarprogramvaran misslyckades, eftersom läsarens batteri inte är tillräckligt laddat. Ladda läsaren till över 50 %% innan du försöker igen. - Ladda läsare Uppdateringen av läsarprogramvaran misslyckades, eftersom läsarens batteri endast är laddat till %1$s%%. Ladda läsaren till över 50 %% innan du försöker igen. - %1$s (%2$d) - Alla - Filtrerade beställningar - Alla beställningar - Kontrollera din mobila enhet - Senaste 30 dagarna - Senaste 7 dagarna - Senaste 2 dagarna - Idag - Ange adress + Ladda läsare Din kortläsares programvara behöver uppdateras för att fungera korrekt Ange ett giltigt postnummer i dina butiksinställningar och försök igen Postnumret i butiksadressen är ogiltigt + Ange adress Ange din butiksadress för att fortsätta + Kontrollera din mobila enhet Adressen kan inte uppdateras med en tom e-postadress. Kontrollera att du kör den senaste versionen av WooCommerce. + Senaste 30 dagarna + Senaste 7 dagarna + Senaste 2 dagarna + Idag + %1$s (%2$d) + Alla + Filtrerade beställningar + Alla beställningar Inte nu Installera Jetpack - Användarprofiler Tillåt flera användare att komma åt WooCommerce Mobile. + Användarprofiler Nya analysvyer gör det möjligt att se besökare, rapporter med mera. Analys Få push-notiser för nya beställningar, recensioner med mera levererade till din enhet. Push-notiser Installera det kostnadsfria Jetpack-tillägget för den bästa mobilupplevelsen. Få ut så mycket som möjligt av din butik + Använd som leveransadress Använd som faktureringsadress Filter (%d) Filter @@ -1708,14 +1725,14 @@ Language: sv_SE Visa beställningar Filtrerade beställningar Alla beställningar - Använd som leveransadress Berätta mer om %s … - Läsarens serienummer kopierad till urklipp Beskriv din produkt för dina framtida kunder… - Lägg till leveransadress + Läsarens serienummer kopierad till urklipp Lägg till faktureringsadress - Faktureringsadress + Lägg till leveransadress Lägg till kundanteckning + Faktureringsadress + Leveransadress Adress Land Postnummer @@ -1727,9 +1744,7 @@ Language: sv_SE E-post Efternamn Förnamn - Leveransadress Redigera en kundbeställningsanteckning - OK Det gick inte att hämta SSR. Kontrollera WooCommerce -> Status i WP-admin. Det gick inte att dela systemstatusrapporten Det gick inte att kopiera SSR till urklipp @@ -1740,56 +1755,57 @@ Language: sv_SE Grattis, du kan nu ta emot kredit- och betalkortsbetalningar med WooCommerce Payments! Ta emot betalningar med en kortläsare Belopp måste vara minst %1$s + OK + Bild på ny funktionsikon + Växla butik + Uppdatering av produkt %1$s misslyckades + %1$d bilder har lagts till för produkten %2$s Produkt uppdaterad Uppdaterar produkt %1$s - OK - Något gick fel - Uppdatering av produkt %1$s misslyckades - Spara kvitto och fortsätt Bilduppladdning kommer fortsätta i bakgrunden - Växla butik - %1$d bilder har lagts till för produkten %2$s - Bild på ny funktionsikon + Spara kvitto och fortsätt + OK Vi kan inte läsa in Beställningstillägg för närvarande - Kategori - Visa utökningar + Något gick fel Vad som är nytt i WooCommerce + Visa utökningar Vi kan tyvärr inte ändra den här funktionsinställningen för tillfället + Kategori Du har en ny recension! 🌟 Du har en ny beställning! 🎉 %d artikel - Sparar din produkt - %d produkter - %d produkt - Försök igen med ett annat kort - Försök igen med samma kort - Ta bort kortet - Se till att kortläsaren är ansluten. - %d arbetsdagar - %d arbetsdag %d merförsäljningsprodukter %d merförsäljningsprodukt %d korsförsäljningsprodukter %d korsförsäljningsprodukt + %d produkter + %d produkt Produktutökningar + Sparar din produkt Väntande recension + Se till att kortläsaren är ansluten. + Försök igen med ett annat kort Prova att trycka på, föra in eller svepa ditt kort Flera kort upptäckta. Prova igen med endast ett kort + Ta bort kortet + Försök igen med samma kort %d poster %d artikel + %d arbetsdagar + %d arbetsdag Vi kunde inte verifiera leveransadressen automatiskt: %s Vi kunde inte automatiskt verifiera ursprungsadressen. Visa adressen i Google Maps för att verifiera att den är korrekt. Vi arbetar på att göra det enklare för dig att se produkttillägg från din enhet! För tillfället kan du endast se tilläggen för dina beställningar. Du kan skapa och redigera dessa tillägg i din webbadminpanel. - Spara + Visa utökningar från din enhet! Om du byter namn på ett tillägg i din webbadminpanel, observera att vissa beställningar inte längre kommer att visa tillägget i appen. Visa utökningar - Visa utökningar från din enhet! - %d filer kunde inte laddas upp + Spara Ladda upp information (%d) - Media kunde inte hittas - <a href=\'\'>Läs mer</a> om att ladda upp bilder + %d filer kunde inte laddas upp %d fil kunde inte laddas upp + Media kunde inte hittas Du kan redigera produkttillägg i webbadminpanelen. + <a href=\'\'>Läs mer</a> om att ladda upp bilder Vi kunde inte verifiera personliga betalningar för den här butiken. Det gick inte att verifiera personliga betalningar för den här butiken. Firmware: %s @@ -1801,61 +1817,77 @@ Language: sv_SE Skriv ut fraktetikett Fraktetikett köpt! Skriv ut fraktetiketter - Håll din läsare laddad - Behöver du lite hjälp? <a href=\'\'>Kontakta support</a> - Svep, tryck eller sätt in kort - Läsare ansluten Personliga betalningar Det tar ungefär tre timmar att ladda din läsare + Håll din läsare laddad Din läsare går in i viloläge efter 10 minuter av inaktivitet. Det är bara att trycka på strömknappen för att återansluta den. Automatisk återanslutning Det är bara att svepa, trycka eller infoga kort på läsaren för att ta emot betalningar. + Svep, tryck eller sätt in kort Grattis, du kan nu ta emot kredit- och betalkortsbetalningar! + Läsare ansluten + Behöver du lite hjälp? <a href=\'\'>Kontakta support</a> <a href=\'\'>Läs mer</a> om att ta emot betalningar med din mobila enhet och att beställa kortläsare Personliga betalningar är inte tillgängliga i testläget. Stäng av det för att fortsätta. Personliga betalningar är för närvarande inte tillgängliga Det finns krav som inväntar granskning i ditt konto. Slutför dessa krav senast den %1$s för att fortsätta ta emot personliga betalningar. + Ditt konto har krav som inväntar granskning Du har minst ett försenat krav i ditt konto. Ta hand om det för att återuppta personliga betalningar. Personliga betalningar är för närvarande inte tillgängliga Du kommer att kunna ta emot personliga betalningar så snart vi har slutfört granskningen av ditt konto. - Ditt konto har krav som inväntar granskning - Uppdatera efter uppdatering - Uppdatera WooCommerce Payments Personliga betalningar är för närvarande inte tillgängliga Tyvärr kan vi inte erbjuda personliga betalningar för den här butiken. + Uppdatera efter uppdatering En föråldrad version av utökningen WooCommerce Payments är installerad för din butik. Uppdatera den för att ta emot personliga betalningar. + Uppdatera WooCommerce Payments Du är nästan där! Slutför konfigurationen av WooCommerce Payments för att börja ta emot personliga betalningar. - Ansluter till ditt konto - Uppdatera efter installation - Behöver du lite hjälp? <a href=\'\'>Kontakta support</a> - Uppdatera efter aktivering - Aktiverar paket - Välj ett paket att aktivera. - Alla tillgängliga paket har aktiverats - Installera WooCommerce Payments - Aktivera WooCommerce Payments Slutför konfigurationen av WooCommerce Payments i adminpanelen för din butik + Uppdatera efter aktivering Utökningen WooCommerce Payments är installerad för din butik, men den har inte aktiverats. Aktivera den för att ta emot personliga betalningar. + Aktivera WooCommerce Payments + Uppdatera efter installation Du måste installera den kostnadsfria utökningen WooCommerce Payments för din butik för att ta emot personliga betalningar. + Installera WooCommerce Payments <a href=\'\'>Läs mer</a> om att ta emot betalningar med din mobila enhet och att beställa kortläsare + Behöver du lite hjälp? <a href=\'\'>Kontakta support</a> Du kan fortfarande ta emot personliga kontanta betalningar genom att aktivera betalningsmetoden \"Postförskott\" i din butik + Vi stöder inte personliga kortbetalningar i %1$s + Ansluter till ditt konto Personliga betalningar Dubbelkolla måtten eller vikten på ditt paket eller prova att använda ett annat paket i Paketinformation Det finns inga fraktavgifter tillgängliga - Vi stöder inte personliga kortbetalningar i %1$s - Stäng - Obligatoriskt fält - Attribut skapade - Ingen internetanslutning - Ingen anslutning till server - Denna betalning kunde inte bearbetas + Alla tillgängliga paket har aktiverats + Aktiverar paket + Välj ett paket att aktivera. + Obligatoriskt fält + Stäng Variationen har skapats Generera ny variation Du kan nu skapa och hantera produktvariationer! Generera variation Nu när du har lagt till attribut kan du skapa din första variation! - Att avbryta en pågående programvaruuppdatering är inte att rekommendera + Attribut skapade %1$s%% slutförd + Att avbryta en pågående programvaruuppdatering är inte att rekommendera + Denna betalning kunde inte bearbetas + Ingen anslutning till server + Ingen internetanslutning + Skicka i originalförpackning + Lägg till i nytt paket + Denna vara finns för närvarande i %s. Vart vill du flytta den? + Avbryt + Flytta + Flytta vara + ”%1$s” sparat + Misslyckades att skapa paket. Försök igen. + Misslyckades att skapa paket: okänt API-problem. + Misslyckades att skapa paket: %1$s + Vänta … + Skapar nytt paket + Ogiltigt värde. + Detta fält är obligatoriskt. + Vikt för tomt paket + Tom förpackningsvikt (%1$s) Höjd (%1$s) Bredd (%1$s) Längd (%1$s) @@ -1864,148 +1896,134 @@ Language: sv_SE Låda Välj pakettyp Pakettyp - Avbryt - Flytta - Vänta … - Skapar nytt paket - Ogiltigt värde. - Detta fält är obligatoriskt. - Vikt för tomt paket + Konfigurera paketet som du kommer att använda för att skicka dina produkter. Vi kommer att spara den för framtida beställningar. Lägg till nytt paket Skapa nytt paket - Misslyckades att skapa paket. Försök igen. - Misslyckades att skapa paket: okänt API-problem. - Misslyckades att skapa paket: %1$s - ”%1$s” sparat - Skicka i originalförpackning - Lägg till i nytt paket - Denna vara finns för närvarande i %s. Vart vill du flytta den? - Flytta vara - Tom förpackningsvikt (%1$s) Paketets mått måste vara större än noll. Uppdatera måtten för din vara i sektionen Frakt på din produktsida för att fortsätta. - Konfigurera paketet som du kommer att använda för att skicka dina produkter. Vi kommer att spara den för framtida beställningar. + Originalförpackning + Varumått + Vara som skickas för sig + Beställningsstatus uppdaterad Skicka Skriv ut Se kvitto Avbryt ändå - Beställningsstatus uppdaterad - Originalförpackning - Varumått - Vara som skickas för sig - Slå på Bluetooth Det gick inte att uppdatera läsarens programvara Sökningen efter programvaruversionsuppdateringar misslyckades <a href=\'\'>Läs mer</a> om att ta emot mobilbetalningar och beställa kortläsare + Slå på Bluetooth + Ingen läsare ansluten Vi kunde inte ansluta din läsare Anslut Flera läsare hittade Beställningen är redan betald - Ingen läsare ansluten - Betalningsmetod har lagts till - Lägg till ett kreditkort - Ange ett giltigt telefonnummer Tack för ditt köp! Klicka på länken nedan för att visa ditt betalningskvitto.\n\n%s Det gick inte att ladda ner tullformuläret Skriv ut tullfaktura Skriv ut tullformulär Ett tullformulär måste skrivas ut och inkluderas i denna internationella försändelse Tullformulär + Betalningsmetod har lagts till + Lägg till ett kreditkort + Ange ett giltigt telefonnummer Skriv ut tullformulär - Ditt kvitto från %s - Lägg till produkt - Din kund valde %1$s Utforska hur du kan öka din butiksförsäljning. Börja sälja idag genom att lägga till din första produkt i butiken. + Lägg till produkt Variationsattribut Aktivera Bluetooth på den mobila enheten + Det gick inte att hämta beställningen. Beställningens status i appen kan vara föråldrad. Lyckas inte hitta ditt e-postprogram + Ditt kvitto från %s Uppdaterar beställning Uppdaterar appstatusen - Det gick inte att hämta beställningen. Beställningens status i appen kan vara föråldrad. - 1 variation - Lär dig mer om att skriva ut kvitton med din enhet - Aktivera Bluetooth- eller Wifi-anslutning på din skrivare. - Parkoppla och anslut skrivaren till din mobil när du uppmanas till det. - Justera pappersstorleken efter behov och välj ”Skriv ut” när du är redo att skriva ut kvittot. + Din kund valde %1$s + Tullformulär kräver ett tiosiffrigt telefonnummer + Tullformulär ifyllt Om du har problem med att skriva ut från din enhet kontaktar du kundsupporten för din skrivare. Om utskrift inte är tillgänglig kan du alltid spara ditt kvitto som PDF och skicka det via e-post för att skriva ut det från en annan enhet. - Tullformulär ifyllt + Justera pappersstorleken efter behov och välj ”Skriv ut” när du är redo att skriva ut kvittot. + Parkoppla och anslut skrivaren till din mobil när du uppmanas till det. När du väljer \"Skriv ut kvitto\" efter att ha godkänt betalningen, ersätt \"Spara som PDF\" med \"Alla skrivare\" och sök efter ny skrivare. + Aktivera Bluetooth- eller Wifi-anslutning på din skrivare. Se till att Print Service-tillägget för din skrivare är installerat. + Lär dig mer om att skriva ut kvitton med din enhet För att skapa en variation måste du först ställa in dess attribut (dvs. \"Färg\", \"Storlek\") + 1 variation %1$s variationer - Tullformulär kräver ett tiosiffrigt telefonnummer USPS-spårning Uppdaterar din läsares programvara Programvaruuppdatering Läsarens programvara har uppdaterats - Karantän - Ingen - Annat - Prov - Innehållsdetaljer - Dokument - Beskrivning - Detta fält är obligatoriskt - Anpassad rad %1$d - Vikt (%1$s per enhet) - Värde (%1$s per enhet) + OKÄNT KORTLÄSARNAMN Koppla från läsare + Uppdatera din läsares programvara för att fortsätta ta emot betalningar + Uppdatera läsarens programvara + %s%% batteri ANSLUTEN LÄSARE Anslut kortläsare + Sätt på kortläsaren och placera den bredvid den mobila enheten Se till att kortläsare är laddad Anslut din kortläsare + Kort avvisades Ansluter till läsaren - Annat + Förbereder för att ta emot betalning + Det deklarerade värdet måste vara större än noll Vikt måste vara större än noll - Land där produkten tillverkades eller monterades - Ursprungsland - Ogiltigt format - Begränsningsdetaljer - Begränsningstyp - Innehållstyp - Returnera till avsändare om paketet inte kan levereras - upp till %s - Paketinnehåll - Sätt på kortläsaren och placera den bredvid den mobila enheten - Kort avvisades + Detta fält är obligatoriskt Beskriv vilken typ av begränsningar detta paket måste ha. Beskriv vilken typ av varor detta paket innehåller. - OKÄNT KORTLÄSARNAMN - Uppdatera din läsares programvara för att fortsätta ta emot betalningar - Uppdatera läsarens programvara - %s%% batteri - Förbereder för att ta emot betalning - Det deklarerade värdet måste vara större än noll + Vikt (%1$s per enhet) + Värde (%1$s per enhet) %1$s om HS-tariffnummer %1$s om internt transaktionsnummer + Anpassad rad %1$d + Annat Sanitär/fytosanitär inspektion + Karantän + Ingen + Annat + Prov Presenter + Dokument Handelsvaror + Land där produkten tillverkades eller monterades + Ursprungsland Tariffnumret måste vara 6 siffror långt HS-tariffnummer (valfritt) + Beskrivning + Paketinnehåll ITN krävs för försändelser till %1$s. ITN krävs för frakt av artiklar till ett värde som överstiger 2 500 USD per tariffnummer - Hoppa över + Ogiltigt format + Begränsningsdetaljer + Innehållsdetaljer + Begränsningstyp + Innehållstyp + Returnera till avsändare om paketet inte kan levereras + upp till %s + Om du har aktiverat den här inställningen kommer kunden att få ett bekräftelsemeddelande via e-post när beställningen har slutförts. + Granska beställning + 🎉 Beställning slutförd! Verifierar roll … Du har inte den korrekta användarrollen - Redigera och lägg till nya produkter var som helst - Granska beställning Lär dig mer om roller och behörigheter - Spåra försäljning och högpresterande produkter - Om du har aktiverat den här inställningen kommer kunden att få ett bekräftelsemeddelande via e-post när beställningen har slutförts. - 🎉 Beställning slutförd! Den här appen har endast stöd för användarrollerna Administratör och Butikschef. Kontakta butiksägaren för att uppgradera din roll. + Redigera och lägg till nya produkter var som helst Hantera och redigera beställningar i farten + Spåra försäljning och högpresterande produkter + Hoppa över Extern produkt Grupperad produkt Variabel produkt En unik digital produkt som tjänster, nedladdningsbara böcker, musik eller videor + En unik fysisk produkt som du kan behöva frakta till kunden Enkel fysisk produkt Öppna inställningar Öppna inställningar - En unik fysisk produkt som du kan behöva frakta till kunden Bluetooth är inaktiverat + Plats är inaktiverad + Saknar nödvändig exakt platsbehörighet Det gick inte att ansluta till läsaren. Ansluter till läsaren Anslut läsaren @@ -2013,68 +2031,62 @@ Language: sv_SE Söker efter läsare Antal artiklar Skapa ny leveransetikett - Plats är inaktiverad - Saknar nödvändig exakt platsbehörighet Enkel virtuell produkt + Vill du ta bort denna variant? + Genererar variation Tar bort produkt Skicka kvitto Skriv ut kvitto - Betalning lyckades - Betalning misslyckades - Tryck eller infoga för att betala - Vill du ta bort denna variant? Fångar upp betalning Behandlar betalning Läsaren är redo + Betalning misslyckades + Betalning lyckades Ta emot betalning + Tryck eller infoga för att betala Det gick inte att förhandsgranska fraktsedeln. Installera en app för PDF-visning och försök igen. Vi kunde inte detektera någon WordPress-webbplats på den angivna adressen. Se till att WordPress är installerat och att du kör den senaste tillgängliga versionen. flera fraktrader - Genererar variation - Vänta … - Spara till senare Kunde inte markera beställningen som slutförd - Etiketter som är äldre än 30 dagar kan inte återbetalas - Skriv ut fraktetikett - Fraktetikett köpt! Ett fel uppstod vid köp av etiketterna + Vänta … Köpa etikett Etikettbilder som är äldre än 180 dagar tas bort av våra teknikpartners i generella säkerhets- och dataskyddssyften. - Typ av variation, t.ex. storlek eller färg - Ändra namn + Skriv ut fraktetikett + Spara till senare + Fraktetikett köpt! + Etiketter som är äldre än 30 dagar kan inte återbetalas Typ + Ändra namn Det gick inte att byta namn på ditt attribut + Typ av variation, t.ex. storlek eller färg Byt namn på attribut - Attribut - och - Ta bort detta attribut? Var det inte meningen att skapa ett nytt konto? Gå tillbaka och skriv din e-postadress igen. + Anslut kortläsare Hantera kortläsare Butiksinställningar - Anslut kortläsare + Attribut Du kan återbetala %1$s Ta emot betalning + och + Ta bort detta attribut? Valfri Fel när dina attribut sparades - Signatur obligatoriskt (%s) + Det är bara webbplatsens ägare som kan hantera betalningsmetoderna för fraktetiketter. Kontakta butiksägaren %1$s (%2$s) för att hantera betalningsmetoder. Lägg till variationer Lägg till variation + Lägg till din första variant %s totalt - Det är bara webbplatsens ägare som kan hantera betalningsmetoderna för fraktetiketter. Kontakta butiksägaren %1$s (%2$s) för att hantera betalningsmetoder. %s avgifter valda Berättigar till gratis signaturkrav Berättigar till gratis upphämtning + Försäkring (%s) + spårning Inkluderar %s Måste signeras av en vuxen (%s) - spårning - Försäkring (%s) + Signatur obligatoriskt (%s) Kunden betalade %1$s av %2$s för frakt - Lägg till din första variant - gratis - Annat - Delsumma - Ett alternativ med detta namn finns redan - Ett attribut med detta namn finns redan + När du köper fraktetiketter med WooCommerce, får du 5 % till 40 % i rabatt jämfört med postkontoret. Vad innebär rabatt på WooCommerce-tjänster? Det gick inte att hämta leveransalternativen Transportföretag och avgifter @@ -2083,33 +2095,40 @@ Language: sv_SE Ordersumma Läs mer om rabatt på WooCommerce-tjänster Rabatt på WoCommerce-tjänster + Delsumma Beställningssammanfattning för fraktetiketter + gratis + Annat + Ett alternativ med detta namn finns redan + Ett attribut med detta namn finns redan Lägg till varje alternativnamn och tryck på retur Eller tryck för att välja ett befintligt alternativ Namn på alternativ - När du köper fraktetiketter med WooCommerce, får du 5 % till 40 % i rabatt jämfört med postkontoret. + Ett fel uppstod när dina inställningar skulle sparas Vänta … Sparar dina inställningar Löper ut %1$s + E-posta inköpskvitton till %1$s (%2$s) på %3$s + Kreditkort hämtas från följande WordPress.com-konto: %1$s <%2$s> + %1$s****%2$s Lägg till ett annat kreditkort Betalningsmetod vald Vänta … - %1$s****%2$s - Ett fel uppstod när dina inställningar skulle sparas - E-posta inköpskvitton till %1$s (%2$s) på %3$s - Kreditkort hämtas från följande WordPress.com-konto: %1$s <%2$s> Hämtar dina inställningar + Kreditkort slutar på %1$s Paypal VISA MasterCard Discover American Express + Eller tryck för att välja ett befintligt attribut + Nytt attributnamn Lägg till attribut Attribut Redigera attribut - Eller tryck för att välja ett befintligt attribut - Nytt attributnamn - Kreditkort slutar på %1$s + Total förpackningsvikt: %1$s %2$s + %1$d varor i %2$d förpackningar + Total förpackningsvikt: %1$s %2$s Anpassade paket Kan inte hämta produkter Vissa obligatoriska fält är tomma. @@ -2118,44 +2137,41 @@ Language: sv_SE Vänta … Laddar in paket! Packet %1$d + %d varor Kan inte ladda paketdefinitioner + Inkluderar förpackningsvikt + Total förpackningsvikt (%1$s) + Vald förpackning Paketdetaljer Flytta + Poster att slutföra Föreslagen adress Angiven adress + Vi har ändrat den adress du angav något. Använd den föreslagna adressen om den stämmer för att försäkra korrekt leverans. Redigera vald adress Använd vald adress - %1$d varor i %2$d förpackningar - Total förpackningsvikt: %1$s %2$s - Inkluderar förpackningsvikt - Total förpackningsvikt (%1$s) - Vald förpackning - Poster att slutföra - Vi har ändrat den adress du angav något. Använd den föreslagna adressen om den stämmer för att försäkra korrekt leverans. - Total förpackningsvikt: %1$s %2$s - %d varor Laddar in adressdata Nya funktioner tillgängliga! - Adress hittades inte + Hitta på karta + Kontakta kunden Ogiltig gata Husnummer saknas - Postnummer + Adress hittades inte + Vi kunde inte verifiera leveransadressen automatiskt. Visa i Google Maps eller testa att kontakta kunden för att verifiera att adressen stämmer. + Adressvalideringen misslyckades Vänta … + Adressvalideringen pågår + Det gick inte att läsa in adressdata + Använd adressen som angavs + Land + Postnummer + Delstat Ort Telefon Företag Namn - Land - Delstat - Använd adressen som angavs - Kontakta kunden - Vi kunde inte verifiera leveransadressen automatiskt. Visa i Google Maps eller testa att kontakta kunden för att verifiera att adressen stämmer. - Adressvalideringen misslyckades - Adressvalideringen pågår - Det gick inte att läsa in adressdata - Hitta på karta - Vänta … Google Maps-appen hittades + Vänta … Bildborttagning på produktvarianter stöds i WooCommerce 4.7 eller senare. Vänta Lägger till spårning @@ -2169,105 +2185,105 @@ Language: sv_SE Förpackningsinformation Skapa fraktetikett Läs mer + Skippa kön på postkontoret genom att skriva ut fraktetiketter till rabatterade priser hemma med din mobila enhet! Spara tid och pengar genom att fullfölja med WooCommerce Shipping WooCommerce Shipping Markera ordern som slutförd + Lär dig mer om att skapa etiketter med din mobila enhet Skapa fraktetikett - Skapa fraktetiketter från din enhet! Du kan nu skapa fraktetiketter för alla fysiska beställningar direkt från din enhet med hjälp av gratistillägget WooCommerce Shipping. Tryck på \"Skapa fraktetikett\" för att prova vår betafunktion! - Lär dig mer om att skapa etiketter med din mobila enhet - Skippa kön på postkontoret genom att skriva ut fraktetiketter till rabatterade priser hemma med din mobila enhet! - Redigera + Skapa fraktetiketter från din enhet! Avgifter Nettobetalning Betald Läs mer om att ansluta Jetpack + Redigera Validera Dra och släpp för att ändra ordning på foton - Radera - Fil-URL - Lägg till fil - 1 fil - %1$d filer - Fil - Avbryt - Ja, ändra + Inställningar för nedladdning Ange ett giltigt namn Ange fil-URL + WordPress mediabibliotek + Kontrollera att den angivna URL:en är giltig Vänta … Laddar upp filer - Filnamn - Inställningar för nedladdning - Kontrollera att den angivna URL:en är giltig Fel vid uppladdning av filen Lägg till nedladdningsbar fil + Lägg till nedladdningsbar fil från Inkludera nedladdningsbara filer med köp + Avbryt + Ja, ändra Alla filer som för närvarande är associerade med den här produkten kommer att tas bort. Är du säker på att du vill ta bort möjligheten att ladda ned filer när produkten köps? + Fil Är du säker på att du vill ta bort den här filen? Nedladdningsbar produkt + Radera Tidsgräns för nedladdning Nedladdningsgräns Ange antalet dagar innan en nedladdningslänk löper ut, eller lämna fältet tomt om länken aldrig löper ut. Ange antalet gånger som filen kan laddas ned eller lämna fältet tomt för obegränsade nerladdningar Detta är namnet på filen som visas för kunden + Filnamn Detta är URL:en för filen som kunderna får tillgång till. URL:er som anges bör redan vara kodade. + Fil-URL + Lägg till fil + 1 fil + %1$d filer Du kan behöva <b>konfigurera Wi-Fi-utskrift direkt på själva skrivaren.</b> Kontrollera att skrivarens fasta programvara är uppdaterad. Anvisningar finns i skrivarens dokumentation. Du kan välja din enhets <b>standardutskriftstjänst</b> eller installera din <b>skrivares varumärkesapp</b> (detta bör visas som ett rekommenderat alternativ) Se till att din skrivare och din enhet är anslutna till <b>samma Wi-Fi-nätverk</b> - WordPress mediabibliotek - Lägg till nedladdningsbar fil från + Testa det nya enkla, länkade och grupperade produktskapandet medan vi gör oss redo för lansering + Öka försäljningen med merförsäljning och korsförsäljning Redigera produkter Lägg till produkter - Öka försäljningen med merförsäljning och korsförsäljning Produkter som marknadsförs i kundvagnen när den nuvarande produkten är vald - Testa det nya enkla, länkade och grupperade produktskapandet medan vi gör oss redo för lansering Korsförsäljning Produkter som marknadsförs istället för den för närvarande visade produkten (dvs. mer lönsamma produkter) Merförsäljning Länkade produkter - Kontakta oss %1$s%2$s x %3$s + Kontakta oss Se till att du kör den senaste versionen av WooCommerce på din webbplats och att du har WooCommerce Admin aktiverad.\n\nBehöver du mer hjälp? %1$s Vi kan inte visa din\n butiks analys Skaffa en länk för inloggning via e-post - Välj pappersstorlek - Pappersstorlek + Hmm. Vi hittar inget konto hos WordPress.com som är kopplat till denna e-postadress. + Testa att visa Beställningstillägg medan vi gör oss redo för lanseringen Skapar produkter + Inställningar + Ett fel inträffade när produkten skulle slängas Produkt flyttad till papperskorg Flytta till papperskorg Vill du flytta denna produkt till papperskorgen? + Släng produkt Denna produkt har inga variationer än + Det går endast att lägga till alternativ som storlek och färg på webben. Dessa kommer att visas som alternativ på produktsidan på din webbplats. Skapa produkter från appen! Produkt hittades inte - Hmm. Vi hittar inget konto hos WordPress.com som är kopplat till denna e-postadress. - Inställningar - Ett fel inträffade när produkten skulle slängas - Släng produkt - Det går endast att lägga till alternativ som storlek och färg på webben. Dessa kommer att visas som alternativ på produktsidan på din webbplats. + Om du fortfarande har problem med att skriva ut från din enhet kan du <b>spara din etikett som en PDF-fil</b> och skicka den med e-post för att skriva ut den från en annan enhet. + När du har valt <b>\"Skriv ut fraktetikett\"</b> kan du behöva välja och lägga till en skrivare om du inte har skrivit ut från den här enheten tidigare. Alternativ för etikettformat + Skriv ut med din enhet Etikett (4 x 6 tum) Letter (8,5 x 11 tum) Legal (8,5 x 14 tum) Fel vid förhandsgranskning av fraktetikett + Vet du hur du skriver ut med din mobila enhet? Se alternativen för etikettlayout och pappersstorlek Skriv ut fraktetikett + Välj pappersstorlek + Pappersstorlek + Om du redan har använt etiketten på ett paket är det ett brott mot våra användarvillkor att skriva ut och använda den igen. Om det inträffade ett utskriftsfel när du köpte etiketten kan du skriva ut den igen. Vi arbetar för att göra det enklare för dig att skriva ut fraktetiketter direkt från din enhet! Om du har skapat fraktetiketter för den här ordern i butiksadmin i WooCommerce Shopping kan du nu skriva ut dem under Beställningsinformation här. Skriv ut fraktetiketter från din enhet! - Skriv ut med din enhet - Om du redan har använt etiketten på ett paket är det ett brott mot våra användarvillkor att skriva ut och använda den igen. - Om du fortfarande har problem med att skriva ut från din enhet kan du <b>spara din etikett som en PDF-fil</b> och skicka den med e-post för att skriva ut den från en annan enhet. - När du har valt <b>\"Skriv ut fraktetikett\"</b> kan du behöva välja och lägga till en skrivare om du inte har skrivit ut från den här enheten tidigare. Skriv ut fraktetikett - Vet du hur du skriver ut med din mobila enhet? - Testa att visa Beställningstillägg medan vi gör oss redo för lanseringen \u0022%1$s\u0022 Produktutkast sparat - Sparar utkast - Spara som utkast Det gick inte att spara produktutkastet + Sparar utkast I papperskorgen + Spara som utkast Bekräftelse av registrering Återställ ditt lösenord Ange din webbplatsadress @@ -2296,96 +2312,96 @@ Language: sv_SE Skicka länk via e-post Ge feedback Produkt publicerad + Fel vid publicering av produkt + Publicerar produkt PUBLICERA Ny produkt - Publicerar produkt - Fel vid publicering av produkt - Lägg till foto - Byt ut foto Endast ett foto kan visas per produktvariant + Byt ut foto + Lägg till foto Lägg till bild för variant Lär dig hur man installerar och ansluter Jetpack + För att använda den här appen för %1$s behöver du ha anslutit Jetpack-tillägget till din butik. Logga in med ett annat konto Välj butik att ansluta Fortsätt med WordPress.com - För att använda den här appen för %1$s behöver du ha anslutit Jetpack-tillägget till din butik. - Ange lösenord + En produkt med variationer som färg eller storlek %d produkt vald %d produkter valda Lägg till produkter till gruppen Lägg till produkt - En produkt med variationer som färg eller storlek + Ange lösenord + Tillbaka till butik Kontakta oss här + Tänk på att detta inte är ett supportärende och att vi inte kan adressera individuell feedback.\n\nBehöver du hjälp? %1$s + Tack för att du delar dina\n tankar med oss Feedback skickad Vänta … Laddar in Hur kan vi förbättra? Ja, ändra - Välj en produkttyp - Skicka feedback - Vissa variationer har inga priser - Grupperade produkter - Ändra produkttyp - Aktiverad - %1$s lämnade en recension - Ta bort den grupperade produkten - Variation uppdaterad - Tillbaka till butik - Tänk på att detta inte är ett supportärende och att vi inte kan adressera individuell feedback.\n\nBehöver du hjälp? %1$s - Tack för att du delar dina\n tankar med oss Om du ändrar produkttypen ändras en del av produktinformationen Är du säker på att du vill ändra produkttypen? Länk till en produkt på en extern webbplats En samling relaterade produkter + Välj en produkttyp + Skicka feedback + Vissa variationer har inga priser Variationer utan pris kommer inte att visas i din butik + Variation uppdaterad + Ta bort den grupperade produkten + Grupperade produkter + Ändra produkttyp Inget pris angivet + Aktiverad Du måste ange försäljningspriset om en försäljning är schemalagd Du kan nu redigera grupperade, externa och variabla produkter, ändra produkttyp och uppdatera kategorier och etiketter. + %1$s lämnade en recension + Jag gillar den Kunde vara bättre Tycker du om WooCommerce-appen? - Jag gillar den Fel vid uppdatering av variation Fel vid hämtning av variation Fel uppstod när etiketter lades till Lägger till etiketter Din återbetalning bearbetas. Vänta … + Begäran om återbetalning har skickats Återbetalningsetikett (-%1$s) + Belopp berättigat för återbetalning Inköpsdatum + Du kan begära en återbetalning för en fraktetikett som inte har använts för att skicka ett paket. Den kommer att ta minst 14 dagar att behandla. Begär en återbetalning - Belopp berättigat för återbetalning Fraktetikett för återbetalning - Du kan begära en återbetalning för en fraktetikett som inte har använts för att skicka ett paket. Den kommer att ta minst 14 dagar att behandla. - Begäran om återbetalning har skickats - Organisera dina produkter i relaterade grupper - Gör dina produkter lättare att hitta med etiketter - Ett kort utdrag om din produkt Fysisk + Ett kort utdrag om din produkt + Gör dina produkter lättare att hitta med etiketter + Organisera dina produkter i relaterade grupper + Lägg till vikt och dimensioner + Lägg till fler detaljer + Organisera dina produkter i etiketter + Lägg till din första etikett Etiketter Lägg till etikett + Inaktiverat Virtuell produkt Lägg till fler detaljer %1$s produkt nedladdningsbar %s produkt - Lägg till fler detaljer - Organisera dina produkter i etiketter - Lägg till vikt och dimensioner - Lägg till din första etikett - Inaktiverat Återstående produkter - %1$s\n%2$s - Kreditkort - Betalningsmetod %1$s \u2022 %2$s + %1$s etikettåterbetalning begärd Spåra försändelse + %1$s\n%2$s Dölj information om försändelse Visa försändelseinformation + Kreditkort + Betalningsmetod Transportföretag och avgifter Information om förpackning Leverans Avsändare Förpackning %d - %1$s etikettåterbetalning begärd SKU: %1$s %1$s (%2$s alternativ) Fraktetiketter @@ -2406,22 +2422,30 @@ Language: sv_SE Integritetsnotis för användare i Kalifornien Behåll ändringar Fram till %1$s - Nya redigeringsalternativ tillgängliga Vi har lagt till fler redigeringsfunktionaliteter till produkter! Du kan nu uppdatera bilder, se förhandsgranskningar och dela dina produkter. + Nya redigeringsalternativ tillgängliga + Begränsad redigering tillgänglig Produkter %1$s x %2$s %1$s %2$s - Begränsad redigering tillgänglig Extern Enkel Publicerat privat Inga bilder ännu WordPress mediebibliotek WordPress mediebibliotek - Slug - Produktlänk + Bestämmer produktens placering i katalogen. Ju lägre siffervärdet är desto högre kommer artikeln att vara i produktlistan. Du kan också använda negativa siffror. + Menysortering + Ett valfritt meddelande som kan skickas till kunden efter köpet + Denna text kommer att visas på knappen som länkar till den externa produkten Knapptext Ange den externa URL:en till produkten + Produktlänk + Lägg till produktlänk + Aktivera recensioner + Detta är den URL-vänliga versionen av produktrubriken + Slug + Denna inställning avgör vilka butikssidor produkterna kommer att listas på. Utvald produkt Dold Endast sökresultat @@ -2435,7 +2459,16 @@ Language: sv_SE Synlighet Status Produktinställningar + Fel när lösenordet uppdaterades + Valt sorteringsalternativ + Valt filteralternativ Visa produkter + Filter \u2022 %d + Filter (%d) + Ö till A + Titel: Ö till A + A till Ö + Titel: A till Ö Äldsta Datum: äldsta till nyaste Nyaste @@ -2443,76 +2476,54 @@ Language: sv_SE Sortera efter Filter Inga produkter hittades + Alla Produkttyp %d valda Inte inställd Fler alternativ Rensa - Lägg till produktlänk - Aktivera recensioner - Detta är den URL-vänliga versionen av produktrubriken - Denna inställning avgör vilka butikssidor produkterna kommer att listas på. - Fel när lösenordet uppdaterades - Valt sorteringsalternativ - Valt filteralternativ - Denna text kommer att visas på knappen som länkar till den externa produkten - Menysortering - Bestämmer produktens placering i katalogen. Ju lägre siffervärdet är desto högre kommer artikeln att vara i produktlistan. Du kan också använda negativa siffror. - Ett valfritt meddelande som kan skickas till kunden efter köpet - Filter \u2022 %d - Filter (%d) - Ö till A - Titel: Ö till A - A till Ö - Titel: A till Ö - Alla + Väntande recension + Om WooCommerce + Ställs in av strömsparläget + Systemstandard Mörk + Ljus Utseende Kort sammanfattning om produkten Kort beskrivning - Uppgradera för att fortsätta se din statistik - Om WooCommerce - Systemstandard - Väntande recension - Ställs in av strömsparläget - Ljus Vi har lanserat förbättringar av våra analyser. Uppgradera till WooCommerce 4.0 eller senare eller installera WooCommerce Admin-tillägget för att fortsätta att se din statistik efter den 1 september 2020 - Ta bort slutdatum + Uppgradera för att fortsätta se din statistik Reapriset måste vara lägre än det ordinarie priset + Ta bort slutdatum Lägg till frakt Produktbilderna laddas fortfarande upp. Vill du ignorera dina ändringar? Ange ett nummer Vi har lagt till redigeringsfunktionalitet till enkla produkter. Håll utkik för fler alternativ snart! - Till - Från - Momsinställningar - Momsstatus Frakt Momsbelagd Ingen Standardmoms Momsgrupp + Momsstatus + Momsinställningar + Till + Från Starta och avsluta en rea automatiskt Schemalägg rea i ditt administratörsarkiv - Höjd - Bredd - Längd - Dimensioner SKU används redan av en annan produkt Fraktinställningar Ingen fraktklass - %1$s via %2$s - Återbetalningar + Dimensioner + Höjd + Bredd + Längd Återbetalda produkter - Återbetalda produkter - Är du säker på att du vill utfärda en återbetalning? Det här kan inte ångras. %1$s (%2$s x %3$d) - från %1$s - Readatum - Lägg till pris - Handera lager - %1$s – %2$s + %1$s via %2$s + Är du säker på att du vill utfärda en återbetalning? Det här kan inte ångras. + Återbetalda produkter + Återbetalningar Registrera dig på WordPress.com Vi hittade inga resultat för %s Få högkvalitativa produktrecensioner för din butik @@ -2524,43 +2535,42 @@ Language: sv_SE Hur många artiklar som finns i lager Antal Begränsa till en per order + Handera lager Hjälper att enkelt identifiera den här produkten + från %1$s + %1$s – %2$s + Readatum + Lägg till pris Lägg till lager Kollar upp din beställning… Ange text + Ange produktrubrik + Produkt sparad + Det gick inte att uppdatera produkten + Vänta … Beskriv din produkt Beskrivning Redigera beskrivning - Det gick inte att uppdatera produkten - Vänta … - Ange produktrubrik - Produkt sparad - Klart - Uppdatera Vill du ignorera dina ändringar? + Uppdatera + Klart Återbetalning pågår, vänta … Återbetala frakt Välj kvantitet Fraktåterbetalning Produktåterbetalning + %1$s x %2$s vardera %d artiklar valda Välj ingen Välj alla Väntar på återbetalningsbekräftelse … - %1$s x %2$s vardera - Ta ett foto - Välj en uppladdningsmetod - Lägg till en produktbild - Ta bort foto - Lägg till foton - Foton - Lägg till bild - Ta bort - Kommande - Bildoptimering Ändra storlek på och komprimera bilder för snabbare uppladdning + Bildoptimering + Ta ett foto Välj från enheten + Välj en uppladdningsmetod Uppladdningar + Laddar upp bilder … %1$d av %2$d Laddar upp bild… Det går inte att komma åt kameran Är du säker på att du vill ta bort den här bilden? @@ -2568,7 +2578,13 @@ Language: sv_SE Vänta tills den aktuella åtgärden har slutförts Det gick inte att ladda upp produktbilden Det gick inte att ta bort produktbilden - Laddar upp bilder … %1$d av %2$d + Lägg till en produktbild + Ta bort foto + Lägg till foton + Foton + Lägg till bild + Kommande + Ta bort Vi kunde inte komma åt din webbplats. För att lösa detta behöver du kontakta ditt webbhotell. Vi kunde inte komma åt din webbplats på grund av ett problem med <b>SSL-certifikatet</b>. För att lösa detta behöver du kontakta ditt webbhotell. Vi kunde inte komma åt din webbplats eftersom det kräver <b>HTTP-autentisering</b>. För att lösa detta behöver du kontakta ditt webbhotell. @@ -2577,8 +2593,8 @@ Language: sv_SE Logga in med dina inloggningsuppgifter. Logga in med dina inloggningsuppgifter för %1$s Skicka verifieringsmeddelande via e-post - Produktredigering Testa den nya produktredigeringsfunktionen då vi snart är klara för lansering + Produktredigering Det uppstod ett fel när ditt konto skulle hämtas. Du kan försöka igen nu eller stänga och försöka igen senare. Ett fel har uppstått. Logga in för att fortsätta Ansluter till din webbplats … @@ -2613,12 +2629,15 @@ Language: sv_SE Inga matchande produkter Inga produkter ännu %s i lager + I lager \u2022 %d variationer Produktbild %1$s lämnade en recension om %2$s Förkastad Den nya produktrecensionen kunde inte hämtas Produktrecensionerna kunde inte hämtas - I lager \u2022 %d variationer + Något gick fel med återbetalningen. Var vänlig försök igen. + Återbetalningen har skickats. + Din återbetalning för %s behandlas. Vänligen vänta … Offertikon Manuell återbetalning Återbetalningsinformation @@ -2636,9 +2655,6 @@ Language: sv_SE Återbetala %s %s tillgängligt för återbetalning Utfärda återbetalning - Något gick fel med återbetalningen. Var vänlig försök igen. - Återbetalningen har skickats. - Din återbetalning för %s behandlas. Vänligen vänta … %1$s via %2$s Förbättrad statistik Betafunktioner @@ -2652,12 +2668,12 @@ Language: sv_SE Dagens statistik Logga in Har du redan Jetpack? %1$s + Försöker logga in med Jetpack … uppdatera appen för att fortsätta + För att använda den här appen för %1$s behöver du installera Jetpack-tillägget och ansluta det till det här kontot. \n\nNär detta är klart, starta om appen. Försök med en annan butik Databasen har nedgraderats, återskapar tabeller och läser in butiker Läser in butiker - Försöker logga in med Jetpack … - För att använda den här appen för %1$s behöver du installera Jetpack-tillägget och ansluta det till det här kontot. \n\nNär detta är klart, starta om appen. Inga transportföretag hittades Ange en fullständig webbplatsadress som example.com. Inga omdömen än! @@ -2668,11 +2684,12 @@ Language: sv_SE Det gick inte att hämta inställningarna: En del API:er är inte tillgängliga för den här OAuth app-ID + konto-kombinationen. Vi letar efter medarbetare! Kopiera spårningsnummer - uppdatera appen Kollar efter WooCommerce … + uppdatera appen Ingen adress specificerad Behöver du hjälp med att hitta e-postadressen som du använde för att ansluta? Webbplatsen på den här adressen är inte en WordPress-webbplats. Webbplatsen måste använda WordPress för att vi ska kunna ansluta till den. + Logga in med WordPress.com för att ansluta till <b>%1$s</b> Zimbabwe Zambia Yemen @@ -2809,7 +2826,6 @@ Language: sv_SE Jamaica Elfenbenskusten Italien - Logga in med WordPress.com för att ansluta till <b>%1$s</b> Israel Isle of Man Irland @@ -2916,15 +2932,24 @@ Language: sv_SE Afghanistan Åland Recension + Anpassat transportföretag Anpassad + Ange ett transportföretagsnamn Ange ett spårningsnummer + Välj ett transportföretag Vill du ta bort den här spårningen? Det gick inte att lägga till spårning Spårning av försändelse har lagts till + Det gick inte att hämta transportföretag + Valt transportföretag + Transportföretag Datum skickat Ange spårningslänk + Ange transportföretagsnamn Ange spårningsnummer + Välj transportföretag Spårningslänk (tillval) + Transportföretagsnamn Spårningsnummer Fraktbolag Lägg till spårning @@ -2937,35 +2962,26 @@ Language: sv_SE Spåra försändelse Du kan hitta e-postadressen som du använder för att ansluta till WordPress.com från din webbplatsadminsitration på %1$sJetpack Dashboard%2$s under %3$sAnslutningar > Kontoanslutning%4$s Vilken e-postadress ska jag använda för att logga in? + Behöver du hjälp med att hitta e-postmeddelandet? Jetpack är ett kostnadsfritt tillägg i WordPress som ansluter din butik till de verktyg som behövs för att ge den bästa möjliga mobilupplevelsen, inklusive pushmeddelanden och statistik Vad är Jetpack? Visa anslutna butiker Det ser ut som att %1$s är ansluten till ett annat WordPress.com-konto. Fortsätt redigera - Anpassat transportföretag - Ange ett transportföretagsnamn - Välj ett transportföretag - Det gick inte att hämta transportföretag - Valt transportföretag - Transportföretag - Ange transportföretagsnamn - Välj transportföretag - Transportföretagsnamn - Behöver du hjälp med att hitta e-postmeddelandet? - Tillåt - Tillåt inte - Läs mer - I lager - Slut i lager - Tillåt, men meddela kunden Logga in med ditt användarnamn och lösenord. Logga in med ditt WordPress.com användarnamn istället för din e-postadress. - Hjälpcenter Webbplatsen på denna adress är inte en WordPress-webbplats. För att vi ska kunna ansluta till den måste webbplatsen använda WordPress. - Restnoterad - Variabel - Grupperade + Hjälpcenter Virtuell + Grupperade + Variabel + Tillåt, men meddela kunden + Tillåt + Tillåt inte + Restnoterad + Slut i lager + I lager + Läs mer Bilden kunde inte laddas Utkast Privat @@ -3011,11 +3027,11 @@ Language: sv_SE Prova nu Klart Peka för att byta butiker + Välj butik Logga ut Ändra orderstatus Klicka för att ändra orderstatus Tillämpa - Välj butik Nej tack Senare Betygsätt nu @@ -3026,14 +3042,14 @@ Language: sv_SE Dela din butiks URL Dela din butik Alla granskningar har markerats som lästa - Kan inte ansluta till %s Uppdatera butik till WooCommerce 3.5 + Kan inte ansluta till %s Avfärda + Ett fel uppstod när alla granskningar skulle markeras som lästa Markera alla som lästa Meddelande Ring Ring eller SMS:a kunden - Ett fel uppstod när alla granskningar skulle markeras som lästa Det gick inte att uppdatera produktrecensionsstatusen Det gick inte att uppdatera produktrecensionsinformationen Ta bort @@ -3046,16 +3062,16 @@ Language: sv_SE Hantera notifikationer Aviseringar Är du säker på att du vill logga ut från kontot %s? - Om detta inaktiveras kommer noteringen att vara privat Recension markerad som %1$s + Om detta inaktiveras kommer noteringen att vara privat Det gick inte att hämta ordern Tillbaka Produktrecensionsaviseringar Ny order-aviseringar Till kund + Verifierar webbplats… Uppdatera instruktioner Sök - Verifierar webbplats… Uppdatera och %d mer. %d nya notiser @@ -3087,9 +3103,9 @@ Language: sv_SE Kraschrapporter Dela Version %s - Vi har gjort för många försök att leverera en verifieringskod via SMS – Vänta lite och be om en ny om en minut. - Det finns inget WordPress.com-konto som stämmer mot detta Google-konto. - Logga in på WordPress.com-kontot du använde för att ansluta Jetpack. + HTTP-lösenord + HTTP-användarnamn + Autentisering krävs Magisk länk har skickats Registrering via e-post Bekräftelse av kod @@ -3098,9 +3114,32 @@ Language: sv_SE Inloggning via magisk länk Webbplatsens inloggningsadress E-postadress för inloggning - Har du inget konto? %1$sRegistrera dig%2$s - Registrering via Google… + Ett fel har inträffat. + Fyll i en autentiseringskod för att fortsätta. + Dubbelkontrollera ditt lösenord för att fortsätta. + Inloggningen avbruten + Vänta medan inloggningen pågår. + Inloggning pågår… + Tryck för att fortsätta. + Inloggad! + Ett nätverksfel har inträffat. Kontrollera anslutningen och försök igen. + Ange en webbplats som ligger på WordPress.com eller en WordPress-webbplats på egen server som är ansluten till Jetpack + Det gick inte att ansluta. Vi får fel 403 (förbjudet) när vi anropar XMLRPC-ändpunkten för din webbplats. Appen behöver detta för att kunna kommunicera med din webbplats. Kontakta webbhotellet för att lösa detta problem. + Det gick inte att ansluta. Din webbserver blockerar POST-anrop, som appen behöver för att kommunicera med din webbplats. Kontakta ditt webbhotell för att lösa detta problem. + Kunde inte ansluta. Obligatoriska XML-RPC-metoder saknas på servern. + Kontrollera att webbplatsens URL är giltig + Ett fel uppstod + Glömt ditt lösenord? + Ange en giltig epostadress + Kontrollerar e-post + Logga in igen för att fortsätta. + Logga in på WordPress.com-kontot du använde för att ansluta Jetpack. + Kunde inte hämta din profil + En dublett-webbplats har hittats. + Det går inte att lägga till denna webbplats. Den finns redan i appen. + Användarnamnet eller lösenordet som angavs är felaktigt Det tog för lång tid för Google att svara. Du kan behöva vänta tills du har en stabilare internetuppkoppling. + Registrering via Google… Registrering med Google Registrering med e-postadress Genom att registrera dig accepterar du våra %1$sAnvändarvillkor%2$s. @@ -3110,54 +3149,20 @@ Language: sv_SE Det gick inte att sända e-postmeddelandet. Du kan försöka igen nu eller stänga och göra ett nytt försök senare. Skriv in din e-postadress för att skapa ditt nya WordPress.com-konto. Det gick inte att kontrollera e-postadressen. - Ett fel har inträffat. - Fyll i en autentiseringskod för att fortsätta. - Dubbelkontrollera ditt lösenord för att fortsätta. - Inloggningen avbruten - Vänta medan inloggningen pågår. - Inloggning pågår… - Tryck för att fortsätta. - Inloggad! - Google login kunde inte startas. - Skriv in ett lösenord \nDu kanske vill prova ett annat konto? + Google login kunde inte startas. + Vi har gjort för många försök att leverera en verifieringskod via SMS – Vänta lite och be om en ny om en minut. Det var något som inte stämde vid uppkoppling till Google-kontot. + Det finns inget WordPress.com-konto som stämmer mot detta Google-konto. Stäng Logga in via Google. - Ett nätverksfel har inträffat. Kontrollera anslutningen och försök igen. Inloggad som Lyckas inte hitta ditt e-postprogram + Har du inget konto? %1$sRegistrera dig%2$s Skriv in en verifieringskod - En dublett-webbplats har hittats. - Det går inte att lägga till denna webbplats. Den finns redan i appen. - Det gick inte att ansluta. Vi får fel 403 (förbjudet) när vi anropar XMLRPC-ändpunkten för din webbplats. Appen behöver detta för att kunna kommunicera med din webbplats. Kontakta webbhotellet för att lösa detta problem. - Det gick inte att ansluta. Din webbserver blockerar POST-anrop, som appen behöver för att kommunicera med din webbplats. Kontakta ditt webbhotell för att lösa detta problem. - Kontrollerar e-post - Kunde inte ansluta. Obligatoriska XML-RPC-metoder saknas på servern. - Kunde inte hämta din profil - Logga in igen för att fortsätta. - Glömt ditt lösenord? - Användarnamnet eller lösenordet som angavs är felaktigt - Ange en giltig epostadress - Ett fel uppstod - Autentisering krävs - Kontrollera att webbplatsens URL är giltig - HTTP-lösenord - HTTP-användarnamn - Ange en webbplats som ligger på WordPress.com eller en WordPress-webbplats på egen server som är ansluten till Jetpack - Alternativt: - Allmänt - \@%s - Logga in med ditt användarnamn. - Logga in genom att skriva din webbplatsadress. - Skicka ett nytt SMS med en kod till mig istället. - Vi har skickat ett SMS till telefonnumret som slutar på %s. Skriv in bekräftelsekoden from SMS;et. - Ange motsvarande lösenord för WordPress.com för att fortsätta med detta Google-konto. Denna fråga får du endast en gång. - Logga in på WordPress.com för att dela innehållet. - Skriv in adressen för den WordPress-webbplats dit du vill dela innehållet. - Ett fel inträffade när standardwebbläsaren skulle öppnas. Välj en annan app: - Kan inte öppna länken + Skriv in ett lösenord Skriv in ett användarnamn + Logga in på WordPress.com för att dela innehållet. Logga in till ditt konto på WordPress.com för att nå inlägget. Ett fel inträffade när webbplatsen skulle läggas till. Felkod: %s Webbplatsadressen kontrolleras @@ -3166,15 +3171,25 @@ Language: sv_SE Vilket webbplatsadress har jag? Behöver du hjälp att hitta din webbplatsadress? Webbplatsadress + Skriv in adressen för den WordPress-webbplats dit du vill dela innehållet. \@%s Redan inloggad på WordPress.com Fortsätt + Anslut en webbplats Anslut ytterligare webbplats + Ange motsvarande lösenord för WordPress.com för att fortsätta med detta Google-konto. Denna fråga får du endast en gång. Skriv in ditt lösenord för WordPress.com. + Inte tillgänglig för närvarande. Ange ditt lösenord Begär e-postadress för inloggning Det verkar som om ditt lösenord inte stämmer. Dubbelkolla dina uppgifer och försök igen. Begär en verifieringskod via SMS. + Skicka ett nytt SMS med en kod till mig istället. Skicka mig en kod via SMS istället. + Vi har skickat ett SMS till telefonnumret som slutar på %s. Skriv in bekräftelsekoden from SMS;et. + Nästan där! Ange verifieringskoden för WordPress.com från din autentiseringsapp. + Logga in med ditt användarnamn. + Logga in genom att skriva din webbplatsadress. + Alternativt: Öppna e-posten Nästa Hantera din Jetpack-förstärkta webbplats när som helst – WordPress finns i din ficka. @@ -3182,75 +3197,35 @@ Language: sv_SE Håll koll på dina favoritwebbplatser och delta i diskussion där och när det passar dig. Se hur läsare från hela världen läser och interagerar med din webbplats – i direktsändning. Publicera från parkbänken. Blogga från bussen. Kommentera när du dricker kaffe. WordPress finns där du är. - Du är redan inloggad till ditt konto hos WordPress.com. Du kan inte lägga till någon webbplats från WordPress.com som är kopplad till ett annat konto. - Försök igen - Logga ut - Skicka länk - Inte tillgänglig för närvarande. Ange ditt lösenord - Loggar in + Logga in + Hjälp + Lösenord + Användarnamn Ange ditt lösenord istället - E-postadress - Ångra + Skicka länk Ogiltig verifieringskod Verifieringskod - Hjälp - Kasta bort - Logga in - Användarnamn - Lösenord - Utan titel - Inställningar - Idag - Avbryt - Äldre än en månad - Äldre än en vecka - Äldre än 2 dagar - Igår - Idag - Produkter - Detta år - Denna månad - Denna vecka - Produkt - Dölj detaljer - Fortsätt - Lär dig mer - Rabatt - Betalning - -%1$s%2$s - %1$s%2$s - WooCommerce - Ingen SMS-app hittades - Besökare - År - Månader - Veckor - Dagar - Logga in med ett annat konto - Inga WooCommerce-butiker - Ditt profilfoto - Ansluten butik - Ingen aktivitet denna period - Felbild - Intäkter - Misslyckades - Slutförd - Avbruten + E-postadress Support för WooCommerce Android %s alternativ ej markerat alternativ markerat Policyer från tredje part Cookie-policy Integritetspolicy + Skapad med kärlek av Automattic. %1$s Vi använder andra spårningsverktyg, inklusive några från tredje part. Läs om dessa verktyg och hur du kontrollerar dem. Läs integritetspolicyn Denna information hjälper oss att förbättra våra produkter, se till att marknadsföringen till dig är mer relevant, anpassa din upplevelse av WooCommerce och annat som finns beskrivet i vår integritetspolicy. Dela information med vårt analysverktyg om din användning av olika tjänster medan du är inloggad på ditt WordPress-konto Samla in information Integritetsinställningar + Inställningar Orderstatus Återbetalad + Avbruten Pausad + Slutförd + Misslyckades Inväntar betalning Behandlas Kunde inte lägga till notering @@ -3258,6 +3233,7 @@ Language: sv_SE Lägg till Skicka notering till kund via e-post Det gick inte att ändra ordern + Det gick inte att hämta noteringar Ordern har markerats som slutförd Markera ordern som slutförd Lägg till en ordernotering @@ -3266,6 +3242,7 @@ Language: sv_SE Visa fakturering Betalning godkänd Ordernoteringar + Privat Skapa en ordernotering Kundprofilsbild Kundnotering @@ -3290,32 +3267,71 @@ Language: sv_SE Inga ordrar Visa ordrar Visa order + Ingen aktivitet denna period + Totalt antal beställningar: %s + Toppresterande + Felbild Det gick inte att hämta data + Intäkter Ordrar + Besökare + År + Månader + Veckor + Dagar + Logga in med ett annat konto + Inga WooCommerce-butiker + Ditt profilfoto + Ansluten butik + Läs %1$skonfigurationsinstruktionerna%2$s. + Denna app kräver Jetpack för att ansluta till din butik. + \@%s + Ange adressen till den WooCommerce-butik som du vill ansluta. Logga in med e-postadressen för ditt WordPress.com-konto för att hantera dina WooCommerce-butiker. + Du är redan inloggad till ditt konto hos WordPress.com. Du kan inte lägga till någon webbplats från WordPress.com som är kopplad till ett annat konto. + Kan inte öppna länken + Ingen SMS-app hittades Ingen app för e-post hittades Ingen telefonappen hittades + Ett fel inträffade när standardwebbläsaren skulle öppnas. Välj en annan app: Kan inte öppna länken %1$s at %2$s + Äldre än en månad + Äldre än en vecka + Äldre än 2 dagar + Igår + Idag + Produkter + Kasta bort + Detta år + Denna månad + Denna vecka + Idag + Produkt Ditt nätverk är inte tillgängligt. Kontrollera din data- eller WiFi-anslutning. Offline u2014 med cachade data + Lär dig mer + Avbryt + Utan titel + Fortsätt + Ångra + Försök igen + Dölj detaljer + Detaljer + Rabatt Delsumma + Momser + Betalning Frakt + -%1$s%2$s + %1$s%2$s Ordrar Min butik + Logga ut + Loggar in Alla - Denna app kräver Jetpack för att ansluta till din butik. - Detaljer - Momser - Skapad med kärlek av Automattic. %1$s - Ange adressen till den WooCommerce-butik som du vill ansluta. - Privat - Anslut en webbplats - Totalt antal beställningar: %s - Toppresterande - Det gick inte att hämta noteringar - Läs %1$skonfigurationsinstruktionerna%2$s. - Nästan där! Ange verifieringskoden för WordPress.com från din autentiseringsapp. + Allmänt + WooCommerce \@string/date_timeframe_custom \@string/date_timeframe_today diff --git a/WooCommerce/src/main/res/values-tr/strings.xml b/WooCommerce/src/main/res/values-tr/strings.xml index 02416c31452..a8a484ec3e9 100644 --- a/WooCommerce/src/main/res/values-tr/strings.xml +++ b/WooCommerce/src/main/res/values-tr/strings.xml @@ -1,12 +1,33 @@ + Ayrıntıları Onayla + Reklam hedefi + İlgi alanları + Konum + Cihazlar + Dil + Bütçe + Ayrıntılar + Hemen satın al + Reklamı düzenle + Önizleme Devre Dışı Bırakıldı + Ürün seçimi + Ürün seç (%s) + <b>Yayınlayın:</b> Tanıtımınızın başlamasını ve başarısını izleyin. + <b>Hızlı inceleme:</b> Hızlı bir moderatör kontrolü için reklamınızı gönderin. + <b>Bütçenizi ayarlayın</b>: Harcamanıza ve kampanya uzunluğunuza karar verin. + <b>Hedeflemeyi özelleştirin</b>: Konuma veya ilgi alanlarına göre hedef kitle seçin ve olası erişimi görün. + <b>Bir ürün seçin</b>: Blaze ile neyi tanıtacağınızı seçin. + Stok Yönet + Stok yönetilmedi + Blaze\'in nasıl çalıştığını öğrenin Kampanyanızı başlatın Reklamlarınız WordPress.com ve Tumblr ağlarındaki milyonlarca sitede. Geniş bir kitleye ulaşın @@ -126,6 +147,7 @@ Language: tr %1$s ile %2$s öğe arasında %d öğe %d öğe + %1$.2f olan ürün miktarını %2$.2f olarak değiştir Yapılandırmayı kaydet Yapılandırma Ürün %s diff --git a/WooCommerce/src/main/res/values-zh-rCN/strings.xml b/WooCommerce/src/main/res/values-zh-rCN/strings.xml index e1efb019a6b..c6f2560383c 100644 --- a/WooCommerce/src/main/res/values-zh-rCN/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rCN/strings.xml @@ -1,18 +1,39 @@ + 确认详情 + 广告目的地 + 兴趣 + 位置 + 设备 + 语言 + 预算 + 详情 + 立即购买 + 编辑广告 + 预览 禁用 + 产品选择 + 选择产品 %s + <b>上线:</b>在推广活动开始后进行观察,并跟踪其成功情况。 + <b>快速审核:</b>提交您的广告,以供审核员进行快速检查。 + <b>设置您的预算:</b>决定您将要花费的资金以及广告活动的长度。 + <b>自定义目标:</b>按地点或兴趣选择受众,查看潜在覆盖范围。 + <b>选择产品:</b>选择要通过 Blaze 推广的产品。 + 管理库存 + 未管理的库存 开始您的广告活动 在 WordPress.com 和 Tumblr 网络中数以百万计的站点上发布您的广告。 接触广大受众 “我们的工具会在感兴趣的购物者可以找到的地方呈现您的产品。” 轻松简单实现全球覆盖 几分钟内发布广告 — 无需经验或高额预算,起价仅为 5 美元。 + 了解 Blaze 的运作机制 起步快,影响大 我们的工具旨在助力商家快速简单地进行广告活动设置,以最大限度地提高流量。 推广 @@ -126,6 +147,7 @@ Language: zh_CN %d 个项目 选择一个变体 选择变体 + 将产品数量从 %1$.2f 更改为 %2$.2f 保存配置 配置 产品 %s diff --git a/WooCommerce/src/main/res/values-zh-rTW/strings.xml b/WooCommerce/src/main/res/values-zh-rTW/strings.xml index 540ef36c711..2e8b786e652 100644 --- a/WooCommerce/src/main/res/values-zh-rTW/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rTW/strings.xml @@ -1,12 +1,33 @@ + 確認詳細資料 + 廣告目的地 + 興趣 + 地點 + 裝置 + 語言 + 預算 + 詳細資料 + 立即選購 + 編輯廣告 + 預覽 殘障人士 + 商品選擇 + 選擇商品 %s + <b>上線:</b>在行銷活動開始時予以觀察並追蹤是否成功。 + <b>快速審查:</b>提交你的廣告供審核員進行快速檢查。 + <b>設定預算:</b>決定預算與行銷活動長度。 + <b>自訂目標:</b>按地點或興趣選取觀眾,並檢視潛在觸及對象。 + <b>選擇商品:</b>選擇要透過 Blaze 推廣的商品。 + 管理庫存 + 未管理庫存 + 瞭解 Blaze 的運作方式 著手展開行銷活動 你的廣告會出現在 WordPress.com 與 Tumblr 網路中的數百萬個網站。 觸及廣大受眾 @@ -126,6 +147,7 @@ Language: zh_TW %1$s 至 %2$s 個項目 %d 個項目 %d 個項目 + 將商品數量從 %1$.2f 變更為 %2$.2f 儲存設定 設定 產品 %s From 638378c4ee2ca1b020433b8b87200e38dfff0609 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Fri, 26 Jan 2024 22:30:51 +0000 Subject: [PATCH 065/160] Bump version number --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index f143e99ba52..6c8ea60476e 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -versionName=17.0-rc-1 -versionCode=499 \ No newline at end of file +versionName=17.0 +versionCode=500 \ No newline at end of file From 665de990e41199fce1b685ad10faaf1cce0beeb7 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Fri, 26 Jan 2024 22:30:53 +0000 Subject: [PATCH 066/160] Update metadata translations for 17.0 --- .../metadata/android/ar/changelogs/500.txt | 2 + .../metadata/android/de-DE/changelogs/500.txt | 2 + .../metadata/android/en-US/changelogs/498.txt | 1 - .../metadata/android/en-US/changelogs/500.txt | 1 + .../metadata/android/es-ES/changelogs/500.txt | 2 + .../metadata/android/fr-FR/changelogs/500.txt | 2 + .../metadata/android/id/changelogs/500.txt | 2 + .../metadata/android/it-IT/changelogs/500.txt | 2 + .../metadata/android/iw-IL/changelogs/500.txt | 2 + .../metadata/android/ja-JP/changelogs/500.txt | 2 + .../metadata/android/ko-KR/changelogs/500.txt | 2 + .../metadata/android/nl-NL/changelogs/500.txt | 2 + .../metadata/android/pt-BR/changelogs/500.txt | 2 + fastlane/metadata/android/release_notes.xml | 68 ++++++++++++++++++- .../metadata/android/ru-RU/changelogs/500.txt | 2 + .../metadata/android/sv-SE/changelogs/500.txt | 2 + .../metadata/android/tr-TR/changelogs/500.txt | 2 + .../metadata/android/zh-CN/changelogs/500.txt | 2 + .../metadata/android/zh-TW/changelogs/500.txt | 2 + 19 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/ar/changelogs/500.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/500.txt delete mode 100644 fastlane/metadata/android/en-US/changelogs/498.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/500.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/500.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/500.txt create mode 100644 fastlane/metadata/android/id/changelogs/500.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/500.txt create mode 100644 fastlane/metadata/android/iw-IL/changelogs/500.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/500.txt create mode 100644 fastlane/metadata/android/ko-KR/changelogs/500.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/500.txt create mode 100644 fastlane/metadata/android/pt-BR/changelogs/500.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/500.txt create mode 100644 fastlane/metadata/android/sv-SE/changelogs/500.txt create mode 100644 fastlane/metadata/android/tr-TR/changelogs/500.txt create mode 100644 fastlane/metadata/android/zh-CN/changelogs/500.txt create mode 100644 fastlane/metadata/android/zh-TW/changelogs/500.txt diff --git a/fastlane/metadata/android/ar/changelogs/500.txt b/fastlane/metadata/android/ar/changelogs/500.txt new file mode 100644 index 00000000000..f20a59cbccf --- /dev/null +++ b/fastlane/metadata/android/ar/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +استعد لتجربة أكثر سلاسة مع آخر تحديث لدينا! لقد قمنا بحل الخلل المزعج الذي منع ظهور بطاقات الهدايا في قسم "الإجماليات" في أثناء إنشاء الطلب وتحريره. أصبحت الآن إدارة مبيعاتك من خلال تطبيق WooCommerce الخاص بنا أكثر سلاسة من أي وقت مضى. قم بالتحديث الآن واستمتع بتحسينات خالية من العقبات! diff --git a/fastlane/metadata/android/de-DE/changelogs/500.txt b/fastlane/metadata/android/de-DE/changelogs/500.txt new file mode 100644 index 00000000000..7e1f532fde8 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Genieße dank unserem neuesten Update ein reibungsloseres Erlebnis! Wir haben den lästigen Fehler behoben, wodurch Geschenkkarten während der Bestellerstellung und -bearbeitung nicht im Abschnitt „Gesamtbeträge“ angezeigt wurden. Die Verwaltung deiner Verkäufe mit unserer WooCommerce-App funktioniert nun reibungsloser als je zuvor. Führe jetzt ein Update durch, um von den praktischen Verbesserungen zu profitieren! diff --git a/fastlane/metadata/android/en-US/changelogs/498.txt b/fastlane/metadata/android/en-US/changelogs/498.txt deleted file mode 100644 index 1fc34f8058e..00000000000 --- a/fastlane/metadata/android/en-US/changelogs/498.txt +++ /dev/null @@ -1 +0,0 @@ -Fresh out of the oven! Our latest WooCommerce app update brings you a sleeker shopping cart experience. We've handily relocated the order creation to the bottom of your screen for easy thumb access, giving you more room to navigate. Plus, we've streamlined your workflow by detaching the order editing from the creation process. Get ready for a tidier, more efficient way to manage your sales. Tap "Update" and feel the difference! diff --git a/fastlane/metadata/android/en-US/changelogs/500.txt b/fastlane/metadata/android/en-US/changelogs/500.txt new file mode 100644 index 00000000000..af646718f68 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/500.txt @@ -0,0 +1 @@ +Get ready for a smoother experience with our latest update! We've ironed out the pesky bug that kept gift cards from showing up in the 'totals' section during order creation and editing. Managing your sales with our WooCommerce app is now more seamless than ever. Update now and enjoy the hassle-free enhancements! diff --git a/fastlane/metadata/android/es-ES/changelogs/500.txt b/fastlane/metadata/android/es-ES/changelogs/500.txt new file mode 100644 index 00000000000..73f726555c6 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Prepárate para disfrutar más y mejor con nuestra última actualización. Hemos acabado con el tedioso error que ocasionaba que las tarjetas regalo aparecieran siempre en el desglose del total durante la edición y creación de pedidos. Gestionar tus ventas es ahora coser y cantar con nuestra aplicación WooCommerce. Actualiza y disfruta de todas las mejoras para hacerte la vida más fácil. diff --git a/fastlane/metadata/android/fr-FR/changelogs/500.txt b/fastlane/metadata/android/fr-FR/changelogs/500.txt new file mode 100644 index 00000000000..8fe56dc2056 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0 : +Préparez-vous à connaître une expérience plus fluide grâce à notre dernière mise à jour ! Nous nous sommes débarrassés de ce bug agaçant à cause duquel les cartes-cadeaux apparaissaient sans cesse dans la section « Totaux » lors de la création et de la modification de la commande. Gérer vos ventes avec notre application WooCommerce est désormais plus fluide de jamais. Faites la mise à jour dès à présent et bénéficiez des améliorations sans prise de tête ! diff --git a/fastlane/metadata/android/id/changelogs/500.txt b/fastlane/metadata/android/id/changelogs/500.txt new file mode 100644 index 00000000000..030ffddf889 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Nikmati pengalaman pengguna yang lebih mulus dengan pembaruan terakhir kami! Kami sudah mengatasi bug yang membuat kartu hadiah tidak muncul di bagian 'total' dalam proses pembuatan dan pengeditan pesanan. Mengelola penjualan dengan aplikasi WooCommerce kini jauh lebih mudah. Perbarui sekarang lalu nikmati fitur dan fungsi aplikasi tanpa repot! diff --git a/fastlane/metadata/android/it-IT/changelogs/500.txt b/fastlane/metadata/android/it-IT/changelogs/500.txt new file mode 100644 index 00000000000..cfdf2ba9438 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Preparati a un'esperienza più fluida con il nostro ultimo aggiornamento! Abbiamo risolto il fastidioso bug che impediva alle gift card di comparire nella sezione "totali" durante la creazione e la modifica degli ordini. La gestione delle vendite con la nostra app WooCommerce è ora più semplice che mai. Effettua l'aggiornamento e goditi i miglioramenti senza stress! diff --git a/fastlane/metadata/android/iw-IL/changelogs/500.txt b/fastlane/metadata/android/iw-IL/changelogs/500.txt new file mode 100644 index 00000000000..39edad00523 --- /dev/null +++ b/fastlane/metadata/android/iw-IL/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +העדכון האחרון מבטיח חוויית שימוש חלקה יותר כעת! מחצנו כמה באגים שמנעו תצוגה של שוברי מתנה במקטע 'סך הכול' במהלך יצירה ועריכה של הזמנה. עכשיו ניתן לנהל מבצעים באפליקציה של WooCommerce בצורה יעילה עוד יותר. כדאי לעדכן עכשיו כדי ליהנות משיפורים לעבודה קלה יותר! diff --git a/fastlane/metadata/android/ja-JP/changelogs/500.txt b/fastlane/metadata/android/ja-JP/changelogs/500.txt new file mode 100644 index 00000000000..724a2dcd269 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +最新のアップデートで、操作環境をスムーズにする準備をしましょう。 注文の作成と編集中にギフトカードが「合計」セクションに表示されないという厄介なバグを修正しました。 WooCommerce アプリを使用した販売管理がこれまで以上にシームレスになりました。 今すぐアップデートして手間のかからない強化された機能をご利用ください。 diff --git a/fastlane/metadata/android/ko-KR/changelogs/500.txt b/fastlane/metadata/android/ko-KR/changelogs/500.txt new file mode 100644 index 00000000000..764d32d1e68 --- /dev/null +++ b/fastlane/metadata/android/ko-KR/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +최신 업데이트로 더 원활한 환경을 준비하세요. 주문 생성 및 편집 중 '합계' 섹션에 상품권이 표시되지 않던 성가신 버그를 해결했습니다. 이제 그 어느 때보다 원활하게 우커머스 앱으로 판매를 관리할 수 있습니다. 지금 업데이트하여 편리하게 향상된 기능을 누리세요. diff --git a/fastlane/metadata/android/nl-NL/changelogs/500.txt b/fastlane/metadata/android/nl-NL/changelogs/500.txt new file mode 100644 index 00000000000..da4c3e5e1e7 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Bereid je voor op een vloeiendere ervaring met onze laatste update! We hebben het hardnekkige probleem verholpen waarbij cadeaubonnen niet werden weergegeven in het gedeelte 'totaal' tijdens het aanmaken en bewerken van bestellingen. Het beheren van je verkopen met onze WooCommerce-app is nu naadlozer dan ooit. Update nu en geniet van de handige verbeteringen! diff --git a/fastlane/metadata/android/pt-BR/changelogs/500.txt b/fastlane/metadata/android/pt-BR/changelogs/500.txt new file mode 100644 index 00000000000..868a0e0ca2a --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Prepare-se para uma experiência mais tranquila com a nossa mais nova atualização. Eliminamos o bug irritante que impedia que vales-presentes aparecessem na seção de totais na criação e edição do pedido. O gerenciamento das suas vendas no app do WooCommerce ficou mais simples do que nunca! Atualize agora e aproveite as melhorias descomplicadas. diff --git a/fastlane/metadata/android/release_notes.xml b/fastlane/metadata/android/release_notes.xml index d8826fabb4f..5309db794ca 100644 --- a/fastlane/metadata/android/release_notes.xml +++ b/fastlane/metadata/android/release_notes.xml @@ -1,4 +1,68 @@ + +17.0: +استعد لتجربة أكثر سلاسة مع آخر تحديث لدينا! لقد قمنا بحل الخلل المزعج الذي منع ظهور بطاقات الهدايا في قسم "الإجماليات" في أثناء إنشاء الطلب وتحريره. أصبحت الآن إدارة مبيعاتك من خلال تطبيق WooCommerce الخاص بنا أكثر سلاسة من أي وقت مضى. قم بالتحديث الآن واستمتع بتحسينات خالية من العقبات! + + +17.0: +Genieße dank unserem neuesten Update ein reibungsloseres Erlebnis! Wir haben den lästigen Fehler behoben, wodurch Geschenkkarten während der Bestellerstellung und -bearbeitung nicht im Abschnitt „Gesamtbeträge“ angezeigt wurden. Die Verwaltung deiner Verkäufe mit unserer WooCommerce-App funktioniert nun reibungsloser als je zuvor. Führe jetzt ein Update durch, um von den praktischen Verbesserungen zu profitieren! + + +17.0: +Prepárate para disfrutar más y mejor con nuestra última actualización. Hemos acabado con el tedioso error que ocasionaba que las tarjetas regalo aparecieran siempre en el desglose del total durante la edición y creación de pedidos. Gestionar tus ventas es ahora coser y cantar con nuestra aplicación WooCommerce. Actualiza y disfruta de todas las mejoras para hacerte la vida más fácil. + + +17.0 : +Préparez-vous à connaître une expérience plus fluide grâce à notre dernière mise à jour ! Nous nous sommes débarrassés de ce bug agaçant à cause duquel les cartes-cadeaux apparaissaient sans cesse dans la section « Totaux » lors de la création et de la modification de la commande. Gérer vos ventes avec notre application WooCommerce est désormais plus fluide de jamais. Faites la mise à jour dès à présent et bénéficiez des améliorations sans prise de tête ! + + +17.0: +העדכון האחרון מבטיח חוויית שימוש חלקה יותר כעת! מחצנו כמה באגים שמנעו תצוגה של שוברי מתנה במקטע 'סך הכול' במהלך יצירה ועריכה של הזמנה. עכשיו ניתן לנהל מבצעים באפליקציה של WooCommerce בצורה יעילה עוד יותר. כדאי לעדכן עכשיו כדי ליהנות משיפורים לעבודה קלה יותר! + + +17.0: +Nikmati pengalaman pengguna yang lebih mulus dengan pembaruan terakhir kami! Kami sudah mengatasi bug yang membuat kartu hadiah tidak muncul di bagian 'total' dalam proses pembuatan dan pengeditan pesanan. Mengelola penjualan dengan aplikasi WooCommerce kini jauh lebih mudah. Perbarui sekarang lalu nikmati fitur dan fungsi aplikasi tanpa repot! + + +17.0: +Preparati a un'esperienza più fluida con il nostro ultimo aggiornamento! Abbiamo risolto il fastidioso bug che impediva alle gift card di comparire nella sezione "totali" durante la creazione e la modifica degli ordini. La gestione delle vendite con la nostra app WooCommerce è ora più semplice che mai. Effettua l'aggiornamento e goditi i miglioramenti senza stress! + + +17.0: +最新のアップデートで、操作環境をスムーズにする準備をしましょう。 注文の作成と編集中にギフトカードが「合計」セクションに表示されないという厄介なバグを修正しました。 WooCommerce アプリを使用した販売管理がこれまで以上にシームレスになりました。 今すぐアップデートして手間のかからない強化された機能をご利用ください。 + + +17.0: +최신 업데이트로 더 원활한 환경을 준비하세요. 주문 생성 및 편집 중 '합계' 섹션에 상품권이 표시되지 않던 성가신 버그를 해결했습니다. 이제 그 어느 때보다 원활하게 우커머스 앱으로 판매를 관리할 수 있습니다. 지금 업데이트하여 편리하게 향상된 기능을 누리세요. + + +17.0: +Bereid je voor op een vloeiendere ervaring met onze laatste update! We hebben het hardnekkige probleem verholpen waarbij cadeaubonnen niet werden weergegeven in het gedeelte 'totaal' tijdens het aanmaken en bewerken van bestellingen. Het beheren van je verkopen met onze WooCommerce-app is nu naadlozer dan ooit. Update nu en geniet van de handige verbeteringen! + + +17.0: +Prepare-se para uma experiência mais tranquila com a nossa mais nova atualização. Eliminamos o bug irritante que impedia que vales-presentes aparecessem na seção de totais na criação e edição do pedido. O gerenciamento das suas vendas no app do WooCommerce ficou mais simples do que nunca! Atualize agora e aproveite as melhorias descomplicadas. + + +17.0: +Очередное обновление сделает вашу работу ещё приятнее! Мы нашли и ликвидировали досадную ошибку, из-за которой подарочные карты не отображались в разделе «Итоги» при создании и редактировании заказа. Управлять продажами в приложении WooCommerce стало ещё проще. Установите обновление и получайте удовольствие от работы с новыми функциями без сбоев! + + +17.0: +Gör dig redo för en smidigare upplevelse med vår senaste uppdatering. Vi har åtgärdat den irriterande buggen som hindrade presentkort från att visas i sektionen "Totalsummor" vid skapande och redigering av beställningar. Att hantera din försäljning med vår WooCommerce-app är nu mer sömlöst än någonsin. Uppdatera nu och ta del av förbättringar utan krångel. + + +17.0: +En yeni güncellememizle daha sorunsuz bir deneyime hazır olun! Sipariş oluşturma ve düzenleme sırasında hediye kartlarının "toplam" bölümünde görünmesini engelleyen rahatsız edici hatayı düzelttik. WooCommerce uygulamasıyla satışlarınızı yönetmek artık her zamankinden daha kusursuz. Şimdi güncelleyin ve sorunsuz iyileştirmelerin keyfini çıkarın! + + +17.0: +最新更新将为您带来更顺畅的体验,准备好享受吧! 我们修复了一个恼人的错误,即在创建和编辑订单时,礼品卡不会显示在“总计”部分。 现在,使用我们的 WooCommerce 应用程序管理您的销售比以往任何时候都更加顺畅。 立即更新,享受轻松无忧的增强功能! + + +17.0: +準備好從最新更新獲得更流暢的體驗! 我們已修正在建立和編輯訂單時,導致「總計」區塊無法顯示禮物卡的惱人錯誤。 使用 WooCommerce 應用程式管理銷售時,現在變得比以往更加流暢。 立即更新,並享受使用便利的強化功能! + -16.9: -Fresh out of the oven! Our latest WooCommerce app update brings you a sleeker shopping cart experience. We've handily relocated the order creation to the bottom of your screen for easy thumb access, giving you more room to navigate. Plus, we've streamlined your workflow by detaching the order editing from the creation process. Get ready for a tidier, more efficient way to manage your sales. Tap "Update" and feel the difference! +17.0: +Get ready for a smoother experience with our latest update! We've ironed out the pesky bug that kept gift cards from showing up in the 'totals' section during order creation and editing. Managing your sales with our WooCommerce app is now more seamless than ever. Update now and enjoy the hassle-free enhancements! diff --git a/fastlane/metadata/android/ru-RU/changelogs/500.txt b/fastlane/metadata/android/ru-RU/changelogs/500.txt new file mode 100644 index 00000000000..002d5c148a5 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Очередное обновление сделает вашу работу ещё приятнее! Мы нашли и ликвидировали досадную ошибку, из-за которой подарочные карты не отображались в разделе «Итоги» при создании и редактировании заказа. Управлять продажами в приложении WooCommerce стало ещё проще. Установите обновление и получайте удовольствие от работы с новыми функциями без сбоев! diff --git a/fastlane/metadata/android/sv-SE/changelogs/500.txt b/fastlane/metadata/android/sv-SE/changelogs/500.txt new file mode 100644 index 00000000000..4df30cf56ca --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +Gör dig redo för en smidigare upplevelse med vår senaste uppdatering. Vi har åtgärdat den irriterande buggen som hindrade presentkort från att visas i sektionen "Totalsummor" vid skapande och redigering av beställningar. Att hantera din försäljning med vår WooCommerce-app är nu mer sömlöst än någonsin. Uppdatera nu och ta del av förbättringar utan krångel. diff --git a/fastlane/metadata/android/tr-TR/changelogs/500.txt b/fastlane/metadata/android/tr-TR/changelogs/500.txt new file mode 100644 index 00000000000..ff0562e1e1d --- /dev/null +++ b/fastlane/metadata/android/tr-TR/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +En yeni güncellememizle daha sorunsuz bir deneyime hazır olun! Sipariş oluşturma ve düzenleme sırasında hediye kartlarının "toplam" bölümünde görünmesini engelleyen rahatsız edici hatayı düzelttik. WooCommerce uygulamasıyla satışlarınızı yönetmek artık her zamankinden daha kusursuz. Şimdi güncelleyin ve sorunsuz iyileştirmelerin keyfini çıkarın! diff --git a/fastlane/metadata/android/zh-CN/changelogs/500.txt b/fastlane/metadata/android/zh-CN/changelogs/500.txt new file mode 100644 index 00000000000..555ac7f25df --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +最新更新将为您带来更顺畅的体验,准备好享受吧! 我们修复了一个恼人的错误,即在创建和编辑订单时,礼品卡不会显示在“总计”部分。 现在,使用我们的 WooCommerce 应用程序管理您的销售比以往任何时候都更加顺畅。 立即更新,享受轻松无忧的增强功能! diff --git a/fastlane/metadata/android/zh-TW/changelogs/500.txt b/fastlane/metadata/android/zh-TW/changelogs/500.txt new file mode 100644 index 00000000000..4246525245a --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/500.txt @@ -0,0 +1,2 @@ +17.0: +準備好從最新更新獲得更流暢的體驗! 我們已修正在建立和編輯訂單時,導致「總計」區塊無法顯示禮物卡的惱人錯誤。 使用 WooCommerce 應用程式管理銷售時,現在變得比以往更加流暢。 立即更新,並享受使用便利的強化功能! From 09b891c63f7f39e70844371420f72a96ee6c3e53 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Sat, 27 Jan 2024 00:04:02 +0000 Subject: [PATCH 067/160] Bump version number --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index 6c8ea60476e..8de50a4ec44 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -versionName=17.0 -versionCode=500 \ No newline at end of file +versionName=17.1-rc-1 +versionCode=501 \ No newline at end of file From 789ab7b282ac845befca2825e9861f71df9b92c4 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Sat, 27 Jan 2024 00:04:02 +0000 Subject: [PATCH 068/160] Update draft release notes for 17.1. --- WooCommerce/metadata/release_notes.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/metadata/release_notes.txt b/WooCommerce/metadata/release_notes.txt index af646718f68..d109fa17eb1 100644 --- a/WooCommerce/metadata/release_notes.txt +++ b/WooCommerce/metadata/release_notes.txt @@ -1 +1,2 @@ -Get ready for a smoother experience with our latest update! We've ironed out the pesky bug that kept gift cards from showing up in the 'totals' section during order creation and editing. Managing your sales with our WooCommerce app is now more seamless than ever. Update now and enjoy the hassle-free enhancements! +- [*] [Internal] Fixed crash when going to background from the order creation screen [https://github.com/woocommerce/woocommerce-android/pull/10600] + From a0ae51bfb80cd484e70ad15869011154b602a60f Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Sat, 27 Jan 2024 00:04:02 +0000 Subject: [PATCH 069/160] Release Notes: add new section for next version (17.2) --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 74134cbe787..ec4d3658913 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,4 +1,8 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] +17.2 +----- + + 17.1 ----- - [*] [Internal] Fixed crash when going to background from the order creation screen [https://github.com/woocommerce/woocommerce-android/pull/10600] From b49e94bc39afbdc498bc567fbb79b4d1ec0e1ad7 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 26 Jan 2024 16:14:14 -0800 Subject: [PATCH 070/160] Update FluxC to `2.64.0` --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7bf7df5415c..35ec6228c00 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = 'trunk-bf45b08090e8241eb930e1eef32f935bc6cd0424' + fluxCVersion = '2.64.0' glideVersion = '4.13.2' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 980077d91dad06d9caddb95b5ce953ee6d9d6dfa Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Fri, 26 Jan 2024 16:14:52 -0800 Subject: [PATCH 071/160] Update release notes for 17.1 --- WooCommerce/metadata/release_notes.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/metadata/release_notes.txt b/WooCommerce/metadata/release_notes.txt index d109fa17eb1..9d6a9642a4f 100644 --- a/WooCommerce/metadata/release_notes.txt +++ b/WooCommerce/metadata/release_notes.txt @@ -1,2 +1 @@ -- [*] [Internal] Fixed crash when going to background from the order creation screen [https://github.com/woocommerce/woocommerce-android/pull/10600] - +This release focuses on bug fixes and improvements to help you get your business started. Keep your feedback rolling in; it helps us figure out what to work on next. From c9b5c844e337c57d1291621148f04aef06e701d5 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Sat, 27 Jan 2024 00:17:44 +0000 Subject: [PATCH 072/160] Update `PlayStoreStrings.po` for 17.1 --- WooCommerce/metadata/PlayStoreStrings.pot | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/WooCommerce/metadata/PlayStoreStrings.pot b/WooCommerce/metadata/PlayStoreStrings.pot index 72661766035..8f4d562d0ff 100644 --- a/WooCommerce/metadata/PlayStoreStrings.pot +++ b/WooCommerce/metadata/PlayStoreStrings.pot @@ -11,16 +11,16 @@ msgstr "" "Project-Id-Version: Release Notes & Play Store Descriptions\n" #. translators: Release notes for this version to be displayed in the Play Store. Limit to 500 characters including spaces and commas! -msgctxt "release_note_170" +msgctxt "release_note_171" msgid "" -"17.0:\n" -"Get ready for a smoother experience with our latest update! We've ironed out the pesky bug that kept gift cards from showing up in the 'totals' section during order creation and editing. Managing your sales with our WooCommerce app is now more seamless than ever. Update now and enjoy the hassle-free enhancements!\n" +"17.1:\n" +"This release focuses on bug fixes and improvements to help you get your business started. Keep your feedback rolling in; it helps us figure out what to work on next.\n" msgstr "" -msgctxt "release_note_169" +msgctxt "release_note_170" msgid "" -"16.9:\n" -"Fresh out of the oven! Our latest WooCommerce app update brings you a sleeker shopping cart experience. We've handily relocated the order creation to the bottom of your screen for easy thumb access, giving you more room to navigate. Plus, we've streamlined your workflow by detaching the order editing from the creation process. Get ready for a tidier, more efficient way to manage your sales. Tap "Update" and feel the difference!\n" +"17.0:\n" +"Get ready for a smoother experience with our latest update! We've ironed out the pesky bug that kept gift cards from showing up in the 'totals' section during order creation and editing. Managing your sales with our WooCommerce app is now more seamless than ever. Update now and enjoy the hassle-free enhancements!\n" msgstr "" #. translators: Short description of the app to be displayed in the Play Store. Limit to 80 characters including spaces and commas! From 3fcf99941e3fe8329ff33508ee5461f3e4829539 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Sat, 27 Jan 2024 00:17:45 +0000 Subject: [PATCH 073/160] Send strings to translation. --- fastlane/resources/values/strings.xml | 39 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/fastlane/resources/values/strings.xml b/fastlane/resources/values/strings.xml index 0c2d807230c..c3319c2b7db 100644 --- a/fastlane/resources/values/strings.xml +++ b/fastlane/resources/values/strings.xml @@ -2041,9 +2041,10 @@ Explore! Scan barcode to update inventory We value your input! - You\'ve used our AI-assisted feature to add products multiple times now. We\'d love to hear your thoughts to make it even better. + Got a minute? Help us improve our AI-assisted features with your quick feedback. Start the Survey - Skip + Remind Me Later + Don\’t show it Again Preview - Edit ad + Edit ad Shop now Details Budget @@ -3835,6 +3836,38 @@ Interests Ad destination Confirm Details + %1$s days from %2$s + All + + Set your budget + How much would you like to spend on your product promotion campaign? + Total spend + for %1$s days + %1$s daily + Estimated people reached per day + Duration + Edit + Update + Impressions + Done + Impressions reflect the frequency with which your ad appears to potential customers.\n\n + While exact numbers can\'t be assured due to fluctuating online traffic and user behavior, we aim to match your ad\'s actual impressions as closely as possible to your target count.\n\n + Remember, impressions are about visibility, not action taken by viewers. + Set duration + %1$s days + Starts + Apply + + + Change image + Tagline + Description + %d characters remaining + Suggested by AI From ba0cb306681c5a01ffd0cd6a1f260dc5113ed533 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 14:50:45 +0100 Subject: [PATCH 074/160] Add the functions to fetch & observe languages --- .../com/woocommerce/android/ui/blaze/BlazeRepository.kt | 6 ++++++ 1 file changed, 6 insertions(+) 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 f339617f7d0..feef8c1de85 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 @@ -4,6 +4,7 @@ import android.os.Parcelable import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.util.TimezoneProvider +import kotlinx.coroutines.flow.map import kotlinx.parcelize.Parcelize import org.wordpress.android.fluxc.persistence.blaze.BlazeCampaignsDao.BlazeAdSuggestionEntity import org.wordpress.android.fluxc.store.blaze.BlazeCampaignsStore @@ -26,6 +27,11 @@ class BlazeRepository @Inject constructor( const val ONE_DAY_IN_MILLIS = 1000 * 60 * 60 * 24 } + fun observeLanguages() = blazeCampaignsStore.observeBlazeTargetingLanguages() + .map { it.map { language -> Language(language.id, language.name) } } + + suspend fun fetchLanguages() = blazeCampaignsStore.fetchBlazeTargetingLanguages() + suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get()) suspend fun getAdSuggestions(productId: Long): List? { From 390962f86b1058958cbeec9e0b73607c09978db9 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 14:51:05 +0100 Subject: [PATCH 075/160] Add BlazeTargetType enum --- .../ui/blaze/creation/targets/BlazeTargetType.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeTargetType.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeTargetType.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeTargetType.kt new file mode 100644 index 00000000000..5a087429de8 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeTargetType.kt @@ -0,0 +1,12 @@ +package com.woocommerce.android.ui.blaze.creation.targets + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +enum class BlazeTargetType : Parcelable { + LANGUAGE, + DEVICE, + INTEREST, + LOCATION +} From 3925cd64be756067314010adc7d64e6b600e3674 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 14:52:37 +0100 Subject: [PATCH 076/160] Add target selection navigation --- .../BlazeCampaignCreationPreviewFragment.kt | 13 +++++++++++++ .../BlazeCampaignTargetSelectionFragment.kt | 8 ++++++++ .../nav_graph_blaze_campaign_creation.xml | 14 ++++++++++++++ 3 files changed, 35 insertions(+) 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 087d1ab0394..b708dc42a64 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 @@ -13,6 +13,9 @@ import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdF import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.EditAdResult 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.NavigateToTargetSelectionScreen +import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionFragment +import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionViewModel.TargetSelectionResult import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit @@ -54,6 +57,13 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { event.campaignImageUrl ) ) + is NavigateToTargetSelectionScreen -> findNavController().navigateSafely( + BlazeCampaignCreationPreviewFragmentDirections + .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignTargetSelectionFragment( + event.targetType, + event.selectedIds.toTypedArray() + ) + ) } } } @@ -62,5 +72,8 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { handleResult(BlazeCampaignCreationEditAdFragment.EDIT_AD_RESULT) { viewModel.onAdUpdated(it.tagline, it.description, it.campaignImageUrl) } + handleResult(BlazeCampaignTargetSelectionFragment.BLAZE_TARGET_SELECTION_RESULT) { + viewModel.onTargetSelectionUpdated(it.targetType, it.selectedIds) + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt index 0e7e47cf1b5..a6481f18b63 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionFragment.kt @@ -6,14 +6,20 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class BlazeCampaignTargetSelectionFragment : BaseFragment() { + companion object { + const val BLAZE_TARGET_SELECTION_RESULT = "blaze_target_selection_result" + } + override val activityAppBarStatus: AppBarStatus get() = AppBarStatus.Hidden @@ -21,6 +27,7 @@ class BlazeCampaignTargetSelectionFragment : BaseFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return composeView { + BlazeCampaignTargetSelectionScreen(viewModel) } } @@ -33,6 +40,7 @@ class BlazeCampaignTargetSelectionFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { is MultiLiveEvent.Event.Exit -> findNavController().popBackStack() + is ExitWithResult<*> -> navigateBackWithResult(BLAZE_TARGET_SELECTION_RESULT, event.data) } } } 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 2993f47b054..22ed9b6623b 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 @@ -56,6 +56,9 @@ + + + + + From b52091422a0d49a043c379e14a91537104b8e8a6 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 14:55:17 +0100 Subject: [PATCH 077/160] Handle language selection in the preview ViewModel --- .../BlazeCampaignCreationPreviewViewModel.kt | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 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 7f1f8503b8a..7e2238a0fb1 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 @@ -16,6 +16,9 @@ import com.woocommerce.android.ui.blaze.BlazeRepository.Language import com.woocommerce.android.ui.blaze.BlazeRepository.Location import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading +import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType +import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.DEVICE +import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.LANGUAGE import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider @@ -41,27 +44,35 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val adDetails = savedStateHandle.getStateFlow(viewModelScope, Loading) private val budget = savedStateHandle.getStateFlow(viewModelScope, getDefaultBudget()) - private val selectedLanguages = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) - private val selectedDevices = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) - private val selectedInterests = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) - private val selectedLocations = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val languages = blazeRepository.observeLanguages() + private val selectedLanguages = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val selectedDevices = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val selectedInterests = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val selectedLocations = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) val viewState = combine( adDetails, budget, + languages, selectedLanguages, selectedDevices, selectedInterests, selectedLocations - ) { adDetails, budget, languages, devices, interests, locations -> + ) { adDetails, budget, languages, selectedLanguages, selectedDevices, selectedInterests, selectedLocations -> CampaignPreviewUiState( adDetails = adDetails, - campaignDetails = campaign.toCampaignDetailsUi(budget, languages, devices, locations, interests) + campaignDetails = campaign.toCampaignDetailsUi( + budget, + languages.filter { it.code in selectedLanguages }, + emptyList(), + emptyList(), + emptyList() + ) ) }.asLiveData() init { - loadSuggestions() + loadData() } fun onBackPressed() { @@ -92,8 +103,18 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } } - private fun loadSuggestions() { + fun onTargetSelectionUpdated(targetType: BlazeTargetType, selectedIds: List) { launch { + when (targetType) { + LANGUAGE -> selectedLanguages.update { selectedIds } + else -> Unit + } + } + } + + private fun loadData() { + launch { + blazeRepository.fetchLanguages() blazeRepository.getAdSuggestions(navArgs.productId).let { suggestions -> adDetails.update { AdDetails( @@ -111,8 +132,8 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( budget: Budget, languages: List, devices: List, - locations: List, - interests: List + interests: List, + locations: List ) = CampaignDetailsUi( budget = CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget), @@ -124,13 +145,15 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_language), displayValue = languages.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add language selection */ }, + onItemSelected = { + triggerEvent(NavigateToTargetSelectionScreen(LANGUAGE, languages.map { it.code })) + }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_devices), displayValue = devices.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add devices selection */ }, + onItemSelected = { /* TODO Add device selection */ }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location), @@ -213,4 +236,8 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) object NavigateToBudgetScreen : MultiLiveEvent.Event() + data class NavigateToTargetSelectionScreen( + val targetType: BlazeTargetType, + val selectedIds: List + ) : MultiLiveEvent.Event() } From 6dfb0a0210724aaa8fd9c96f4a3021a17c3cc658 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 14:55:51 +0100 Subject: [PATCH 078/160] Add target selection screen UI --- .../BlazeCampaignTargetSelectionScreen.kt | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt index 0c9d32089ef..8155a962bc5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt @@ -1,7 +1,100 @@ package com.woocommerce.android.ui.blaze.creation.targets +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.icons.Icons.Filled +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.woocommerce.android.R.string +import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionViewModel.TargetItem +import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionViewModel.ViewState +import com.woocommerce.android.ui.compose.component.MultiSelectAllItemsButton +import com.woocommerce.android.ui.compose.component.MultiSelectList +import com.woocommerce.android.ui.compose.component.Toolbar +import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews @Composable fun BlazeCampaignTargetSelectionScreen(viewModel: BlazeCampaignTargetSelectionViewModel) { + viewModel.viewState.observeAsState().value?.let { state -> + TargetSelectionScreen( + state = state, + onBackPressed = viewModel::onBackPressed, + onSaveTapped = viewModel::onSaveTapped, + onItemTapped = viewModel::onItemTapped, + onAllButtonTapped = viewModel::onAllButtonTapped + ) + } +} + +@Composable +private fun TargetSelectionScreen( + state: ViewState, + onBackPressed: () -> Unit, + onSaveTapped: () -> Unit, + onItemTapped: (TargetItem) -> Unit, + onAllButtonTapped: () -> Unit +) { + Scaffold( + topBar = { + Toolbar( + title = state.title, + onNavigationButtonClick = onBackPressed, + navigationIcon = Filled.ArrowBack, + actionButtonText = stringResource(id = string.save).uppercase(), + onActionButtonClick = onSaveTapped + ) + }, + modifier = Modifier.background(MaterialTheme.colors.surface) + ) { paddingValues -> + MultiSelectList( + items = state.items, + selectedItems = state.selectedItems, + itemFormatter = { value }, + onItemToggled = onItemTapped, + allItemsButton = MultiSelectAllItemsButton( + text = stringResource(id = string.blaze_campaign_preview_target_default_value), + onClicked = onAllButtonTapped + ), + modifier = Modifier + .background(MaterialTheme.colors.surface) + .padding(paddingValues) + ) + } +} + + +@LightDarkThemePreviews +@Composable +fun PreviewTargetSelectionScreen() { + TargetSelectionScreen( + state = ViewState( + items = listOf( + TargetItem("1", "Item 1"), + TargetItem("2", "Item 2"), + TargetItem("3", "Item 3"), + TargetItem("4", "Item 4"), + TargetItem("5", "Item 5"), + TargetItem("6", "Item 6"), + TargetItem("7", "Item 7"), + TargetItem("8", "Item 8"), + TargetItem("9", "Item 9") + ), + selectedItems = listOf( + TargetItem("4", "Item 4"), + TargetItem("5", "Item 5"), + TargetItem("8", "Item 8"), + TargetItem("9", "Item 9") + ), + title = "Title" + ), + onBackPressed = { /*TODO*/ }, + onSaveTapped = { /*TODO*/ }, + onItemTapped = {}, + onAllButtonTapped = {} + ) } From b3c87cd854b564f9b985adb40f4e695709b3d093 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 14:58:40 +0100 Subject: [PATCH 079/160] Add target selection logic --- .../BlazeCampaignTargetSelectionViewModel.kt | 95 +++++++++++++++++-- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index dc39ed7f34c..dcad5645b31 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -1,23 +1,100 @@ package com.woocommerce.android.ui.blaze.creation.targets +import android.os.Parcelable import androidx.lifecycle.SavedStateHandle -import com.woocommerce.android.ui.products.ProductListRepository -import com.woocommerce.android.ui.products.ProductStatus -import com.woocommerce.android.util.CoroutineDispatchers -import com.woocommerce.android.util.WooLog -import com.woocommerce.android.viewmodel.MultiLiveEvent +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.woocommerce.android.R +import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult +import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.getStateFlow import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.wordpress.android.fluxc.store.WCProductStore.ProductFilterOption -import org.wordpress.android.fluxc.store.WCProductStore.ProductSorting +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import kotlinx.parcelize.Parcelize import javax.inject.Inject @HiltViewModel class BlazeCampaignTargetSelectionViewModel @Inject constructor( + private val resourceProvider: ResourceProvider, + blazeRepository: BlazeRepository, savedStateHandle: SavedStateHandle, ) : ScopedViewModel(savedStateHandle) { + private val navArgs: BlazeCampaignTargetSelectionFragmentArgs by savedStateHandle.navArgs() + + private val items: Flow> = when (navArgs.targetType) { + BlazeTargetType.LANGUAGE -> blazeRepository.observeLanguages().map { languages -> + languages.map { language -> + TargetItem( + id = language.code, + value = language.name + ) + } + } + else -> flowOf(emptyList()) + } + + private val selectedIds = savedStateHandle.getStateFlow(viewModelScope, navArgs.selectedIds.toSet()) + + val viewState = combine(items, selectedIds) { items, selectedIds -> + ViewState( + items = items, + selectedItems = selectedIds.map { id -> items.first { it.id == id } }, + title = when (navArgs.targetType) { + BlazeTargetType.LANGUAGE -> resourceProvider.getString(R.string.blaze_campaign_preview_details_language) + else -> "" + } + ) + }.asLiveData() + + fun onItemTapped(item: TargetItem) { + selectedIds.update { selectedIds -> + if (selectedIds.contains(item.id)) { + selectedIds - item.id + } else { + selectedIds + item.id + } + } + } + + fun onAllButtonTapped() { + selectedIds.update { emptySet() } + } + + fun onBackPressed() { + triggerEvent(Exit) + } + + fun onSaveTapped() { + // Empty selection set means all items are selected + val result = if (selectedIds.value.size == viewState.value?.items?.size) + emptyList() + else + selectedIds.value.toList() + triggerEvent(ExitWithResult(TargetSelectionResult(navArgs.targetType, result))) + } + + data class TargetItem( + val id: String, + val value: String, + ) + + data class ViewState( + val items: List, + val selectedItems: List, + val title: String + ) + + @Parcelize + data class TargetSelectionResult( + val targetType: BlazeTargetType, + val selectedIds: List + ) : Parcelable } From 7b67888dce3560f58eaff01afe07e9bd1ec3c97b Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 15:00:40 +0100 Subject: [PATCH 080/160] Fix ktlint issues --- .../creation/preview/BlazeCampaignCreationPreviewViewModel.kt | 1 - .../creation/targets/BlazeCampaignTargetSelectionScreen.kt | 1 - .../creation/targets/BlazeCampaignTargetSelectionViewModel.kt | 4 ++-- 3 files changed, 2 insertions(+), 4 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 7e2238a0fb1..2fdf28ee18b 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 @@ -17,7 +17,6 @@ import com.woocommerce.android.ui.blaze.BlazeRepository.Location import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType -import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.DEVICE import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.LANGUAGE import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt index 8155a962bc5..6cc1dfaaa46 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt @@ -67,7 +67,6 @@ private fun TargetSelectionScreen( } } - @LightDarkThemePreviews @Composable fun PreviewTargetSelectionScreen() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index dcad5645b31..139ae0e1a0a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -14,10 +14,10 @@ import com.woocommerce.android.viewmodel.getStateFlow import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.parcelize.Parcelize import javax.inject.Inject From ec280ac139ae15023ac25cc3668f5032a724d5e2 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Sat, 27 Jan 2024 15:10:26 +0100 Subject: [PATCH 081/160] Remove unused parameters --- .../preview/BlazeCampaignCreationPreviewViewModel.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 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 2fdf28ee18b..fb23ea12acb 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 @@ -5,7 +5,6 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.R.string -import com.woocommerce.android.extensions.combine import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.Budget @@ -25,6 +24,7 @@ import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.getStateFlow import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -45,19 +45,13 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val budget = savedStateHandle.getStateFlow(viewModelScope, getDefaultBudget()) private val languages = blazeRepository.observeLanguages() private val selectedLanguages = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) - private val selectedDevices = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) - private val selectedInterests = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) - private val selectedLocations = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) val viewState = combine( adDetails, budget, languages, - selectedLanguages, - selectedDevices, - selectedInterests, - selectedLocations - ) { adDetails, budget, languages, selectedLanguages, selectedDevices, selectedInterests, selectedLocations -> + selectedLanguages + ) { adDetails, budget, languages, selectedLanguages -> CampaignPreviewUiState( adDetails = adDetails, campaignDetails = campaign.toCampaignDetailsUi( From aa15e2b4dd1b477fed4266695faf38ec8d3547b2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 09:33:40 +0100 Subject: [PATCH 082/160] Support of a dev via injectable flag for testing --- .../ui/payments/receipt/PaymentReceiptHelper.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index cf07df75256..0895f192066 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.AppPrefsWrapper -import com.woocommerce.android.BuildConfig import com.woocommerce.android.extensions.semverCompareTo import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType @@ -15,6 +14,7 @@ class PaymentReceiptHelper @Inject constructor( private val wooCommerceStore: WooCommerceStore, private val appPrefsWrapper: AppPrefsWrapper, private val orderStore: WCOrderStore, + private val isDevSiteSupported: IsDevSiteSupported, ) { fun storeReceiptUrl(orderId: Long, receiptUrl: String) { selectedSite.get().let { @@ -83,10 +83,9 @@ class PaymentReceiptHelper @Inject constructor( WooCommerceStore.WooPlugin.WOO_CORE ) return if (sitePlugin == null) { - if (BuildConfig.DEBUG) { + if (isDevSiteSupported()) { wooCommerceStore.getSitePlugins(selectedSite.get()) - .firstOrNull { it.name == "woocommerce-dev/woocommerce" } - ?.let { it.version } + .firstOrNull { it.name == "woocommerce-dev/woocommerce" }?.version } else { "" } @@ -101,4 +100,8 @@ class PaymentReceiptHelper @Inject constructor( const val RECEIPT_EXPIRATION_DAYS = 365 } + + class IsDevSiteSupported @Inject constructor() { + operator fun invoke() = true + } } From 0afba1c58466de39a453487e0995b56d2d2ec926 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 09:35:04 +0100 Subject: [PATCH 083/160] Fixed detekt complaints --- .../android/ui/payments/receipt/PaymentReceiptHelper.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 0895f192066..d498764eb7b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -85,7 +85,7 @@ class PaymentReceiptHelper @Inject constructor( return if (sitePlugin == null) { if (isDevSiteSupported()) { wooCommerceStore.getSitePlugins(selectedSite.get()) - .firstOrNull { it.name == "woocommerce-dev/woocommerce" }?.version + .firstOrNull { it.name == "woocommerce-dev/woocommerce" }?.version } else { "" } @@ -102,6 +102,7 @@ class PaymentReceiptHelper @Inject constructor( } class IsDevSiteSupported @Inject constructor() { + @Suppress("FunctionOnlyReturningConstant") operator fun invoke() = true } } From 1d7025a244b9030407b803d0614561d884b28bb7 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 09:50:19 +0100 Subject: [PATCH 084/160] Made tests compilable. Fixed tests in OrderDetailViewModelTest --- .../android/ui/orders/OrderDetailViewModelTest.kt | 10 +++++----- .../cardreader/CardReaderPaymentViewModelTest.kt | 4 ++-- .../payment/CardReaderPaymentOrderHelperTest.kt | 2 +- .../ui/payments/receipt/PaymentReceiptHelperTest.kt | 13 +++++++++---- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index e7c1e68b49a..50ccfc4b76c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -35,6 +35,7 @@ import com.woocommerce.android.ui.orders.details.OrderProduct import com.woocommerce.android.ui.orders.details.OrderProductMapper import com.woocommerce.android.ui.orders.details.ShippingLabelOnboardingRepository import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.ui.products.addons.AddonRepository @@ -115,7 +116,10 @@ class OrderDetailViewModelTest : BaseUnitTest() { private val orderDetailsTransactionLauncher = mock() private val orderProductMapper = OrderProductMapper() private val productDetailRepository: ProductDetailRepository = mock() - private val paymentReceiptHelper: PaymentReceiptHelper = mock() + private val paymentReceiptHelper: PaymentReceiptHelper = mock { + onBlocking { isReceiptAvailable(any()) }.thenReturn( false) + onBlocking { getReceiptUrl(any()) }.thenReturn(Result.success("https://www.testname.com")) + } private val order = OrderTestUtils.generateTestOrder(ORDER_ID) private val orderInfo = OrderInfo(OrderTestUtils.generateTestOrder(ORDER_ID)) @@ -1129,8 +1133,6 @@ class OrderDetailViewModelTest : BaseUnitTest() { doReturn(order).whenever(orderDetailRepository).getOrderById(any()) doReturn(order).whenever(orderDetailRepository).fetchOrderById(any()) doReturn(false).whenever(orderDetailRepository).fetchOrderNotes(any()) - doReturn("testing url") - .whenever(appPrefsWrapper).getReceiptUrl(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) doReturn(false).whenever(addonsRepository).containsAddonsFrom(any()) viewModel.start() @@ -1145,8 +1147,6 @@ class OrderDetailViewModelTest : BaseUnitTest() { doReturn(order).whenever(orderDetailRepository).getOrderById(any()) doReturn(order).whenever(orderDetailRepository).fetchOrderById(any()) doReturn(false).whenever(orderDetailRepository).fetchOrderNotes(any()) - doReturn("testing url") - .whenever(appPrefsWrapper).getReceiptUrl(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) doReturn(false).whenever(addonsRepository).containsAddonsFrom(any()) viewModel.start() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 7b504fa8b7e..7081fa95ec6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -241,11 +241,11 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { flow {} } whenever(paymentReceiptHelper.isPluginCanSendReceipt(siteModel)).thenReturn(true) - whenever(paymentReceiptHelper.getReceiptUrl(ORDER_ID)).thenReturn("test url") + whenever(paymentReceiptHelper.getReceiptUrl(ORDER_ID)).thenReturn(Result.success("test url")) whenever(cardReaderPaymentOrderHelper.getPaymentDescription(mockedOrder)).thenReturn("test description") whenever(cardReaderPaymentOrderHelper.getAmountLabel(mockedOrder)) .thenReturn("$DUMMY_CURRENCY_SYMBOL$DUMMY_TOTAL") - whenever(cardReaderPaymentOrderHelper.getReceiptDocumentName(mockedOrder)).thenReturn("receipt-order-1") + whenever(cardReaderPaymentOrderHelper.getReceiptDocumentName(mockedOrder.id)).thenReturn("receipt-order-1") whenever(cardReaderManager.batteryStatus).thenAnswer { flow { emit(CardReaderBatteryStatus.Unknown) } } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelperTest.kt index 2b098e7e8e2..db69163c8c3 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelperTest.kt @@ -82,7 +82,7 @@ class CardReaderPaymentOrderHelperTest { } // WHEN - val result = helper.getReceiptDocumentName(order) + val result = helper.getReceiptDocumentName(order.id) // THEN assertThat(result).isEqualTo("receipt-order-1") diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt index fcad01fe160..60512902bb6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType +import com.woocommerce.android.viewmodel.BaseUnitTest import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -10,17 +11,21 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.store.WCOrderStore import org.wordpress.android.fluxc.store.WooCommerceStore @ExperimentalCoroutinesApi -class PaymentReceiptHelperTest { +class PaymentReceiptHelperTest : BaseUnitTest() { private val selectedSite: SelectedSite = mock { on { get() }.thenReturn(mock()) } private val appPrefsWrapper: AppPrefsWrapper = mock() private val wooCommerceStore: WooCommerceStore = mock() + private val orderStore: WCOrderStore = mock() + private val isDevSiteSupported: PaymentReceiptHelper.IsDevSiteSupported = mock() - private val helper = PaymentReceiptHelper(selectedSite, wooCommerceStore, appPrefsWrapper) + private val helper = + PaymentReceiptHelper(selectedSite, wooCommerceStore, appPrefsWrapper, orderStore, isDevSiteSupported) @Test fun `given selected site, when storeReceiptUrl, then url is stored`() { @@ -35,7 +40,7 @@ class PaymentReceiptHelperTest { } @Test - fun `given selected site and no saved url, when getReceiptUrl, then null returned`() { + fun `given selected site and no saved url, when getReceiptUrl, then null returned`() = testBlocking { // GIVEN val site = selectedSite.get() whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn(null) @@ -48,7 +53,7 @@ class PaymentReceiptHelperTest { } @Test - fun `given selected site and saved url, when getReceiptUrl, then url returned`() { + fun `given selected site and saved url, when getReceiptUrl, then url returned`() =testBlocking { // GIVEN val site = selectedSite.get() whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn("url") From 293181805368aaa65823e195041b9c81f65c679c Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 10:16:12 +0100 Subject: [PATCH 085/160] Added tests if the changes in OrderDetailViewModel --- .../ui/orders/OrderDetailViewModelTest.kt | 88 +++++++++++++++++++ .../receipt/PaymentReceiptHelperTest.kt | 2 +- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index 50ccfc4b76c..38d4fb4171a 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -325,6 +325,54 @@ class OrderDetailViewModelTest : BaseUnitTest() { assertThat(shippingLabels).isEmpty() } + @Test + fun `given receipt is available, when view model started, then state with receipt isReceiptButtonsVisible true emitted`() = + testBlocking { + // GIVEN + whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(true) + whenever(paymentCollectibilityChecker.isCollectable(any())).thenReturn(false) + whenever(orderDetailRepository.getOrderById(any())).thenReturn(order) + whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(true) + whenever(orderDetailRepository.getOrderNotes(any())).thenReturn(testOrderNotes) + whenever(orderDetailRepository.getOrderShipmentTrackings(any())).thenReturn(testOrderShipmentTrackings) + whenever(orderDetailRepository.getOrderRefunds(any())).thenReturn(emptyList()) + whenever(orderDetailRepository.getOrderShippingLabels(any())).thenReturn(emptyList()) + whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false) + + // WHEN + var detailViewState: OrderDetailViewState? = null + viewModel.viewStateData.observeForever { _, new -> detailViewState = new } + + viewModel.start() + + // THEN + assertThat(detailViewState!!.orderInfo!!.isReceiptButtonsVisible).isTrue() + } + + @Test + fun `given receipt is not available, when view model started, then state with receipt isReceiptButtonsVisible false emitted`() = + testBlocking { + // GIVEN + whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(false) + whenever(paymentCollectibilityChecker.isCollectable(any())).thenReturn(false) + whenever(orderDetailRepository.getOrderById(any())).thenReturn(order) + whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(true) + whenever(orderDetailRepository.getOrderNotes(any())).thenReturn(testOrderNotes) + whenever(orderDetailRepository.getOrderShipmentTrackings(any())).thenReturn(testOrderShipmentTrackings) + whenever(orderDetailRepository.getOrderRefunds(any())).thenReturn(emptyList()) + whenever(orderDetailRepository.getOrderShippingLabels(any())).thenReturn(emptyList()) + whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false) + + // WHEN + var detailViewState: OrderDetailViewState? = null + viewModel.viewStateData.observeForever { _, new -> detailViewState = new } + + viewModel.start() + + // THEN + assertThat(detailViewState!!.orderInfo!!.isReceiptButtonsVisible).isFalse() + } + @Test fun `collect button hidden if payment is not collectable`() = testBlocking { @@ -1155,6 +1203,46 @@ class OrderDetailViewModelTest : BaseUnitTest() { verify(orderDetailTracker).trackReceiptViewTapped(order.id, order.status) } + @Test + fun `given receipt request returns error, when user taps on see receipt, then snackbar event emitted`() = + testBlocking { + // GIVEN + whenever(orderDetailRepository.getOrderById(any())).thenReturn(order) + whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(false) + whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false) + + whenever(paymentReceiptHelper.getReceiptUrl(order.id)).thenReturn(Result.failure(Exception(""))) + + // WHEN + viewModel.start() + + viewModel.onSeeReceiptClicked() + + // THEN + assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo(string.receipt_fetching_error) + } + + @Test + fun `given receipt request returns success, when user taps on see receipt, then PreviewReceipt event emitted`() = + testBlocking { + // GIVEN + whenever(orderDetailRepository.getOrderById(any())).thenReturn(order) + whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(false) + whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false) + val receiptUrl = "https://example.com" + whenever(paymentReceiptHelper.getReceiptUrl(order.id)).thenReturn(Result.success(receiptUrl)) + + // WHEN + viewModel.start() + + viewModel.onSeeReceiptClicked() + + // THEN + assertThat((viewModel.event.value as PreviewReceipt).orderId).isEqualTo(order.id) + assertThat((viewModel.event.value as PreviewReceipt).receiptUrl).isEqualTo(receiptUrl) + assertThat((viewModel.event.value as PreviewReceipt).billingEmail).isEqualTo(order.billingAddress.email) + } + @Test fun `given order is paid, when status is processing order complete button should be visible`() = testBlocking { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt index 60512902bb6..58c6c6e3b69 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt @@ -53,7 +53,7 @@ class PaymentReceiptHelperTest : BaseUnitTest() { } @Test - fun `given selected site and saved url, when getReceiptUrl, then url returned`() =testBlocking { + fun `given selected site and saved url, when getReceiptUrl, then url returned`() = testBlocking { // GIVEN val site = selectedSite.get() whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn("url") From 22bb34a585500cae45dc9e3cee1a8df1c9080992 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 10:30:50 +0100 Subject: [PATCH 086/160] Added tests on the changes in CardReaderPaymentViewModel --- .../CardReaderPaymentViewModelTest.kt | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 7081fa95ec6..74bab73e34a 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -2296,6 +2296,44 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { verify(tracker).trackPrintReceiptTapped() } + @Test + fun `given get receipt url fails, when user clicks on print receipt button, then ShowSnackbar emitted`() = + testBlocking { + // GIVEN + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("")) } + } + whenever(paymentReceiptHelper.getReceiptUrl(any())).thenReturn(Result.failure(Exception())) + + // WHEN + viewModel.start() + + (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onPrimaryActionClicked.invoke() + + // THEN + assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo(R.string.receipt_fetching_error) + } + + @Test + fun `given get receipt url succeeds, when user clicks on print receipt button, then PrintReceipt emitted`() = + testBlocking { + // GIVEN + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("")) } + } + val receiptUrl = "testUrl" + whenever(paymentReceiptHelper.getReceiptUrl(any())).thenReturn(Result.success(receiptUrl)) + + // WHEN + viewModel.start() + + (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onPrimaryActionClicked.invoke() + + // THEN + assertThat((viewModel.event.value as PrintReceipt).receiptUrl).isEqualTo(receiptUrl) + assertThat((viewModel.event.value as PrintReceipt).documentName).isEqualTo("receipt-order-1") + } + @Test fun `when OS accepts the print request, then print success event tracked`() { viewModel.onPrintResult(STARTED) @@ -2318,7 +2356,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { } @Test - fun `given external reader, when user clicks on send receipt button, then SendReceipt event emitted`() = + fun `given external reader and receipt fetching success, when user clicks on send receipt button, then SendReceipt event emitted`() = testBlocking { whenever(cardReaderManager.collectPayment(any())).thenAnswer { flow { emit(PaymentCompleted("")) } @@ -2331,7 +2369,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { } @Test - fun `given built in reader, when user clicks on send receipt button, then SendReceipt event emitted`() = + fun `given built in reader and receipt fetching success, when user clicks on send receipt button, then SendReceipt event emitted`() = testBlocking { whenever(cardReaderManager.collectPayment(any())).thenAnswer { flow { emit(PaymentCompleted("")) } @@ -2346,6 +2384,38 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { assertThat(viewModel.event.value).isInstanceOf(SendReceipt::class.java) } + @Test + fun `given external reader and receipt fetching fails, when user clicks on send receipt button, then ShowSnackabar event emitted`() = + testBlocking { + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("")) } + } + whenever(paymentReceiptHelper.getReceiptUrl(any())).thenReturn(Result.failure(Exception())) + + viewModel.start() + + (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() + + assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo(R.string.receipt_fetching_error) + } + + @Test + fun `given built reader and receipt fetching fails, when user clicks on send receipt button, then ShowSnackabar event emitted`() = + testBlocking { + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("")) } + } + whenever(paymentReceiptHelper.getReceiptUrl(any())).thenReturn(Result.failure(Exception())) + + initViewModel(BUILT_IN) + + viewModel.start() + + (viewModel.viewStateData.value as BuiltInReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() + + assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo(R.string.receipt_fetching_error) + } + @Test fun `given external reader, when user clicks on send receipt button, then event tracked`() = testBlocking { From 6b97296e3d01d5849b4eb4759667ad1ac1dddce9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:05:44 +0100 Subject: [PATCH 087/160] Added tests on the changes in PaymentReceiptHelper --- .../payments/receipt/PaymentReceiptHelper.kt | 7 +- .../ui/orders/OrderDetailViewModelTest.kt | 2 +- .../receipt/PaymentReceiptHelperTest.kt | 252 +++++++++++++++++- 3 files changed, 254 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index d498764eb7b..26e5bbffbd1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -37,7 +37,12 @@ class PaymentReceiptHelper @Inject constructor( Result.success(result.receiptUrl) } } else { - Result.success(getReceiptUrlFromAppPrefs(orderId)) + val urlFromAppPrefs = getReceiptUrlFromAppPrefs(orderId) + if (urlFromAppPrefs.isEmpty()) { + Result.failure(Exception("Receipt url not found")) + } else { + Result.success(urlFromAppPrefs) + } } suspend fun isReceiptAvailable(orderId: Long) = diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index 38d4fb4171a..cdfa6b734da 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -117,7 +117,7 @@ class OrderDetailViewModelTest : BaseUnitTest() { private val orderProductMapper = OrderProductMapper() private val productDetailRepository: ProductDetailRepository = mock() private val paymentReceiptHelper: PaymentReceiptHelper = mock { - onBlocking { isReceiptAvailable(any()) }.thenReturn( false) + onBlocking { isReceiptAvailable(any()) }.thenReturn(false) onBlocking { getReceiptUrl(any()) }.thenReturn(Result.success("https://www.testname.com")) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt index 58c6c6e3b69..e3437846429 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt @@ -11,6 +11,12 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.plugin.SitePluginModel +import org.wordpress.android.fluxc.network.BaseRequest +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload +import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.OrderReceiptResponse import org.wordpress.android.fluxc.store.WCOrderStore import org.wordpress.android.fluxc.store.WooCommerceStore @@ -40,29 +46,182 @@ class PaymentReceiptHelperTest : BaseUnitTest() { } @Test - fun `given selected site and no saved url, when getReceiptUrl, then null returned`() = testBlocking { + fun `given version 3_9_9 and no saved url, when getReceiptUrl, then failure returned`() = testBlocking { // GIVEN val site = selectedSite.get() - whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn(null) + whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn("") + val plugin = mock { + on { version }.thenReturn("3.9.9") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) // WHEN val result = helper.getReceiptUrl(1) // THEN - assertThat(result).isNull() + assertThat(result.isFailure).isTrue() } @Test - fun `given selected site and saved url, when getReceiptUrl, then url returned`() = testBlocking { + fun `given version 3_9_9 site and saved url, when getReceiptUrl, then url returned`() = testBlocking { // GIVEN val site = selectedSite.get() whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn("url") + val plugin = mock { + on { version }.thenReturn("3.9.9") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) // WHEN val result = helper.getReceiptUrl(1) // THEN - assertThat(result).isEqualTo("url") + assertThat(result.getOrThrow()).isEqualTo("url") + } + + @Test + fun `given version 6_4_0 site and remote call success, when getReceiptUrl, then url returned`() = testBlocking { + // GIVEN + val site = selectedSite.get() + val plugin = mock { + on { version }.thenReturn("6.4.0") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + WooPayload(OrderReceiptResponse("url", "date")) + ) + + // WHEN + val result = helper.getReceiptUrl(1) + + // THEN + assertThat(result.getOrThrow()).isEqualTo("url") + } + + @Test + fun `given version 6_4_0 site and remote call fails, when getReceiptUrl, then failure returned`() = testBlocking { + // GIVEN + val site = selectedSite.get() + val plugin = mock { + on { version }.thenReturn("6.4.0") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + WooPayload( + WooError( + type = WooErrorType.API_ERROR, + original = BaseRequest.GenericErrorType.NETWORK_ERROR, + message = "error" + ) + ) + ) + + // WHEN + val result = helper.getReceiptUrl(1) + + // THEN + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()!!.message).isEqualTo("error") + } + + @Test + fun `given version dev usage enabled and 6_4_0 and remote call fails, when getReceiptUrl, then failure returned`() = testBlocking { + // GIVEN + val site = selectedSite.get() + val plugin = mock { + on { version }.thenReturn("6.4.0") + on { name }.thenReturn("woocommerce-dev/woocommerce") + } + whenever( + wooCommerceStore.getSitePlugins( + selectedSite.get(), + ) + ).thenReturn(listOf(plugin)) + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + WooPayload( + WooError( + type = WooErrorType.API_ERROR, + original = BaseRequest.GenericErrorType.NETWORK_ERROR, + message = "error" + ) + ) + ) + whenever(isDevSiteSupported()).thenReturn(true) + + // WHEN + val result = helper.getReceiptUrl(1) + + // THEN + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()!!.message).isEqualTo("error") + } + + @Test + fun `given version dev usage enabled and 6_4_0 and remote call success, when getReceiptUrl, then failure returned`() = testBlocking { + // GIVEN + val site = selectedSite.get() + val plugin = mock { + on { version }.thenReturn("6.4.0") + on { name }.thenReturn("woocommerce-dev/woocommerce") + } + whenever( + wooCommerceStore.getSitePlugins( + selectedSite.get(), + ) + ).thenReturn(listOf(plugin)) + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + WooPayload(OrderReceiptResponse("url", "date")) + ) + whenever(isDevSiteSupported()).thenReturn(true) + + // WHEN + val result = helper.getReceiptUrl(1) + + // THEN + assertThat(result.getOrThrow()).isEqualTo("url") + } + + @Test + fun `given version dev usage enabled and 6_3_0 and locally stored receipt, when getReceiptUrl, then url returned`() = testBlocking { + // GIVEN + val site = selectedSite.get() + val plugin = mock { + on { version }.thenReturn("6.3.0") + on { name }.thenReturn("woocommerce-dev/woocommerce") + } + whenever( + wooCommerceStore.getSitePlugins( + selectedSite.get(), + ) + ).thenReturn(listOf(plugin)) + whenever(isDevSiteSupported()).thenReturn(true) + whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn("url") + + // WHEN + val result = helper.getReceiptUrl(1) + + // THEN + assertThat(result.getOrThrow()).isEqualTo("url") } @Test @@ -123,4 +282,87 @@ class PaymentReceiptHelperTest : BaseUnitTest() { // THEN assertThat(result).isTrue() } + + @Test + fun `given version 6_4_0, when isReceiptAvailable, then true returned`() = testBlocking { + // GIVEN + val plugin = mock { + on { version }.thenReturn("6.4.0") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) + + // WHEN + val result = helper.isReceiptAvailable(1) + + // THEN + assertThat(result).isTrue() + } + + @Test + fun `given version empty 6_3_9 and empty local storage, when isReceiptAvailable, then false returned`() = testBlocking { + // GIVEN + val plugin = mock { + on { version }.thenReturn("") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) + whenever(appPrefsWrapper.getReceiptUrl(any(), any(), any(), any())).thenReturn("") + + // WHEN + val result = helper.isReceiptAvailable(1) + + // THEN + assertThat(result).isFalse() + } + + @Test + fun `given version empty string and empty local storage, when isReceiptAvailable, then false returned`() = testBlocking { + // GIVEN + val plugin = mock { + on { version }.thenReturn("") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) + whenever(appPrefsWrapper.getReceiptUrl(any(), any(), any(), any())).thenReturn("") + + // WHEN + val result = helper.isReceiptAvailable(1) + + // THEN + assertThat(result).isFalse() + } + + @Test + fun `given version empty 6_3_9 and non empty local storage, when isReceiptAvailable, then true returned`() = testBlocking { + // GIVEN + val plugin = mock { + on { version }.thenReturn("") + } + whenever( + wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + ).thenReturn(plugin) + whenever(appPrefsWrapper.getReceiptUrl(any(), any(), any(), any())).thenReturn("url") + + // WHEN + val result = helper.isReceiptAvailable(1) + + // THEN + assertThat(result).isTrue() + } } From 46385486876649df2cb227e45ebaf392a39f2bb0 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:12:46 +0100 Subject: [PATCH 088/160] Added a check if the order paid to show/hide receipt button --- .../android/ui/orders/details/OrderDetailViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index b999a289ec8..b018d182f78 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -581,7 +581,7 @@ class OrderDetailViewModel @Inject constructor( orderInfo = OrderDetailViewState.OrderInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectable, - isReceiptButtonsVisible = paymentReceiptHelper.isReceiptAvailable(order.id), + isReceiptButtonsVisible = paymentReceiptHelper.isReceiptAvailable(order.id) && order.isOrderPaid, ), orderStatus = orderStatus, toolbarTitle = resourceProvider.getString( From 1cc933a5ae39458ec654a7ae25acf4414bf0312e Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:16:54 +0100 Subject: [PATCH 089/160] Test on the order paid or not --- .../ui/orders/OrderDetailViewModelTest.kt | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index cdfa6b734da..b2601f7f30d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -326,12 +326,14 @@ class OrderDetailViewModelTest : BaseUnitTest() { } @Test - fun `given receipt is available, when view model started, then state with receipt isReceiptButtonsVisible true emitted`() = + fun `given receipt is available and order is paid, when view model started, then state with receipt isReceiptButtonsVisible true emitted`() = testBlocking { // GIVEN whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(true) whenever(paymentCollectibilityChecker.isCollectable(any())).thenReturn(false) - whenever(orderDetailRepository.getOrderById(any())).thenReturn(order) + whenever(orderDetailRepository.getOrderById(any())).thenReturn(order.copy( + datePaid = Date() + )) whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(true) whenever(orderDetailRepository.getOrderNotes(any())).thenReturn(testOrderNotes) whenever(orderDetailRepository.getOrderShipmentTrackings(any())).thenReturn(testOrderShipmentTrackings) @@ -349,6 +351,32 @@ class OrderDetailViewModelTest : BaseUnitTest() { assertThat(detailViewState!!.orderInfo!!.isReceiptButtonsVisible).isTrue() } + @Test + fun `given receipt is available and order not paid, when view model started, then state with receipt isReceiptButtonsVisible false emitted`() = + testBlocking { + // GIVEN + whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(true) + whenever(paymentCollectibilityChecker.isCollectable(any())).thenReturn(true) + whenever(orderDetailRepository.getOrderById(any())).thenReturn(order.copy( + datePaid = null + )) + whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(true) + whenever(orderDetailRepository.getOrderNotes(any())).thenReturn(testOrderNotes) + whenever(orderDetailRepository.getOrderShipmentTrackings(any())).thenReturn(testOrderShipmentTrackings) + whenever(orderDetailRepository.getOrderRefunds(any())).thenReturn(emptyList()) + whenever(orderDetailRepository.getOrderShippingLabels(any())).thenReturn(emptyList()) + whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false) + + // WHEN + var detailViewState: OrderDetailViewState? = null + viewModel.viewStateData.observeForever { _, new -> detailViewState = new } + + viewModel.start() + + // THEN + assertThat(detailViewState!!.orderInfo!!.isReceiptButtonsVisible).isFalse() + } + @Test fun `given receipt is not available, when view model started, then state with receipt isReceiptButtonsVisible false emitted`() = testBlocking { From 6cd472f20363b7e3f7d3728343aa164b79eb6aa0 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:18:29 +0100 Subject: [PATCH 090/160] Fixed formatting --- .../ui/orders/OrderDetailViewModelTest.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index b2601f7f30d..bdbafe61f95 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -331,9 +331,11 @@ class OrderDetailViewModelTest : BaseUnitTest() { // GIVEN whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(true) whenever(paymentCollectibilityChecker.isCollectable(any())).thenReturn(false) - whenever(orderDetailRepository.getOrderById(any())).thenReturn(order.copy( - datePaid = Date() - )) + whenever(orderDetailRepository.getOrderById(any())).thenReturn( + order.copy( + datePaid = Date() + ) + ) whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(true) whenever(orderDetailRepository.getOrderNotes(any())).thenReturn(testOrderNotes) whenever(orderDetailRepository.getOrderShipmentTrackings(any())).thenReturn(testOrderShipmentTrackings) @@ -357,9 +359,11 @@ class OrderDetailViewModelTest : BaseUnitTest() { // GIVEN whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(true) whenever(paymentCollectibilityChecker.isCollectable(any())).thenReturn(true) - whenever(orderDetailRepository.getOrderById(any())).thenReturn(order.copy( - datePaid = null - )) + whenever(orderDetailRepository.getOrderById(any())).thenReturn( + order.copy( + datePaid = null + ) + ) whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(true) whenever(orderDetailRepository.getOrderNotes(any())).thenReturn(testOrderNotes) whenever(orderDetailRepository.getOrderShipmentTrackings(any())).thenReturn(testOrderShipmentTrackings) From dceb4a82945c30fe267288057156eb4bc9f7ce1a Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:44:14 +0100 Subject: [PATCH 091/160] Changed version of expected --- .../ui/payments/receipt/PaymentReceiptHelper.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 26e5bbffbd1..907b1124032 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -89,8 +89,14 @@ class PaymentReceiptHelper @Inject constructor( ) return if (sitePlugin == null) { if (isDevSiteSupported()) { - wooCommerceStore.getSitePlugins(selectedSite.get()) - .firstOrNull { it.name == "woocommerce-dev/woocommerce" }?.version + val devPlugin = wooCommerceStore.getSitePlugins(selectedSite.get()) + .firstOrNull { it.name == "woocommerce-dev/woocommerce" } + if (devPlugin != null) { + // return this version so we use the backend to generate receipts for testing + WC_CAN_GENERATE_RECEIPTS_VERSION + } else { + "" + } } else { "" } @@ -101,7 +107,7 @@ class PaymentReceiptHelper @Inject constructor( private companion object { const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" - const val WC_CAN_GENERATE_RECEIPTS_VERSION = "6.4.0" + const val WC_CAN_GENERATE_RECEIPTS_VERSION = "8.7.0" const val RECEIPT_EXPIRATION_DAYS = 365 } From 100ec3f16c463bb907adee6d2123ab3e6f5ad0bd Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:45:16 +0100 Subject: [PATCH 092/160] Support dev plugin only in debug mode --- .../android/ui/payments/receipt/PaymentReceiptHelper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 907b1124032..53dea128b02 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.AppPrefsWrapper +import com.woocommerce.android.BuildConfig import com.woocommerce.android.extensions.semverCompareTo import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType @@ -113,7 +114,6 @@ class PaymentReceiptHelper @Inject constructor( } class IsDevSiteSupported @Inject constructor() { - @Suppress("FunctionOnlyReturningConstant") - operator fun invoke() = true + operator fun invoke() = BuildConfig.DEBUG } } From f3440e12b88298efe6d99a466507a9c96925b236 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:48:54 +0100 Subject: [PATCH 093/160] Fixed tests --- .../receipt/PaymentReceiptHelperTest.kt | 41 ++++--------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt index e3437846429..8aa5087bcc1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt @@ -90,11 +90,11 @@ class PaymentReceiptHelperTest : BaseUnitTest() { } @Test - fun `given version 6_4_0 site and remote call success, when getReceiptUrl, then url returned`() = testBlocking { + fun `given version 8_7_0 site and remote call success, when getReceiptUrl, then url returned`() = testBlocking { // GIVEN val site = selectedSite.get() val plugin = mock { - on { version }.thenReturn("6.4.0") + on { version }.thenReturn("8.7.0") } whenever( wooCommerceStore.getSitePlugin( @@ -114,11 +114,11 @@ class PaymentReceiptHelperTest : BaseUnitTest() { } @Test - fun `given version 6_4_0 site and remote call fails, when getReceiptUrl, then failure returned`() = testBlocking { + fun `given version 8_7_0 site and remote call fails, when getReceiptUrl, then failure returned`() = testBlocking { // GIVEN val site = selectedSite.get() val plugin = mock { - on { version }.thenReturn("6.4.0") + on { version }.thenReturn("8.7.0") } whenever( wooCommerceStore.getSitePlugin( @@ -149,7 +149,6 @@ class PaymentReceiptHelperTest : BaseUnitTest() { // GIVEN val site = selectedSite.get() val plugin = mock { - on { version }.thenReturn("6.4.0") on { name }.thenReturn("woocommerce-dev/woocommerce") } whenever( @@ -177,11 +176,10 @@ class PaymentReceiptHelperTest : BaseUnitTest() { } @Test - fun `given version dev usage enabled and 6_4_0 and remote call success, when getReceiptUrl, then failure returned`() = testBlocking { + fun `given version dev usage enabled and remote call success, when getReceiptUrl, then failure returned`() = testBlocking { // GIVEN val site = selectedSite.get() val plugin = mock { - on { version }.thenReturn("6.4.0") on { name }.thenReturn("woocommerce-dev/woocommerce") } whenever( @@ -201,29 +199,6 @@ class PaymentReceiptHelperTest : BaseUnitTest() { assertThat(result.getOrThrow()).isEqualTo("url") } - @Test - fun `given version dev usage enabled and 6_3_0 and locally stored receipt, when getReceiptUrl, then url returned`() = testBlocking { - // GIVEN - val site = selectedSite.get() - val plugin = mock { - on { version }.thenReturn("6.3.0") - on { name }.thenReturn("woocommerce-dev/woocommerce") - } - whenever( - wooCommerceStore.getSitePlugins( - selectedSite.get(), - ) - ).thenReturn(listOf(plugin)) - whenever(isDevSiteSupported()).thenReturn(true) - whenever(appPrefsWrapper.getReceiptUrl(site.id, site.siteId, site.selfHostedSiteId, 1)).thenReturn("url") - - // WHEN - val result = helper.getReceiptUrl(1) - - // THEN - assertThat(result.getOrThrow()).isEqualTo("url") - } - @Test fun `given null saved plugin version, when isPluginCanSendReceipt, then result is false`() { // GIVEN @@ -284,10 +259,10 @@ class PaymentReceiptHelperTest : BaseUnitTest() { } @Test - fun `given version 6_4_0, when isReceiptAvailable, then true returned`() = testBlocking { + fun `given version 8_7_0, when isReceiptAvailable, then true returned`() = testBlocking { // GIVEN val plugin = mock { - on { version }.thenReturn("6.4.0") + on { version }.thenReturn("8.7.0") } whenever( wooCommerceStore.getSitePlugin( @@ -304,7 +279,7 @@ class PaymentReceiptHelperTest : BaseUnitTest() { } @Test - fun `given version empty 6_3_9 and empty local storage, when isReceiptAvailable, then false returned`() = testBlocking { + fun `given version empty and empty local storage, when isReceiptAvailable, then false returned`() = testBlocking { // GIVEN val plugin = mock { on { version }.thenReturn("") From 363afca0847393778d0cdea2171a8d99441fd171 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 11:55:34 +0100 Subject: [PATCH 094/160] Moved release note to the next release --- RELEASE-NOTES.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index ccc6a64b548..0480dd755e9 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,16 +1,16 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] +17.4 +---- +- [*] [Internal] Collapse bottom drawer with totals on the order creation/editing screen when scrolling down [https://github.com/woocommerce/woocommerce-android/pull/10573] + 17.3 ----- -- [*] [Internal] Collapse bottom drawer with totals on the order creation/editing screen when scrolling down [https://github.com/woocommerce/woocommerce-android/pull/10573] - [*] [Internal] Better displaying of the "locked" state of the buttons on the order creation screen. Fixed bug when it was possible to add "custom amount" even when the order is locked [https://github.com/woocommerce/woocommerce-android/pull/10562] 17.2 ----- - [*] [Internal] Tracking of "country" and "currency" properties for entry and exit of the payments flows [https://github.com/woocommerce/woocommerce-android/pull/10528] - [*] Fixed bug when gift card was not displayed in the "totals" sections on the order creation/editing screens [https://github.com/woocommerce/woocommerce-android/pull/10546] -17.2 ------ - 17.1 ----- From d5dcb2a36aac7664c322c81f5e20d0a769eb2fd6 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:03:36 +0100 Subject: [PATCH 095/160] Add device target selection --- .../woocommerce/android/ui/blaze/BlazeRepository.kt | 5 +++++ .../preview/BlazeCampaignCreationPreviewViewModel.kt | 6 +++++- .../targets/BlazeCampaignTargetSelectionViewModel.kt | 12 +++++++++--- 3 files changed, 19 insertions(+), 4 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 feef8c1de85..79dde8216a2 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 @@ -32,6 +32,11 @@ class BlazeRepository @Inject constructor( suspend fun fetchLanguages() = blazeCampaignsStore.fetchBlazeTargetingLanguages() + fun observeDevices() = blazeCampaignsStore.observeBlazeTargetingDevices() + .map { it.map { device -> Device(device.id, device.name) } } + + suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingDevices() + suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get()) suspend fun getAdSuggestions(productId: Long): List? { 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 fb23ea12acb..82fa8e27062 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 @@ -100,6 +100,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( launch { when (targetType) { LANGUAGE -> selectedLanguages.update { selectedIds } + DEVICE -> selectedDevices.update { selectedIds } else -> Unit } } @@ -108,6 +109,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private fun loadData() { launch { blazeRepository.fetchLanguages() + blazeRepository.fetchDevices() blazeRepository.getAdSuggestions(navArgs.productId).let { suggestions -> adDetails.update { AdDetails( @@ -146,7 +148,9 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_devices), displayValue = devices.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add device selection */ }, + onItemSelected = { + triggerEvent(NavigateToTargetSelectionScreen(DEVICE, devices.map { it.id })) + }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index 139ae0e1a0a..fc57dfd8778 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -15,7 +15,6 @@ import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.parcelize.Parcelize @@ -38,7 +37,14 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( ) } } - else -> flowOf(emptyList()) + else -> blazeRepository.observeDevices().map { devices -> + devices.map { device -> + TargetItem( + id = device.id, + value = device.name + ) + } + } } private val selectedIds = savedStateHandle.getStateFlow(viewModelScope, navArgs.selectedIds.toSet()) @@ -49,7 +55,7 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( selectedItems = selectedIds.map { id -> items.first { it.id == id } }, title = when (navArgs.targetType) { BlazeTargetType.LANGUAGE -> resourceProvider.getString(R.string.blaze_campaign_preview_details_language) - else -> "" + else -> resourceProvider.getString(R.string.blaze_campaign_preview_details_devices) } ) }.asLiveData() From 348d6543856f213cfc91370bc94ac6eeb37d135d Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:04:47 +0100 Subject: [PATCH 096/160] Combine the state of selected languages and devices --- .../BlazeCampaignCreationPreviewViewModel.kt | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 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 82fa8e27062..f3664005a75 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 @@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.R.string +import com.woocommerce.android.extensions.combine import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.Budget @@ -16,6 +17,7 @@ import com.woocommerce.android.ui.blaze.BlazeRepository.Location import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType +import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.DEVICE import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.LANGUAGE import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -24,7 +26,6 @@ import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.getStateFlow import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -43,21 +44,34 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val adDetails = savedStateHandle.getStateFlow(viewModelScope, Loading) private val budget = savedStateHandle.getStateFlow(viewModelScope, getDefaultBudget()) + private val languages = blazeRepository.observeLanguages() - private val selectedLanguages = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val devices = blazeRepository.observeDevices() + private val selectedLanguages = savedStateHandle.getStateFlow>( + scope = viewModelScope, + initialValue = emptyList(), + key = "selectedLanguages" + ) + private val selectedDevices = savedStateHandle.getStateFlow>( + scope = viewModelScope, + initialValue = emptyList(), + key = "selectedDevices" + ) val viewState = combine( adDetails, budget, languages, - selectedLanguages - ) { adDetails, budget, languages, selectedLanguages -> + devices, + selectedLanguages, + selectedDevices + ) { adDetails, budget, languages, devices, selectedLanguages, selectedDevices -> CampaignPreviewUiState( adDetails = adDetails, campaignDetails = campaign.toCampaignDetailsUi( budget, languages.filter { it.code in selectedLanguages }, - emptyList(), + devices.filter { it.id in selectedDevices }, emptyList(), emptyList() ) From d818121e0c7042d96f809c2a49361a5e2965fb68 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:34:12 +0100 Subject: [PATCH 097/160] Update the flow combine extensions --- .../android/extensions/FlowExtensions.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt index da62b980c36..46b01b5924d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt @@ -3,16 +3,17 @@ package com.woocommerce.android.extensions import kotlinx.coroutines.flow.Flow @Suppress("LongParameterList") -inline fun combine( +inline fun combine( flow: Flow, flow2: Flow, flow3: Flow, flow4: Flow, flow5: Flow, flow6: Flow, - crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R + flow7: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R ): Flow { - return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> -> + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> -> @Suppress("UNCHECKED_CAST", "MagicNumber") transform( args[0] as T1, @@ -21,12 +22,13 @@ inline fun combine( args[3] as T4, args[4] as T5, args[5] as T6, + args[6] as T7, ) } } @Suppress("LongParameterList") -inline fun combine( +inline fun combine( flow: Flow, flow2: Flow, flow3: Flow, @@ -34,9 +36,10 @@ inline fun combine( flow5: Flow, flow6: Flow, flow7: Flow, - crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R + flow8: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R ): Flow { - return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> -> + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> -> @Suppress("UNCHECKED_CAST", "MagicNumber") transform( args[0] as T1, @@ -46,6 +49,7 @@ inline fun combine( args[4] as T5, args[5] as T6, args[6] as T7, + args[7] as T8, ) } } From c63d63a60d6ba9a8e2c69b3ea6ffcbc807696ab0 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:34:58 +0100 Subject: [PATCH 098/160] Fetch Blaze interests in the preview screen --- .../com/woocommerce/android/ui/blaze/BlazeRepository.kt | 7 ++++++- .../preview/BlazeCampaignCreationPreviewViewModel.kt | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) 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 79dde8216a2..b6e75e1087d 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 @@ -35,7 +35,12 @@ class BlazeRepository @Inject constructor( fun observeDevices() = blazeCampaignsStore.observeBlazeTargetingDevices() .map { it.map { device -> Device(device.id, device.name) } } - suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingDevices() + suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingTopics() + + fun observeInterests() = blazeCampaignsStore.observeBlazeTargetingTopics() + .map { it.map { interest -> Interest(interest.id, interest.description) } } + + suspend fun fetchInterests() = blazeCampaignsStore.fetchBlazeTargetingTopics() suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get()) 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 f3664005a75..cd1f37c0ef8 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 @@ -47,6 +47,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val languages = blazeRepository.observeLanguages() private val devices = blazeRepository.observeDevices() + private val interests = blazeRepository.observeInterests() private val selectedLanguages = savedStateHandle.getStateFlow>( scope = viewModelScope, initialValue = emptyList(), @@ -124,6 +125,8 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( launch { blazeRepository.fetchLanguages() blazeRepository.fetchDevices() + blazeRepository.fetchInterests() + blazeRepository.getAdSuggestions(navArgs.productId).let { suggestions -> adDetails.update { AdDetails( From e96d7c00ad9890b8d3b4e88e4e092f8a949622e1 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:35:29 +0100 Subject: [PATCH 099/160] Handle interests options in the target selection screen --- .../BlazeCampaignTargetSelectionViewModel.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index fc57dfd8778..645db7f0721 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -37,7 +37,7 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( ) } } - else -> blazeRepository.observeDevices().map { devices -> + BlazeTargetType.DEVICE -> blazeRepository.observeDevices().map { devices -> devices.map { device -> TargetItem( id = device.id, @@ -45,6 +45,14 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( ) } } + else -> blazeRepository.observeInterests().map { interests -> + interests.map { interest -> + TargetItem( + id = interest.id, + value = interest.description + ) + } + } } private val selectedIds = savedStateHandle.getStateFlow(viewModelScope, navArgs.selectedIds.toSet()) @@ -55,7 +63,8 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( selectedItems = selectedIds.map { id -> items.first { it.id == id } }, title = when (navArgs.targetType) { BlazeTargetType.LANGUAGE -> resourceProvider.getString(R.string.blaze_campaign_preview_details_language) - else -> resourceProvider.getString(R.string.blaze_campaign_preview_details_devices) + BlazeTargetType.INTEREST -> resourceProvider.getString(R.string.blaze_campaign_preview_details_devices) + else -> resourceProvider.getString(R.string.blaze_campaign_preview_details_interests) } ) }.asLiveData() From 082be98c2c4ea7340740a69047f12836758f83a4 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:35:52 +0100 Subject: [PATCH 100/160] Include the interests options in the preview screen --- .../BlazeCampaignCreationPreviewViewModel.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 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 cd1f37c0ef8..ba679268669 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 @@ -18,6 +18,7 @@ import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPr import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.DEVICE +import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.INTEREST import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.LANGUAGE import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -58,22 +59,29 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( initialValue = emptyList(), key = "selectedDevices" ) + private val selectedInterests = savedStateHandle.getStateFlow>( + scope = viewModelScope, + initialValue = emptyList(), + key = "selectedInterests" + ) val viewState = combine( adDetails, budget, languages, devices, + interests, selectedLanguages, - selectedDevices - ) { adDetails, budget, languages, devices, selectedLanguages, selectedDevices -> + selectedDevices, + selectedInterests + ) { adDetails, budget, languages, devices, interests, selectedLanguages, selectedDevices, selectedInterests -> CampaignPreviewUiState( adDetails = adDetails, campaignDetails = campaign.toCampaignDetailsUi( budget, languages.filter { it.code in selectedLanguages }, devices.filter { it.id in selectedDevices }, - emptyList(), + interests.filter { it.id in selectedInterests }, emptyList() ) ) @@ -116,6 +124,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( when (targetType) { LANGUAGE -> selectedLanguages.update { selectedIds } DEVICE -> selectedDevices.update { selectedIds } + INTEREST -> selectedInterests.update { selectedIds } else -> Unit } } @@ -179,7 +188,9 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_interests), displayValue = interests.joinToString { it.description } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, - onItemSelected = { /* TODO Add interests selection */ }, + onItemSelected = { + triggerEvent(NavigateToTargetSelectionScreen(INTEREST, interests.map { it.id })) + }, ), ), destinationUrl = CampaignDetailItemUi( From f7b67011b422d29ddc4dd12357643bea4582188c Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:39:44 +0100 Subject: [PATCH 101/160] Fix the interests selection title --- .../creation/targets/BlazeCampaignTargetSelectionViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index 645db7f0721..45a510f05e7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -63,7 +63,7 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( selectedItems = selectedIds.map { id -> items.first { it.id == id } }, title = when (navArgs.targetType) { BlazeTargetType.LANGUAGE -> resourceProvider.getString(R.string.blaze_campaign_preview_details_language) - BlazeTargetType.INTEREST -> resourceProvider.getString(R.string.blaze_campaign_preview_details_devices) + BlazeTargetType.DEVICE -> resourceProvider.getString(R.string.blaze_campaign_preview_details_devices) else -> resourceProvider.getString(R.string.blaze_campaign_preview_details_interests) } ) From 87e50b954edc535e71940b93ef058f65ddb7fb48 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 14:55:03 +0100 Subject: [PATCH 102/160] Model to include loading status --- .../ui/orders/details/OrderDetailFragment.kt | 2 +- .../ui/orders/details/OrderDetailViewModel.kt | 25 +++++++++---------- .../ui/orders/details/OrderDetailViewState.kt | 6 ++++- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt index f652d0c4dd5..148976a6bd6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt @@ -254,7 +254,7 @@ class OrderDetailFragment : private fun setupObservers(viewModel: OrderDetailViewModel) { viewModel.viewStateData.observe(viewLifecycleOwner) { old, new -> new.orderInfo?.takeIfNotEqualTo(old?.orderInfo) { - showOrderDetail(it.order!!, it.isPaymentCollectableWithCardReader, it.isReceiptButtonsVisible) + showOrderDetail(it.order!!, it.isPaymentCollectableWithCardReader, it.receiptButtonStatus) requireActivity().invalidateOptionsMenu() } new.orderStatus?.takeIfNotEqualTo(old?.orderStatus) { showOrderStatus(it) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index b018d182f78..9ff82749b1a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -49,7 +49,6 @@ import com.woocommerce.android.ui.orders.OrderStatusUpdateSource import com.woocommerce.android.ui.orders.details.customfields.CustomOrderFieldsHelper import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker -import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.ui.products.addons.AddonRepository @@ -92,8 +91,7 @@ class OrderDetailViewModel @Inject constructor( private val getOrderSubscriptions: GetOrderSubscriptions, private val giftCardRepository: GiftCardRepository, private val orderProductMapper: OrderProductMapper, - private val productDetailRepository: ProductDetailRepository, - private val paymentReceiptHelper: PaymentReceiptHelper, + private val productDetailRepository: ProductDetailRepository ) : ScopedViewModel(savedState), OnProductFetchedListener { private val navArgs: OrderDetailFragmentArgs by savedState.navArgs() @@ -345,15 +343,10 @@ class OrderDetailViewModel @Inject constructor( } fun onSeeReceiptClicked() { - launch { - tracker.trackReceiptViewTapped(order.id, order.status) - val receiptResult = paymentReceiptHelper.getReceiptUrl(order.id) - if (receiptResult.isSuccess) { - triggerEvent(PreviewReceipt(order.billingAddress.email, receiptResult.getOrThrow(), order.id)) - } else { - triggerEvent(ShowSnackbar(string.receipt_fetching_error)) - } - } + tracker.trackReceiptViewTapped(order.id, order.status) + loadReceiptUrl()?.let { + triggerEvent(PreviewReceipt(order.billingAddress.email, it, order.id)) + } ?: WooLog.e(T.ORDERS, "ReceiptUrl is null, but SeeReceipt button is visible") } fun onPrintingInstructionsClicked() { @@ -379,6 +372,12 @@ class OrderDetailViewModel @Inject constructor( triggerEvent(ShowSnackbar(message)) } + private fun loadReceiptUrl(): String? { + return selectedSite.getIfExists()?.let { + appPrefs.getReceiptUrl(it.id, it.siteId, it.selfHostedSiteId, order.id) + } + } + fun onViewRefundedProductsClicked() { triggerEvent(ViewRefundedProducts(orderId = order.id)) } @@ -581,7 +580,7 @@ class OrderDetailViewModel @Inject constructor( orderInfo = OrderDetailViewState.OrderInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectable, - isReceiptButtonsVisible = paymentReceiptHelper.isReceiptAvailable(order.id) && order.isOrderPaid, + receiptButtonStatus = !loadReceiptUrl().isNullOrEmpty() ), orderStatus = orderStatus, toolbarTitle = resourceProvider.getString( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt index 77a0558726a..78d2d9736fb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt @@ -33,6 +33,10 @@ data class OrderDetailViewState( data class OrderInfo( val order: Order? = null, val isPaymentCollectableWithCardReader: Boolean = false, - val isReceiptButtonsVisible: Boolean = false + val receiptButtonStatus: ReceiptButtonStatus = ReceiptButtonStatus.Hidden, ) : Parcelable + + enum class ReceiptButtonStatus { + Loading, Hidden, Visible + } } From 1f85a93faee1dc51ed6f1f6adcf155eb7937857b Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 14:57:30 +0100 Subject: [PATCH 103/160] Pass the status to the view --- .../ui/orders/details/OrderDetailFragment.kt | 4 +-- .../views/OrderDetailPaymentInfoView.kt | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt index 148976a6bd6..9590be95f3b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt @@ -437,7 +437,7 @@ class OrderDetailFragment : private fun showOrderDetail( order: Order, isPaymentCollectableWithCardReader: Boolean, - isReceiptButtonsVisible: Boolean + receiptButtonStatus: OrderDetailViewState.ReceiptButtonStatus ) { binding.orderDetailOrderStatus.updateOrder(order) binding.orderDetailShippingMethodNotice.isVisible = order.hasMultipleShippingLines @@ -449,7 +449,7 @@ class OrderDetailFragment : binding.orderDetailPaymentInfo.updatePaymentInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectableWithCardReader, - isReceiptAvailable = isReceiptButtonsVisible, + receiptButtonStatus = receiptButtonStatus, formatCurrencyForDisplay = currencyFormatter.buildBigDecimalFormatter(order.currency), onIssueRefundClickListener = { viewModel.onIssueOrderRefundClicked() }, onSeeReceiptClickListener = { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt index bc5384b9fc2..4f67cee39b9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt @@ -17,6 +17,7 @@ import com.woocommerce.android.extensions.show import com.woocommerce.android.model.GiftCardSummary import com.woocommerce.android.model.Order import com.woocommerce.android.model.Refund +import com.woocommerce.android.ui.orders.details.OrderDetailViewState import com.woocommerce.android.ui.orders.details.adapter.OrderDetailRefundsAdapter import com.woocommerce.android.ui.orders.details.adapter.OrderDetailRefundsLineBuilder import dagger.hilt.android.AndroidEntryPoint @@ -36,7 +37,7 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( @Suppress("LongParameterList") fun updatePaymentInfo( order: Order, - isReceiptAvailable: Boolean, + receiptButtonStatus: OrderDetailViewState.ReceiptButtonStatus, isPaymentCollectableWithCardReader: Boolean, formatCurrencyForDisplay: (BigDecimal) -> String, onSeeReceiptClickListener: (view: View) -> Unit, @@ -88,7 +89,7 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( updateFeesSection(order, formatCurrencyForDisplay) updateRefundSection(order, formatCurrencyForDisplay, onIssueRefundClickListener) updateCollectPaymentSection(order, onCollectPaymentClickListener) - updateSeeReceiptSection(isReceiptAvailable, onSeeReceiptClickListener) + updateSeeReceiptSection(receiptButtonStatus, onSeeReceiptClickListener) updatePrintingInstructionSection(isPaymentCollectableWithCardReader, onPrintingInstructionsClickListener) } @@ -192,16 +193,20 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( } private fun updateSeeReceiptSection( - isReceiptAvailable: Boolean, + receiptButtonStatus: OrderDetailViewState.ReceiptButtonStatus, onSeeReceiptClickListener: (view: View) -> Unit ) { - if (isReceiptAvailable) { - binding.paymentInfoSeeReceiptButton.visibility = VISIBLE - binding.paymentInfoSeeReceiptButton.setOnClickListener( - onSeeReceiptClickListener - ) - } else { - binding.paymentInfoSeeReceiptButton.visibility = GONE + when (receiptButtonStatus) { + OrderDetailViewState.ReceiptButtonStatus.Loading -> { + + } + OrderDetailViewState.ReceiptButtonStatus.Hidden -> binding.paymentInfoSeeReceiptButton.visibility = GONE + OrderDetailViewState.ReceiptButtonStatus.Visible -> { + binding.paymentInfoSeeReceiptButton.visibility = VISIBLE + binding.paymentInfoSeeReceiptButton.setOnClickListener( + onSeeReceiptClickListener + ) + } } } From 493ca09fe96972c4305fb080ed7d192b8e6718ae Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 15:11:12 +0100 Subject: [PATCH 104/160] Visible/Hidden status determination --- .../ui/orders/details/OrderDetailViewModel.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index 9ff82749b1a..d112c6bb020 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -49,6 +49,7 @@ import com.woocommerce.android.ui.orders.OrderStatusUpdateSource import com.woocommerce.android.ui.orders.details.customfields.CustomOrderFieldsHelper import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.ui.products.addons.AddonRepository @@ -91,7 +92,8 @@ class OrderDetailViewModel @Inject constructor( private val getOrderSubscriptions: GetOrderSubscriptions, private val giftCardRepository: GiftCardRepository, private val orderProductMapper: OrderProductMapper, - private val productDetailRepository: ProductDetailRepository + private val productDetailRepository: ProductDetailRepository, + private val paymentReceiptHelper: PaymentReceiptHelper, ) : ScopedViewModel(savedState), OnProductFetchedListener { private val navArgs: OrderDetailFragmentArgs by savedState.navArgs() @@ -343,10 +345,15 @@ class OrderDetailViewModel @Inject constructor( } fun onSeeReceiptClicked() { - tracker.trackReceiptViewTapped(order.id, order.status) - loadReceiptUrl()?.let { - triggerEvent(PreviewReceipt(order.billingAddress.email, it, order.id)) - } ?: WooLog.e(T.ORDERS, "ReceiptUrl is null, but SeeReceipt button is visible") + launch { + tracker.trackReceiptViewTapped(order.id, order.status) + val receiptResult = paymentReceiptHelper.getReceiptUrl(order.id) + if (receiptResult.isSuccess) { + triggerEvent(PreviewReceipt(order.billingAddress.email, receiptResult.getOrThrow(), order.id)) + } else { + triggerEvent(ShowSnackbar(string.receipt_fetching_error)) + } + } } fun onPrintingInstructionsClicked() { @@ -372,12 +379,6 @@ class OrderDetailViewModel @Inject constructor( triggerEvent(ShowSnackbar(message)) } - private fun loadReceiptUrl(): String? { - return selectedSite.getIfExists()?.let { - appPrefs.getReceiptUrl(it.id, it.siteId, it.selfHostedSiteId, order.id) - } - } - fun onViewRefundedProductsClicked() { triggerEvent(ViewRefundedProducts(orderId = order.id)) } @@ -580,7 +581,11 @@ class OrderDetailViewModel @Inject constructor( orderInfo = OrderDetailViewState.OrderInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectable, - receiptButtonStatus = !loadReceiptUrl().isNullOrEmpty() + receiptButtonStatus = if (paymentReceiptHelper.isReceiptAvailable(order.id) && order.isOrderPaid) { + OrderDetailViewState.ReceiptButtonStatus.Visible + } else { + OrderDetailViewState.ReceiptButtonStatus.Hidden + } ), orderStatus = orderStatus, toolbarTitle = resourceProvider.getString( From aef16562cce271d01fc612c5fd5ad24f994ea363 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 15:27:27 +0100 Subject: [PATCH 105/160] Fixed release notes --- RELEASE-NOTES.txt | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 0480dd755e9..305c459789b 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,20 +1,8 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] -17.4 ----- -- [*] [Internal] Collapse bottom drawer with totals on the order creation/editing screen when scrolling down [https://github.com/woocommerce/woocommerce-android/pull/10573] - -17.3 ------ -- [*] [Internal] Better displaying of the "locked" state of the buttons on the order creation screen. Fixed bug when it was possible to add "custom amount" even when the order is locked [https://github.com/woocommerce/woocommerce-android/pull/10562] - -17.2 ------ -- [*] [Internal] Tracking of "country" and "currency" properties for entry and exit of the payments flows [https://github.com/woocommerce/woocommerce-android/pull/10528] -- [*] Fixed bug when gift card was not displayed in the "totals" sections on the order creation/editing screens [https://github.com/woocommerce/woocommerce-android/pull/10546] - 17.1 ----- - [*] [Internal] Fixed crash when going to background from the order creation screen [https://github.com/woocommerce/woocommerce-android/pull/10600] +- [*] [Internal] Collapse bottom drawer with totals on the order creation/editing screen when scrolling down [https://github.com/woocommerce/woocommerce-android/pull/10573] 17.0 ----- From 899ec5b2027c8ba1ae8fe984ccc9ff0da6cb5f25 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 15:30:57 +0100 Subject: [PATCH 106/160] Pass loading status to the view --- .../ui/orders/details/OrderDetailViewModel.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index d112c6bb020..1b80d6660cb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -347,7 +347,21 @@ class OrderDetailViewModel @Inject constructor( fun onSeeReceiptClicked() { launch { tracker.trackReceiptViewTapped(order.id, order.status) + + viewState = viewState.copy( + orderInfo = viewState.orderInfo?.copy( + receiptButtonStatus = OrderDetailViewState.ReceiptButtonStatus.Loading + ) + ) + val receiptResult = paymentReceiptHelper.getReceiptUrl(order.id) + + viewState = viewState.copy( + orderInfo = viewState.orderInfo?.copy( + receiptButtonStatus = OrderDetailViewState.ReceiptButtonStatus.Visible + ) + ) + if (receiptResult.isSuccess) { triggerEvent(PreviewReceipt(order.billingAddress.email, receiptResult.getOrThrow(), order.id)) } else { From 0f997c3102696e778d42f2f2f588530d5593034b Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 15:44:44 +0100 Subject: [PATCH 107/160] Constraint layout usage to show loading indicator --- .../res/layout/order_detail_payment_info.xml | 120 +++++++++--------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml index 71df778356f..c3b8728e0e1 100644 --- a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml +++ b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml @@ -1,7 +1,7 @@ - @@ -370,65 +370,69 @@ tools:text="$34.00"/> - - - - - - - - - + - - - + android:layout_height="wrap_content"> + + + + + + + + + + + + + + - - + From e5e2ab625cd90a5dcf36353c44388a914a80fe58 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 15:56:48 +0100 Subject: [PATCH 108/160] Loading indicator to the receipt button --- .../views/OrderDetailPaymentInfoView.kt | 10 ++++- .../res/layout/order_detail_payment_info.xml | 42 ++++++++++++------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt index 4f67cee39b9..1a2bf575cb5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt @@ -198,10 +198,16 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( ) { when (receiptButtonStatus) { OrderDetailViewState.ReceiptButtonStatus.Loading -> { - + binding.paymentInfoSeeReceiptButton.visibility = VISIBLE + binding.paymentInfoSeeReceiptButton.isEnabled = false + binding.paymentInfoSeeReceiptButtonProgressBar.visibility = VISIBLE + } + OrderDetailViewState.ReceiptButtonStatus.Hidden -> { + binding.paymentInfoSeeReceiptButton.visibility = GONE + binding.paymentInfoSeeReceiptButtonProgressBar.visibility = GONE } - OrderDetailViewState.ReceiptButtonStatus.Hidden -> binding.paymentInfoSeeReceiptButton.visibility = GONE OrderDetailViewState.ReceiptButtonStatus.Visible -> { + binding.paymentInfoSeeReceiptButtonProgressBar.visibility = GONE binding.paymentInfoSeeReceiptButton.visibility = VISIBLE binding.paymentInfoSeeReceiptButton.setOnClickListener( onSeeReceiptClickListener diff --git a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml index c3b8728e0e1..1152c5feec6 100644 --- a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml +++ b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml @@ -382,15 +382,24 @@ style="@style/Woo.Button.TextButton" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginTop="@dimen/minor_100" + android:gravity="start|center_vertical" android:paddingStart="@dimen/major_100" android:paddingEnd="@dimen/major_100" - android:gravity="start" android:text="@string/orderdetail_see_receipt_button" android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@id/paymentInfo_seeReceiptButton_progressBar" app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintTop_toTopOf="@id/paymentInfo_seeReceiptButton" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/paymentInfo_seeReceiptButton" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/paymentInfo_collectCardPresentPaymentButton" /> - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/paymentInfo_issueRefundButton" /> From 2b6bc61337e72c48634b18ba23a9455bdf4d0353 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 16:15:43 +0100 Subject: [PATCH 109/160] Fixed blinking of the receipt details button issue --- .../android/ui/orders/details/OrderDetailViewModel.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index 1b80d6660cb..8902a5da997 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -103,9 +103,13 @@ class OrderDetailViewModel @Inject constructor( get() = requireNotNull(viewState.orderInfo?.order) set(value) { viewState = viewState.copy( - orderInfo = OrderDetailViewState.OrderInfo( + orderInfo = viewState.orderInfo?.copy( + order = value, + isPaymentCollectableWithCardReader = viewState.orderInfo?.isPaymentCollectableWithCardReader + ?: false + ) ?: OrderDetailViewState.OrderInfo( value, - viewState.orderInfo?.isPaymentCollectableWithCardReader ?: false + isPaymentCollectableWithCardReader = false ) ) } From c537a9d0004cbc8a7ada1910f6202a274a3df056 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 16:15:59 +0100 Subject: [PATCH 110/160] Better displaying of the progress bar --- WooCommerce/src/main/res/layout/order_detail_payment_info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml index 1152c5feec6..0566a777164 100644 --- a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml +++ b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml @@ -388,7 +388,7 @@ android:paddingEnd="@dimen/major_100" android:text="@string/orderdetail_see_receipt_button" android:textAllCaps="false" - app:layout_constraintEnd_toStartOf="@id/paymentInfo_seeReceiptButton_progressBar" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> From 3821eb77231a1819e7f8c86ea49c0ba564d271af Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 29 Jan 2024 18:03:16 +0100 Subject: [PATCH 111/160] Fixed and added tests --- .../ui/orders/OrderDetailViewModelTest.kt | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index bdbafe61f95..ad4ad6ae310 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -40,6 +40,7 @@ import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.ui.products.addons.AddonRepository import com.woocommerce.android.util.ContinuationWrapper +import com.woocommerce.android.util.captureValues import com.woocommerce.android.viewmodel.BaseUnitTest import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowUndoSnackbar @@ -326,7 +327,7 @@ class OrderDetailViewModelTest : BaseUnitTest() { } @Test - fun `given receipt is available and order is paid, when view model started, then state with receipt isReceiptButtonsVisible true emitted`() = + fun `given receipt is available and order is paid, when view model started, then state with receipt is visible emitted`() = testBlocking { // GIVEN whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(true) @@ -350,11 +351,13 @@ class OrderDetailViewModelTest : BaseUnitTest() { viewModel.start() // THEN - assertThat(detailViewState!!.orderInfo!!.isReceiptButtonsVisible).isTrue() + assertThat(detailViewState!!.orderInfo!!.receiptButtonStatus).isEqualTo( + OrderDetailViewState.ReceiptButtonStatus.Visible + ) } @Test - fun `given receipt is available and order not paid, when view model started, then state with receipt isReceiptButtonsVisible false emitted`() = + fun `given receipt is available and order not paid, when view model started, then state with receipt is hidden emitted`() = testBlocking { // GIVEN whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(true) @@ -378,11 +381,13 @@ class OrderDetailViewModelTest : BaseUnitTest() { viewModel.start() // THEN - assertThat(detailViewState!!.orderInfo!!.isReceiptButtonsVisible).isFalse() + assertThat(detailViewState!!.orderInfo!!.receiptButtonStatus).isEqualTo( + OrderDetailViewState.ReceiptButtonStatus.Hidden + ) } @Test - fun `given receipt is not available, when view model started, then state with receipt isReceiptButtonsVisible false emitted`() = + fun `given receipt is not available, when view model started, then state with receipt is hidden emitted`() = testBlocking { // GIVEN whenever(paymentReceiptHelper.isReceiptAvailable(any())).thenReturn(false) @@ -402,7 +407,9 @@ class OrderDetailViewModelTest : BaseUnitTest() { viewModel.start() // THEN - assertThat(detailViewState!!.orderInfo!!.isReceiptButtonsVisible).isFalse() + assertThat(detailViewState!!.orderInfo!!.receiptButtonStatus).isEqualTo( + OrderDetailViewState.ReceiptButtonStatus.Hidden + ) } @Test @@ -1275,6 +1282,32 @@ class OrderDetailViewModelTest : BaseUnitTest() { assertThat((viewModel.event.value as PreviewReceipt).billingEmail).isEqualTo(order.billingAddress.email) } + @Test + fun `when onSeeReceiptClicked clicked, then loading receipt status emitted`() = + testBlocking { + // GIVEN + whenever(orderDetailRepository.getOrderById(any())).thenReturn(order) + whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(false) + whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false) + val receiptUrl = "https://example.com" + whenever(paymentReceiptHelper.getReceiptUrl(order.id)).thenReturn(Result.success(receiptUrl)) + + // WHEN + viewModel.start() + + val states = viewModel.viewStateData.liveData.captureValues() + + viewModel.onSeeReceiptClicked() + + // THEN + assertThat((states.last()).orderInfo!!.receiptButtonStatus).isEqualTo( + OrderDetailViewState.ReceiptButtonStatus.Visible + ) + assertThat((states[states.size - 2]).orderInfo!!.receiptButtonStatus).isEqualTo( + OrderDetailViewState.ReceiptButtonStatus.Loading + ) + } + @Test fun `given order is paid, when status is processing order complete button should be visible`() = testBlocking { From 11711ea756f8d2c14b328d1a5ddbf411e95ed143 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 21:08:00 +0000 Subject: [PATCH 112/160] Bump com.google.android.gms:play-services-code-scanner Bumps com.google.android.gms:play-services-code-scanner from 16.0.0-beta3 to 16.1.0. --- updated-dependencies: - dependency-name: com.google.android.gms:play-services-code-scanner dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- WooCommerce/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 742ee153ca4..3dffa9f78c5 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -283,7 +283,7 @@ dependencies { implementation 'com.google.android.play:app-update-ktx:2.1.0' implementation 'com.google.android.play:review-ktx:2.0.1' - implementation 'com.google.android.gms:play-services-code-scanner:16.0.0-beta3' + implementation 'com.google.android.gms:play-services-code-scanner:16.1.0' implementation "com.google.mlkit:text-recognition:$mlkitTextRecognitionVersion" implementation "com.google.mlkit:barcode-scanning:$mlkitBarcodeScanningVersion" From 59849fc93621bcd78f6815dbf00b8dae6d94ecac Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 09:48:12 +0100 Subject: [PATCH 113/160] Operator to download and share receipt file --- .../payments/receipt/PaymentReceiptShare.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt new file mode 100644 index 00000000000..fbe980141a1 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt @@ -0,0 +1,52 @@ +package com.woocommerce.android.ui.payments.receipt + +import android.app.Application +import android.content.Intent +import android.os.Environment +import androidx.core.content.FileProvider +import com.woocommerce.android.media.FileUtils +import com.woocommerce.android.util.FileDownloader +import javax.inject.Inject + +class PaymentReceiptShare @Inject constructor( + private val fileUtils: FileUtils, + private val fileDownloader: FileDownloader, + private val context: Application, +) { + suspend operator fun invoke(receiptUrl: String): ReceiptShareResult { + val receiptFile = fileUtils.createTempTimeStampedFile( + storageDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + ?: context.filesDir, + prefix = "receipt", + fileExtension = "html" + ) ?: return ReceiptShareResult.Error.FileCreation + if (!fileDownloader.downloadFile(receiptUrl, receiptFile)) { + return ReceiptShareResult.Error.FileDownload + } + + val uri = FileProvider.getUriForFile( + context, + context.packageName + ".provider", + receiptFile + ) + val intent = Intent(Intent.ACTION_SEND).apply { + type = "application/*" + putExtra(Intent.EXTRA_STREAM, uri) + } + return try { + context.startActivity(Intent.createChooser(intent, null)) + ReceiptShareResult.Success + } catch (e: Exception) { + ReceiptShareResult.Error.Sharing + } + } + + sealed class ReceiptShareResult { + object Success : ReceiptShareResult() + sealed class Error : ReceiptShareResult() { + object Sharing : Error() + object FileCreation : Error() + object FileDownload : Error() + } + } +} From 1ff994617834f8b8e277912c4d0af63276cb2d14 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 30 Jan 2024 10:23:52 +0100 Subject: [PATCH 114/160] Disable the Save buttons if no options selected --- .../targets/BlazeCampaignTargetSelectionScreen.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt index 6cc1dfaaa46..4a6e525e919 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt @@ -4,6 +4,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons.Filled import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.runtime.Composable @@ -45,8 +47,11 @@ private fun TargetSelectionScreen( title = state.title, onNavigationButtonClick = onBackPressed, navigationIcon = Filled.ArrowBack, - actionButtonText = stringResource(id = string.save).uppercase(), - onActionButtonClick = onSaveTapped + actions = { + TextButton(onClick = onSaveTapped, enabled = state.selectedItems.isNotEmpty()) { + Text(stringResource(id = string.save).uppercase()) + } + } ) }, modifier = Modifier.background(MaterialTheme.colors.surface) From 9b8087082e7df9659c2e79970034ec5a1f7705ae Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 10:27:08 +0100 Subject: [PATCH 115/160] Handle sharing errors --- .../payment/CardReaderPaymentViewModel.kt | 2 +- .../payments/receipt/PaymentReceiptShare.kt | 10 +++- .../receipt/preview/ReceiptPreviewFragment.kt | 4 +- .../preview/ReceiptPreviewViewModel.kt | 58 +++++++++++-------- .../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 | 4 +- .../preview/ReceiptPreviewViewModelTest.kt | 8 +-- 22 files changed, 68 insertions(+), 50 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 339e8b351a1..711062c7a6b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -749,7 +749,7 @@ class CardReaderPaymentViewModel fun onEmailActivityNotFound() { tracker.trackEmailReceiptFailed() - triggerEvent(ShowSnackbarInDialog(R.string.card_reader_payment_email_client_not_found)) + triggerEvent(ShowSnackbarInDialog(R.string.card_reader_payment_receipt_app_to_share_not_found)) } fun onPrintResult(result: PrintJobResult) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt index fbe980141a1..4fd8d553bd9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt @@ -34,17 +34,21 @@ class PaymentReceiptShare @Inject constructor( putExtra(Intent.EXTRA_STREAM, uri) } return try { - context.startActivity(Intent.createChooser(intent, null)) + context.startActivity( + Intent.createChooser(intent, null).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) ReceiptShareResult.Success } catch (e: Exception) { - ReceiptShareResult.Error.Sharing + ReceiptShareResult.Error.Sharing(e) } } sealed class ReceiptShareResult { object Success : ReceiptShareResult() sealed class Error : ReceiptShareResult() { - object Sharing : Error() + data class Sharing(val exception: Exception) : Error() object FileCreation : Error() object FileDownload : Error() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt index 9523b67ec65..e6da1500a36 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt @@ -52,7 +52,7 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview), true } R.id.menu_send -> { - viewModel.onSendEmailClicked() + viewModel.onShareClicked() true } else -> false @@ -111,7 +111,7 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview), private fun composeEmail(event: SendReceipt) { ActivityUtils.sendEmail(requireActivity(), event.address, event.subject, event.content) { - viewModel.onEmailActivityNotFound() + viewModel.onActivityToShareNotFound() } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index e609731afa8..f2ae1fcd135 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -11,9 +11,7 @@ import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED import com.woocommerce.android.analytics.AnalyticsTrackerWrapper -import com.woocommerce.android.model.UiString.UiStringRes -import com.woocommerce.android.model.UiString.UiStringText -import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult @@ -32,7 +30,7 @@ class ReceiptPreviewViewModel @Inject constructor( savedState: SavedStateHandle, private val tracker: AnalyticsTrackerWrapper, - private val selectedSite: SelectedSite, + private val paymentReceiptShare: PaymentReceiptShare, ) : ScopedViewModel(savedState) { private val args: ReceiptPreviewFragmentArgs by savedState.navArgs() @@ -52,30 +50,44 @@ class ReceiptPreviewViewModel triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}")) } - fun onSendEmailClicked() { + fun onShareClicked() { launch { tracker.track(RECEIPT_EMAIL_TAPPED) - triggerEvent( - SendReceipt( - content = UiStringRes( - string.card_reader_payment_receipt_email_content, - listOf(UiStringText(args.receiptUrl)) - ), - subject = UiStringRes( - string.card_reader_payment_receipt_email_subject, - listOf(UiStringText(selectedSite.get().name.orEmpty())) - ), - address = args.billingEmail - ) - ) + when (val sharingResult = paymentReceiptShare(args.receiptUrl)) { + PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { + tracker.track( + RECEIPT_EMAIL_FAILED, + errorContext = this@ReceiptPreviewViewModel.javaClass.simpleName, + errorType = "file_creation_failed", + errorDescription = "File creation failed" + ) + triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_stored)) + } + PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { + tracker.track( + RECEIPT_EMAIL_FAILED, + errorContext = this@ReceiptPreviewViewModel.javaClass.simpleName, + errorType = "file_download_failed", + errorDescription = "File download failed" + ) + triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_downloaded)) + } + is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { + tracker.track( + RECEIPT_EMAIL_FAILED, + errorContext = this@ReceiptPreviewViewModel.javaClass.simpleName, + errorType = "no_app_found", + errorDescription = sharingResult.exception.message + ) + triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_app_to_share_not_found)) + } + PaymentReceiptShare.ReceiptShareResult.Success -> { + // no-op + } + } } } - fun onEmailActivityNotFound() { - tracker.track(RECEIPT_EMAIL_FAILED) - triggerEvent(ShowSnackbar(string.card_reader_payment_email_client_not_found)) - } - fun onPrintResult(result: PrintJobResult) { tracker.track( when (result) { diff --git a/WooCommerce/src/main/res/values-ar/strings.xml b/WooCommerce/src/main/res/values-ar/strings.xml index cc49beffa7f..76aedcc43e9 100644 --- a/WooCommerce/src/main/res/values-ar/strings.xml +++ b/WooCommerce/src/main/res/values-ar/strings.xml @@ -1910,7 +1910,7 @@ Language: ar سمات المجموعات تشغيل بلوتوث الجهاز المحمول حدث خطأ في أثناء إحضار الطلب. قد تكون حالة الطلب الموجودة في التطبيق قديمة. - لا يمكن اكتشاف تطبيق عميل البريد الإلكتروني الخاص بك + لا يمكن اكتشاف تطبيق عميل البريد الإلكتروني الخاص بك إيصالك من %s تحديث الطلب تحديث حالة التطبيق diff --git a/WooCommerce/src/main/res/values-de/strings.xml b/WooCommerce/src/main/res/values-de/strings.xml index f069ba46c0f..cf4d312a3a1 100644 --- a/WooCommerce/src/main/res/values-de/strings.xml +++ b/WooCommerce/src/main/res/values-de/strings.xml @@ -1910,7 +1910,7 @@ Language: de Variantenattribute Aktiviere Bluetooth auf dem Mobilgerät Fehler beim Abrufen der Bestellung Der Bestellstatus in der App ist eventuell nicht aktuell. - E-Mail-Client-App wird nicht erkannt. + E-Mail-Client-App wird nicht erkannt. Dein Beleg vom %s Bestellung aktualisieren App-Status aktualisieren diff --git a/WooCommerce/src/main/res/values-es/strings.xml b/WooCommerce/src/main/res/values-es/strings.xml index 8e32a914850..fe0e0d5bd54 100644 --- a/WooCommerce/src/main/res/values-es/strings.xml +++ b/WooCommerce/src/main/res/values-es/strings.xml @@ -1910,7 +1910,7 @@ Language: es Atributos de variaciones Activa Bluetooth en el dispositivo móvil Error al recuperar el pedido. El estado del pedido en la aplicación podría estar desactualizado. - No se pudo detectar una aplicación cliente de correo electrónico + No se pudo detectar una aplicación cliente de correo electrónico Tu recibo de %s Actualizando pedido Actualizando estado en la aplicación diff --git a/WooCommerce/src/main/res/values-fr/strings.xml b/WooCommerce/src/main/res/values-fr/strings.xml index 2fdba76735a..f18596537c6 100644 --- a/WooCommerce/src/main/res/values-fr/strings.xml +++ b/WooCommerce/src/main/res/values-fr/strings.xml @@ -1910,7 +1910,7 @@ Language: fr Attributs des variantes Activer le Bluetooth de l’appareil mobile Erreur lors de l’extraction de la commande. L’état de la commande dans l’application est peut-être obsolète. - Aucune application de messagerie détectée + Aucune application de messagerie détectée Votre reçu de %s Actualisation de la commande Mise à jour de l’état de l’application diff --git a/WooCommerce/src/main/res/values-he/strings.xml b/WooCommerce/src/main/res/values-he/strings.xml index 4e5a67434b2..8622401ba37 100644 --- a/WooCommerce/src/main/res/values-he/strings.xml +++ b/WooCommerce/src/main/res/values-he/strings.xml @@ -1910,7 +1910,7 @@ Language: he_IL מאפייני סוגים יש להפעיל את חיבור ה-Bluetooth של המכשיר הנייד שגיאה בהבאת ההזמנה. ייתכן שמצב ההזמנה באפליקציה לא מעודכן. - לא ניתן לאתר את האפליקציה של שירות האימייל שלך + לא ניתן לאתר את האפליקציה של שירות האימייל שלך הקבלה שלך מאת %s מרענן את ההזמנה מעדכן את מצב האפליקציה diff --git a/WooCommerce/src/main/res/values-id/strings.xml b/WooCommerce/src/main/res/values-id/strings.xml index fa29a8b2c73..b7315da284f 100644 --- a/WooCommerce/src/main/res/values-id/strings.xml +++ b/WooCommerce/src/main/res/values-id/strings.xml @@ -1910,7 +1910,7 @@ Language: id Atribut variasi Aktifkan bluetooth perangkat seluler Terjadi error saat mengambil pesanan. Status pesanan pada aplikasi mungkin telah usang. - Tidak dapat mendeteksi aplikasi klien email Anda + Tidak dapat mendeteksi aplikasi klien email Anda Tanda terima Anda dari %s Menyegarkan pesanan Memperbarui status aplikasi diff --git a/WooCommerce/src/main/res/values-it/strings.xml b/WooCommerce/src/main/res/values-it/strings.xml index 4f1d803552e..cae8e5ca689 100644 --- a/WooCommerce/src/main/res/values-it/strings.xml +++ b/WooCommerce/src/main/res/values-it/strings.xml @@ -1910,7 +1910,7 @@ Language: it Attributi della variante Attiva il Bluetooth del dispositivo mobile Errore durante il recupero dell\'ordine. Lo stato nell\'ordine dell\'app potrebbe essere obsoleto. - Impossibile rilevare il client e-mail della tua app + Impossibile rilevare il client e-mail della tua app La tua ricevuta da %s Aggiornamento dell\'ordine Aggiornamento dello stato dell\'app diff --git a/WooCommerce/src/main/res/values-ja/strings.xml b/WooCommerce/src/main/res/values-ja/strings.xml index ce4d1990c3b..57f251429ea 100644 --- a/WooCommerce/src/main/res/values-ja/strings.xml +++ b/WooCommerce/src/main/res/values-ja/strings.xml @@ -1910,7 +1910,7 @@ Language: ja_JP バリエーション属性 モバイルデバイスの Bluetooth をオンにする 注文を取得する際にエラーが発生しました。 アプリの注文状態が古い可能性があります。 - メールクライアントアプリを検出できません + メールクライアントアプリを検出できません %s のレシート 注文を更新する アプリの状態を更新する diff --git a/WooCommerce/src/main/res/values-ko/strings.xml b/WooCommerce/src/main/res/values-ko/strings.xml index 70f0935b61d..f445739f0b5 100644 --- a/WooCommerce/src/main/res/values-ko/strings.xml +++ b/WooCommerce/src/main/res/values-ko/strings.xml @@ -1910,7 +1910,7 @@ Language: ko_KR 변형 속성 모바일 장치 블루투스 켜기 주문을 가져오는 중 오류가 발생했습니다. 앱의 주문 상태가 기한이 지났을 수 있습니다. - 이메일 클라이언트 앱을 감지할 수 없습니다. + 이메일 클라이언트 앱을 감지할 수 없습니다. %s에서 받은 영수증 주문 새로 고치기 앱 상태 업데이트하기 diff --git a/WooCommerce/src/main/res/values-nl/strings.xml b/WooCommerce/src/main/res/values-nl/strings.xml index e53a15e7869..b60a6ba5886 100644 --- a/WooCommerce/src/main/res/values-nl/strings.xml +++ b/WooCommerce/src/main/res/values-nl/strings.xml @@ -1903,7 +1903,7 @@ Language: nl Variatie-eigenschappen Zet de Bluetooth aan op je mobiele apparaat Fout bij ophalen bestelling. De bestellingsstatus in de app kan verouderd zijn. - Detectie van mailclient-app mislukt + Detectie van mailclient-app mislukt Je kwitantie van %s Bestelling aan het vernieuwen… Status in de app aan het bijwerken… diff --git a/WooCommerce/src/main/res/values-pt-rBR/strings.xml b/WooCommerce/src/main/res/values-pt-rBR/strings.xml index 09d637b857d..ecde05b45f3 100644 --- a/WooCommerce/src/main/res/values-pt-rBR/strings.xml +++ b/WooCommerce/src/main/res/values-pt-rBR/strings.xml @@ -1910,7 +1910,7 @@ Language: pt_BR Atributos das variações Ativar o Bluetooth do dispositivo móvel Erro ao buscar o pedido. O status do pedido pode estar desatualizado no aplicativo. - Não é possível detectar seu aplicativo cliente de e-mail + Não é possível detectar seu aplicativo cliente de e-mail Seu recibo de %s Atualizando pedido Atualizando estado do aplicativo diff --git a/WooCommerce/src/main/res/values-ru/strings.xml b/WooCommerce/src/main/res/values-ru/strings.xml index a04d66d7e2f..cc35e4e790d 100644 --- a/WooCommerce/src/main/res/values-ru/strings.xml +++ b/WooCommerce/src/main/res/values-ru/strings.xml @@ -1910,7 +1910,7 @@ Language: ru Атрибуты вариантов Включите Bluetooth на мобильном устройстве Ошибка при загрузке заказа. Состояние заказа в приложении могло устареть. - Не удалось определить ваш почтовый клиент + Не удалось определить ваш почтовый клиент Ваша квитанция от %s Обновление заказа Обновление состояния приложения diff --git a/WooCommerce/src/main/res/values-sv/strings.xml b/WooCommerce/src/main/res/values-sv/strings.xml index 977cd03c403..9e4e1cf2148 100644 --- a/WooCommerce/src/main/res/values-sv/strings.xml +++ b/WooCommerce/src/main/res/values-sv/strings.xml @@ -1917,7 +1917,7 @@ Language: sv_SE Börja sälja idag genom att lägga till din första produkt i butiken. Variationsattribut Aktivera Bluetooth på den mobila enheten - Lyckas inte hitta ditt e-postprogram + Lyckas inte hitta ditt e-postprogram Uppdaterar beställning Uppdaterar appstatusen Det gick inte att hämta beställningen. Beställningens status i appen kan vara föråldrad. diff --git a/WooCommerce/src/main/res/values-tr/strings.xml b/WooCommerce/src/main/res/values-tr/strings.xml index 70eec362196..bf6d75a1ca7 100644 --- a/WooCommerce/src/main/res/values-tr/strings.xml +++ b/WooCommerce/src/main/res/values-tr/strings.xml @@ -1910,7 +1910,7 @@ Language: tr Varyasyon nitelikleri. Mobil cihazda Bluetooth\'u açın Sipariş alınırken hata oluştu Uygulamadaki sipariş durumu eski olabilir. - E-posta istemcisi uygulamanızı tespit edemiyoruz + E-posta istemcisi uygulamanızı tespit edemiyoruz %s makbuzunuz Sipariş yenileniyor Uygulama durumu güncelleniyor diff --git a/WooCommerce/src/main/res/values-zh-rCN/strings.xml b/WooCommerce/src/main/res/values-zh-rCN/strings.xml index 2094348f61f..1a835ed6868 100644 --- a/WooCommerce/src/main/res/values-zh-rCN/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rCN/strings.xml @@ -1909,7 +1909,7 @@ Language: zh_CN 添加产品 变体属性 打开移动设备蓝牙 - 未侦测到您的电子邮件客户端应用 + 未侦测到您的电子邮件客户端应用 来自 %s 的收据 刷新订单 更新应用程序状态 diff --git a/WooCommerce/src/main/res/values-zh-rTW/strings.xml b/WooCommerce/src/main/res/values-zh-rTW/strings.xml index 0c9892b1cae..2dcd4dcbef0 100644 --- a/WooCommerce/src/main/res/values-zh-rTW/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rTW/strings.xml @@ -1910,7 +1910,7 @@ Language: zh_TW 款式屬性 開啟行動裝置的藍牙 擷取訂單時發生錯誤。 應用程式中的訂單狀態可能已過期。 - 無法偵測電子郵件用戶端應用程式 + 無法偵測電子郵件用戶端應用程式 你的 %s 收據 正在重新整理訂單 正在更新應用程式狀態 diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index df91217db7d..7a855966ae3 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -1507,7 +1507,9 @@ In-Person Payment for Order #%1$s for %2$s blog_id %3$s. Your receipt from %s Thank you for your purchase! Click the link below for your payment receipt.\n\n%s - Can\'t detect your email client app + Unable to detect any application to which the receipt can be shared + Unable to download the receipt + Unable to store the receipt Error fetching order. Order state in the app might be outdated. The order is already paid Please make sure that the card reader is connected. diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt index e0845ba0b96..42bd8adf5e1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt @@ -75,7 +75,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { @Test fun `when user clicks on send email, then send receipt event emitted`() = testBlocking { - viewModel.onSendEmailClicked() + viewModel.onShareClicked() assertThat(viewModel.event.value).isInstanceOf(SendReceipt::class.java) } @@ -83,7 +83,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { @Test fun `when user clicks on send email, then event tracked`() = testBlocking { - viewModel.onSendEmailClicked() + viewModel.onShareClicked() verify(tracker).track(RECEIPT_EMAIL_TAPPED) } @@ -91,7 +91,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { @Test fun `when email application not found, then SnackBar with error shown`() = testBlocking { - viewModel.onEmailActivityNotFound() + viewModel.onActivityToShareNotFound() assertThat(viewModel.event.value).isInstanceOf(ShowSnackbar::class.java) } @@ -99,7 +99,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { @Test fun `when email application not found, then event tracked`() = testBlocking { - viewModel.onEmailActivityNotFound() + viewModel.onActivityToShareNotFound() verify(tracker).track(RECEIPT_EMAIL_FAILED) } From 63d4cb733b9fc8a50f4c6b6bca983c05a874f5c2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 10:28:14 +0100 Subject: [PATCH 116/160] Show loading when fetching --- .../ui/payments/receipt/preview/ReceiptPreviewViewModel.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index f2ae1fcd135..0b9f5ad9116 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -52,6 +52,8 @@ class ReceiptPreviewViewModel fun onShareClicked() { launch { + viewState.value = Loading + tracker.track(RECEIPT_EMAIL_TAPPED) when (val sharingResult = paymentReceiptShare(args.receiptUrl)) { PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { @@ -85,6 +87,8 @@ class ReceiptPreviewViewModel // no-op } } + + viewState.value = Content } } From 8fd1bac3ea376cf5d8b7f0e80b87e2717607f5fb Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 30 Jan 2024 10:31:19 +0100 Subject: [PATCH 117/160] Make the All button a toggle between "all" and "none" selection --- .../targets/BlazeCampaignTargetSelectionViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index fc57dfd8778..f4dd550af6c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -71,7 +71,13 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( } fun onAllButtonTapped() { - selectedIds.update { emptySet() } + selectedIds.update { + if (it.size == viewState.value?.items?.size) { + emptySet() + } else { + viewState.value?.items?.map { item -> item.id }?.toSet() ?: emptySet() + } + } } fun onBackPressed() { From 68863ac6d6ca2003692aa709b923b80d38925db4 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 30 Jan 2024 10:32:21 +0100 Subject: [PATCH 118/160] Show the All button as selected when all items are selected --- .../woocommerce/android/ui/compose/component/MultiSelectList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt index 3f7ad2bfc28..faafac4fe98 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt @@ -44,7 +44,7 @@ fun MultiSelectList( allItemsButton?.let { MultiSelectItem( item = it.text, - isSelected = selectedItems.isEmpty(), + isSelected = selectedItems.size == items.size, onItemToggled = it.onClicked, modifier = Modifier.fillMaxWidth() ) From 1c2b05e5cd05e745195ab95f324a5e3aa18dec7a Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 10:32:28 +0100 Subject: [PATCH 119/160] Removed SendReceipt event --- .../ui/payments/receipt/preview/ReceiptPreviewFragment.kt | 8 -------- .../receipt/preview/ReceiptPreviewViewModelEvent.kt | 3 --- 2 files changed, 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt index e6da1500a36..30753e17d69 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt @@ -14,7 +14,6 @@ import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentReceiptPreviewBinding import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver -import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar @@ -102,16 +101,9 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview), when (it) { is LoadUrl -> binding.receiptPreviewPreviewWebview.loadUrl(it.url) is PrintReceipt -> printHtmlHelper.printReceipt(requireActivity(), it.receiptUrl, it.documentName) - is SendReceipt -> composeEmail(it) is ShowSnackbar -> uiMessageResolver.showSnack(it.message) else -> it.isHandled = false } } } - - private fun composeEmail(event: SendReceipt) { - ActivityUtils.sendEmail(requireActivity(), event.address, event.subject, event.content) { - viewModel.onActivityToShareNotFound() - } - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt index 5edf89f0559..de027028483 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt @@ -1,10 +1,7 @@ package com.woocommerce.android.ui.payments.receipt.preview -import com.woocommerce.android.model.UiString import com.woocommerce.android.viewmodel.MultiLiveEvent data class LoadUrl(val url: String) : MultiLiveEvent.Event() data class PrintReceipt(val receiptUrl: String, val documentName: String) : MultiLiveEvent.Event() - -data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : MultiLiveEvent.Event() From ef7c5ac0c0ce784fb3a337d56cd202aad7b13d67 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 10:53:20 +0100 Subject: [PATCH 120/160] Put order number in the name of a file --- .../android/ui/payments/receipt/PaymentReceiptShare.kt | 4 ++-- .../ui/payments/receipt/preview/ReceiptPreviewViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt index 4fd8d553bd9..dfe8f406c7d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt @@ -13,11 +13,11 @@ class PaymentReceiptShare @Inject constructor( private val fileDownloader: FileDownloader, private val context: Application, ) { - suspend operator fun invoke(receiptUrl: String): ReceiptShareResult { + suspend operator fun invoke(receiptUrl: String, orderNumber: Long): ReceiptShareResult { val receiptFile = fileUtils.createTempTimeStampedFile( storageDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) ?: context.filesDir, - prefix = "receipt", + prefix = "receipt_$orderNumber", fileExtension = "html" ) ?: return ReceiptShareResult.Error.FileCreation if (!fileDownloader.downloadFile(receiptUrl, receiptFile)) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index 0b9f5ad9116..3262dc142dd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -55,7 +55,7 @@ class ReceiptPreviewViewModel viewState.value = Loading tracker.track(RECEIPT_EMAIL_TAPPED) - when (val sharingResult = paymentReceiptShare(args.receiptUrl)) { + when (val sharingResult = paymentReceiptShare(args.receiptUrl, args.orderId)) { PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { tracker.track( RECEIPT_EMAIL_FAILED, From 8b52b5326aa907385f69b3fe64253d0f27daf0bd Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 11:39:38 +0100 Subject: [PATCH 121/160] Share via file after card payment --- .../CardReaderPaymentDialogFragment.kt | 9 ---- .../payment/CardReaderPaymentViewModel.kt | 43 +++++++++++-------- .../CardReaderPaymentViewModelEvent.kt | 3 -- .../payment/CardReaderPaymentViewState.kt | 9 ++++ .../payments/tracking/PaymentsFlowTracker.kt | 27 ++++++++++++ 5 files changed, 62 insertions(+), 29 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt index a317c226d28..ae5b3ce8354 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt @@ -22,7 +22,6 @@ import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.CardReaderPaymentDialogBinding import com.woocommerce.android.extensions.navigateBackWithNotice -import com.woocommerce.android.model.UiString import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.support.requests.SupportRequestFormActivity import com.woocommerce.android.ui.base.UIMessageResolver @@ -32,7 +31,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInR import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulReceiptSentAutomaticallyState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulState import com.woocommerce.android.ui.payments.refunds.RefundSummaryFragment.Companion.KEY_INTERAC_SUCCESS -import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.util.UiHelpers.getTextOfUiString @@ -86,7 +84,6 @@ class CardReaderPaymentDialogFragment : PaymentsBaseDialogFragment(R.layout.card event.documentName ) InteracRefundSuccessful -> navigateBackWithNotice(KEY_INTERAC_SUCCESS) - is SendReceipt -> composeEmail(event.address, event.subject, event.content) is ShowSnackbar -> uiMessageResolver.showSnack(event.message) is ShowSnackbarInDialog -> Snackbar.make( requireView(), event.message, BaseTransientBottomBar.LENGTH_LONG @@ -186,12 +183,6 @@ class CardReaderPaymentDialogFragment : PaymentsBaseDialogFragment(R.layout.card mp.start() } - private fun composeEmail(address: String, subject: UiString, content: UiString) { - ActivityUtils.sendEmail(requireActivity(), address, subject, content) { - viewModel.onEmailActivityNotFound() - } - } - override fun onResume() { super.onResume() AnalyticsTracker.trackViewShown(this) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 711062c7a6b..fc52388c3f9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -68,6 +68,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CoroutineDispatchers @@ -117,6 +118,7 @@ class CardReaderPaymentViewModel private val paymentReceiptHelper: PaymentReceiptHelper, private val cardReaderOnboardingChecker: CardReaderOnboardingChecker, private val cardReaderConfigProvider: CardReaderCountryConfigProvider, + private val paymentReceiptShare: PaymentReceiptShare, ) : ScopedViewModel(savedState) { private val arguments: CardReaderPaymentDialogFragmentArgs by savedState.navArgs() @@ -611,9 +613,7 @@ class CardReaderPaymentViewModel val onSaveUserClicked = { onSaveForLaterClicked() } - val onSendReceiptClicked = { - onSendReceiptClicked(order.billingAddress.email) - } + val onSendReceiptClicked = { onSendReceiptClicked() } if (order.billingAddress.email.isBlank()) { viewState.postValue( @@ -723,27 +723,36 @@ class CardReaderPaymentViewModel } } - private fun onSendReceiptClicked(billingEmail: String) { + private fun onSendReceiptClicked() { launch { tracker.trackEmailReceiptTapped() + val stateBeforeLoading = viewState.value!! + viewState.postValue(ViewState.SharingReceiptState) val receiptResult = paymentReceiptHelper.getReceiptUrl(orderId) + if (receiptResult.isSuccess) { - triggerEvent( - SendReceipt( - content = UiStringRes( - R.string.card_reader_payment_receipt_email_content, - listOf(UiStringText(receiptResult.getOrThrow())) - ), - subject = UiStringRes( - R.string.card_reader_payment_receipt_email_subject, - listOf(UiStringText(selectedSite.get().name.orEmpty())) - ), - address = billingEmail - ) - ) + when (val sharingResult = paymentReceiptShare(receiptResult.getOrThrow(), orderId)) { + is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { + tracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_can_not_be_stored)) + } + is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { + tracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_can_not_be_downloaded)) + } + is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { + tracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_app_to_share_not_found)) + } + PaymentReceiptShare.ReceiptShareResult.Success -> { + // no-op + } + } } else { triggerEvent(ShowSnackbar(R.string.receipt_fetching_error)) } + + viewState.postValue(stateBeforeLoading) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt index 5a44a75ebb3..6ac68063777 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.payments.cardreader.payment import androidx.annotation.StringRes -import com.woocommerce.android.model.UiString import com.woocommerce.android.viewmodel.MultiLiveEvent.Event class ShowSnackbarInDialog(@StringRes val message: Int) : Event() @@ -17,5 +16,3 @@ object EnableNfc : Event() data class PurchaseCardReader(val url: String) : Event() data class PrintReceipt(val receiptUrl: String, val documentName: String) : Event() - -data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt index d7eaa0c5446..1f73c0126df 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt @@ -219,6 +219,15 @@ sealed class ViewState( override val isProgressVisible = true } + object SharingReceiptState : ViewState( + headerLabel = R.string.card_reader_payment_completed_payment_header, + illustration = null, + primaryActionLabel = null, + secondaryActionLabel = null, + ) { + override val isProgressVisible = true + } + object ReFetchingOrderState : ViewState( headerLabel = R.string.card_reader_payment_fetch_order_loading_header, hintLabel = R.string.card_reader_payment_fetch_order_loading_hint, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index 0e7b2739a57..9cdce47de8a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -73,6 +73,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.STRIPE_EXTENSION_GATEWAY import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.WOOCOMMERCE_PAYMENTS import com.woocommerce.android.ui.payments.hub.PaymentsHubViewModel.CashOnDeliverySource +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus.Result.NotAvailable import javax.inject.Inject @@ -580,6 +581,32 @@ class PaymentsFlowTracker @Inject constructor( ) } + fun trackPaymentsReceiptSharingFailed(sharingResult: PaymentReceiptShare.ReceiptShareResult.Error) { + when (sharingResult) { + is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { + track( + RECEIPT_EMAIL_FAILED, + errorType = "file_creation_failed", + errorDescription = "File creation failed" + ) + } + is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { + track( + RECEIPT_EMAIL_FAILED, + errorType = "file_download_failed", + errorDescription = "File download failed" + ) + } + is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { + track( + RECEIPT_EMAIL_FAILED, + errorType = "no_app_found", + errorDescription = sharingResult.exception.message + ) + } + } + } + private fun getAndResetFlowsDuration(): MutableMap { val result = mutableMapOf() .also { mutableMap -> From 0a299f8c750dee683734fef066f4dc86670a782b Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 11:42:29 +0100 Subject: [PATCH 122/160] Use payment flow tracker to track sharing result --- .../preview/ReceiptPreviewViewModel.kt | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index 3262dc142dd..ed7aa503ec5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.R.string -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED @@ -14,6 +13,7 @@ import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED @@ -30,6 +30,7 @@ class ReceiptPreviewViewModel @Inject constructor( savedState: SavedStateHandle, private val tracker: AnalyticsTrackerWrapper, + private val paymentsFlowTracker: PaymentsFlowTracker, private val paymentReceiptShare: PaymentReceiptShare, ) : ScopedViewModel(savedState) { private val args: ReceiptPreviewFragmentArgs by savedState.navArgs() @@ -56,31 +57,16 @@ class ReceiptPreviewViewModel tracker.track(RECEIPT_EMAIL_TAPPED) when (val sharingResult = paymentReceiptShare(args.receiptUrl, args.orderId)) { - PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { - tracker.track( - RECEIPT_EMAIL_FAILED, - errorContext = this@ReceiptPreviewViewModel.javaClass.simpleName, - errorType = "file_creation_failed", - errorDescription = "File creation failed" - ) + is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { + paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_stored)) } - PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { - tracker.track( - RECEIPT_EMAIL_FAILED, - errorContext = this@ReceiptPreviewViewModel.javaClass.simpleName, - errorType = "file_download_failed", - errorDescription = "File download failed" - ) + is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { + paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_downloaded)) } is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { - tracker.track( - RECEIPT_EMAIL_FAILED, - errorContext = this@ReceiptPreviewViewModel.javaClass.simpleName, - errorType = "no_app_found", - errorDescription = sharingResult.exception.message - ) + paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_app_to_share_not_found)) } PaymentReceiptShare.ReceiptShareResult.Success -> { From e3874e745ef88767a063b86cd966cc848125961b Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 11:59:49 +0100 Subject: [PATCH 123/160] Set receipt expiration days to 2 as agreed --- .../android/ui/payments/receipt/PaymentReceiptHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 53dea128b02..8832e65cfeb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -110,7 +110,7 @@ class PaymentReceiptHelper @Inject constructor( const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" const val WC_CAN_GENERATE_RECEIPTS_VERSION = "8.7.0" - const val RECEIPT_EXPIRATION_DAYS = 365 + const val RECEIPT_EXPIRATION_DAYS = 2 } class IsDevSiteSupported @Inject constructor() { From aaf4a97e09c7f395ca79048df5cf8c5c304d55d6 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 12:02:12 +0100 Subject: [PATCH 124/160] Fixed detekt complains --- .../payments/receipt/PaymentReceiptShare.kt | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt index dfe8f406c7d..d2a1c502470 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt @@ -13,35 +13,38 @@ class PaymentReceiptShare @Inject constructor( private val fileDownloader: FileDownloader, private val context: Application, ) { + @Suppress("TooGenericExceptionCaught") suspend operator fun invoke(receiptUrl: String, orderNumber: Long): ReceiptShareResult { val receiptFile = fileUtils.createTempTimeStampedFile( storageDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) ?: context.filesDir, prefix = "receipt_$orderNumber", fileExtension = "html" - ) ?: return ReceiptShareResult.Error.FileCreation - if (!fileDownloader.downloadFile(receiptUrl, receiptFile)) { - return ReceiptShareResult.Error.FileDownload - } - - val uri = FileProvider.getUriForFile( - context, - context.packageName + ".provider", - receiptFile ) - val intent = Intent(Intent.ACTION_SEND).apply { - type = "application/*" - putExtra(Intent.EXTRA_STREAM, uri) - } - return try { - context.startActivity( - Intent.createChooser(intent, null).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } + return if (receiptFile == null) { + ReceiptShareResult.Error.FileCreation + } else if (!fileDownloader.downloadFile(receiptUrl, receiptFile)) { + ReceiptShareResult.Error.FileDownload + } else { + val uri = FileProvider.getUriForFile( + context, + context.packageName + ".provider", + receiptFile ) - ReceiptShareResult.Success - } catch (e: Exception) { - ReceiptShareResult.Error.Sharing(e) + val intent = Intent(Intent.ACTION_SEND).apply { + type = "application/*" + putExtra(Intent.EXTRA_STREAM, uri) + } + try { + context.startActivity( + Intent.createChooser(intent, null).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + ReceiptShareResult.Success + } catch (e: Exception) { + ReceiptShareResult.Error.Sharing(e) + } } } From dee354de185efb50da67a6fa8cd5ce10941b73b4 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 12:27:36 +0100 Subject: [PATCH 125/160] Tests a compilable --- .../CardReaderPaymentViewModelTest.kt | 17 ++++++---- .../receipt/PaymentReceiptShareTest.kt | 27 ++++++++++++++++ .../preview/ReceiptPreviewViewModelTest.kt | 32 +++---------------- 3 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 74bab73e34a..0bbf49ce280 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -68,7 +68,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError.U import com.woocommerce.android.ui.payments.cardreader.payment.PlayChaChing import com.woocommerce.android.ui.payments.cardreader.payment.PrintReceipt import com.woocommerce.android.ui.payments.cardreader.payment.PurchaseCardReader -import com.woocommerce.android.ui.payments.cardreader.payment.SendReceipt import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderCapturingPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderCollectPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInReaderFailedPaymentState @@ -90,6 +89,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CurrencyFormatter @@ -182,6 +182,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { private val cardReaderOnboardingChecker: CardReaderOnboardingChecker = mock() private val cardReaderConfigProvider: CardReaderCountryConfigProvider = mock() private val cardReaderConfig: CardReaderConfigForSupportedCountry = CardReaderConfigForUSA + private val paymentReceiptShare: PaymentReceiptShare = mock() @Suppress("LongMethod") @Before @@ -209,6 +210,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { paymentReceiptHelper = paymentReceiptHelper, cardReaderOnboardingChecker = cardReaderOnboardingChecker, cardReaderConfigProvider = cardReaderConfigProvider, + paymentReceiptShare = paymentReceiptShare, ) whenever(orderRepository.getOrderById(any())).thenReturn(mockedOrder) @@ -2356,16 +2358,19 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { } @Test - fun `given external reader and receipt fetching success, when user clicks on send receipt button, then SendReceipt event emitted`() = + fun `given external reader and receipt fetching and sharing success, when user clicks on send receipt button, then nothing emitted`() = testBlocking { whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(PaymentCompleted("")) } + flow { emit(PaymentCompleted("url")) } } + whenever(paymentReceiptShare("url", 1L)).thenReturn( + PaymentReceiptShare.ReceiptShareResult.Success + ) viewModel.start() (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() - assertThat(viewModel.event.value).isInstanceOf(SendReceipt::class.java) + assertThat(viewModel.event.value).isNull() } @Test @@ -2380,8 +2385,6 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { viewModel.start() (viewModel.viewStateData.value as BuiltInReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() - - assertThat(viewModel.event.value).isInstanceOf(SendReceipt::class.java) } @Test @@ -4401,6 +4404,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { paymentReceiptHelper = paymentReceiptHelper, cardReaderOnboardingChecker = cardReaderOnboardingChecker, cardReaderConfigProvider = cardReaderConfigProvider, + paymentReceiptShare = paymentReceiptShare, ) } @@ -4433,6 +4437,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { paymentReceiptHelper = paymentReceiptHelper, cardReaderOnboardingChecker = cardReaderOnboardingChecker, cardReaderConfigProvider = cardReaderConfigProvider, + paymentReceiptShare = paymentReceiptShare, ) } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt new file mode 100644 index 00000000000..327879d2c3b --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt @@ -0,0 +1,27 @@ +package com.woocommerce.android.ui.payments.receipt + +import android.app.Application +import com.woocommerce.android.media.FileUtils +import com.woocommerce.android.util.FileDownloader +import com.woocommerce.android.viewmodel.BaseUnitTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Test +import org.mockito.kotlin.mock + +@ExperimentalCoroutinesApi +class PaymentReceiptShareTest : BaseUnitTest() { + private val fileUtils: FileUtils = mock() + private val fileDownloader: FileDownloader = mock() + private val context: Application = mock() + + private val sut = PaymentReceiptShare( + fileUtils = fileUtils, + fileDownloader = fileDownloader, + context = context, + ) + + @Test + fun `test receipt share`() = testBlocking { + sut.invoke("receiptUrl", 123L) + } +} diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt index 42bd8adf5e1..3b26237b967 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.payments.receipt.preview import androidx.lifecycle.SavedStateHandle -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED @@ -9,13 +8,14 @@ import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.STARTED import com.woocommerce.android.viewmodel.BaseUnitTest -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -31,6 +31,8 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { private val selectedSite: SelectedSite = mock() private val tracker: AnalyticsTrackerWrapper = mock() + private val paymentsFlowTracker: PaymentsFlowTracker = mock() + private val paymentReceiptShare: PaymentReceiptShare = mock() private val savedState: SavedStateHandle = ReceiptPreviewFragmentArgs( receiptUrl = "testing url", @@ -40,7 +42,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { @Before fun setUp() { - viewModel = ReceiptPreviewViewModel(savedState, tracker, selectedSite) + viewModel = ReceiptPreviewViewModel(savedState, tracker, paymentsFlowTracker, paymentReceiptShare) whenever(selectedSite.get()).thenReturn(SiteModel().apply { name = "testName" }) } @@ -72,14 +74,6 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { assertThat(contentState.isContentVisible).isTrue } - @Test - fun `when user clicks on send email, then send receipt event emitted`() = - testBlocking { - viewModel.onShareClicked() - - assertThat(viewModel.event.value).isInstanceOf(SendReceipt::class.java) - } - @Test fun `when user clicks on send email, then event tracked`() = testBlocking { @@ -88,22 +82,6 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { verify(tracker).track(RECEIPT_EMAIL_TAPPED) } - @Test - fun `when email application not found, then SnackBar with error shown`() = - testBlocking { - viewModel.onActivityToShareNotFound() - - assertThat(viewModel.event.value).isInstanceOf(ShowSnackbar::class.java) - } - - @Test - fun `when email application not found, then event tracked`() = - testBlocking { - viewModel.onActivityToShareNotFound() - - verify(tracker).track(RECEIPT_EMAIL_FAILED) - } - @Test fun `when user clicks on print receipt, then print receipt event emitted`() = testBlocking { From 6bda786190f1cbe96800e8b29179aa16a0562051 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 12:39:22 +0100 Subject: [PATCH 126/160] Partial testing for PaymentReceiptShare --- .../receipt/PaymentReceiptShareTest.kt | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt index 327879d2c3b..966918a2284 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShareTest.kt @@ -5,14 +5,23 @@ import com.woocommerce.android.media.FileUtils import com.woocommerce.android.util.FileDownloader import com.woocommerce.android.viewmodel.BaseUnitTest import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.io.File @ExperimentalCoroutinesApi class PaymentReceiptShareTest : BaseUnitTest() { private val fileUtils: FileUtils = mock() private val fileDownloader: FileDownloader = mock() - private val context: Application = mock() + + private val file: File = mock() + private val context: Application = mock { + on { getExternalFilesDir(anyOrNull()) }.thenReturn(file) + } private val sut = PaymentReceiptShare( fileUtils = fileUtils, @@ -21,7 +30,39 @@ class PaymentReceiptShareTest : BaseUnitTest() { ) @Test - fun `test receipt share`() = testBlocking { - sut.invoke("receiptUrl", 123L) + fun `given file not created, when invoke, then FileCreation error returned`() = testBlocking { + // GIVEN + whenever( + fileUtils.createTempTimeStampedFile( + storageDir = anyOrNull(), + prefix = eq("receipt_999"), + fileExtension = eq("html"), + ) + ).thenReturn(null) + + // WHEN + val result = sut("receiptUrl", 999L) + + // THEN + assertThat(result).isInstanceOf(PaymentReceiptShare.ReceiptShareResult.Error.FileCreation::class.java) + } + + @Test + fun `given file created but not downloaded, when invoke, then FileDownload error returned`() = testBlocking { + // GIVEN + whenever( + fileUtils.createTempTimeStampedFile( + storageDir = anyOrNull(), + prefix = eq("receipt_999"), + fileExtension = eq("html"), + ) + ).thenReturn(file) + whenever(fileDownloader.downloadFile(eq("receiptUrl"), eq(file))).thenReturn(false) + + // WHEN + val result = sut("receiptUrl", 999L) + + // THEN + assertThat(result).isInstanceOf(PaymentReceiptShare.ReceiptShareResult.Error.FileDownload::class.java) } } From 207803de8676b8d4373f88c19618e7c25e513f49 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 14:37:56 +0100 Subject: [PATCH 127/160] Test for CardReaderPaymentViewModel --- .../CardReaderPaymentViewModelTest.kt | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 0bbf49ce280..a9cbfa61c5f 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -2358,23 +2358,23 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { } @Test - fun `given external reader and receipt fetching and sharing success, when user clicks on send receipt button, then nothing emitted`() = + fun `given external reader and receipt fetching and sharing success, when user clicks on send receipt button, then PlayChaChing emitted`() = testBlocking { whenever(cardReaderManager.collectPayment(any())).thenAnswer { flow { emit(PaymentCompleted("url")) } } - whenever(paymentReceiptShare("url", 1L)).thenReturn( + whenever(paymentReceiptShare("test url", 1L)).thenReturn( PaymentReceiptShare.ReceiptShareResult.Success ) viewModel.start() (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() - assertThat(viewModel.event.value).isNull() + assertThat(viewModel.event.value).isEqualTo(PlayChaChing) } @Test - fun `given built in reader and receipt fetching success, when user clicks on send receipt button, then SendReceipt event emitted`() = + fun `given built in reader and receipt fetching and sharing success, when user clicks on send receipt button, then PlayChaChing emitted`() = testBlocking { whenever(cardReaderManager.collectPayment(any())).thenAnswer { flow { emit(PaymentCompleted("")) } @@ -2385,6 +2385,64 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { viewModel.start() (viewModel.viewStateData.value as BuiltInReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() + + assertThat(viewModel.event.value).isEqualTo(PlayChaChing) + } + + @Test + fun `given receipt fetching success and receipt file not created, when user clicks on send receipt button, then ShowSnackbar emitted`() = + testBlocking { + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("url")) } + } + whenever(paymentReceiptShare("test url", 1L)).thenReturn( + PaymentReceiptShare.ReceiptShareResult.Error.FileCreation + ) + viewModel.start() + + (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() + + assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo( + R.string.card_reader_payment_receipt_can_not_be_stored + ) + verify(tracker).trackPaymentsReceiptSharingFailed(PaymentReceiptShare.ReceiptShareResult.Error.FileCreation) + } + + @Test + fun `given receipt fetching success and receipt file not downloaded, when user clicks on send receipt button, then ShowSnackbar emitted`() = + testBlocking { + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("url")) } + } + whenever(paymentReceiptShare("test url", 1L)).thenReturn( + PaymentReceiptShare.ReceiptShareResult.Error.FileDownload + ) + viewModel.start() + + (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() + + assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo( + R.string.card_reader_payment_receipt_can_not_be_downloaded + ) + verify(tracker).trackPaymentsReceiptSharingFailed(PaymentReceiptShare.ReceiptShareResult.Error.FileDownload) + } + + @Test + fun `given receipt fetching success and receipt file not shared, when user clicks on send receipt button, then ShowSnackbar emitted`() = + testBlocking { + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("url")) } + } + val sharing = PaymentReceiptShare.ReceiptShareResult.Error.Sharing(Exception()) + whenever(paymentReceiptShare("test url", 1L)).thenReturn(sharing) + viewModel.start() + + (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() + + assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo( + R.string.card_reader_payment_receipt_app_to_share_not_found + ) + verify(tracker).trackPaymentsReceiptSharingFailed(sharing) } @Test From c2d1be0b728be040cef9a0a5ce69988c302ac8ad Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 14:39:25 +0100 Subject: [PATCH 128/160] Fixed PaymentReceiptHelperTest --- .../ui/payments/receipt/PaymentReceiptHelperTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt index 8aa5087bcc1..18b25902f08 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelperTest.kt @@ -102,7 +102,7 @@ class PaymentReceiptHelperTest : BaseUnitTest() { WooCommerceStore.WooPlugin.WOO_CORE ) ).thenReturn(plugin) - whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 2)).thenReturn( WooPayload(OrderReceiptResponse("url", "date")) ) @@ -126,7 +126,7 @@ class PaymentReceiptHelperTest : BaseUnitTest() { WooCommerceStore.WooPlugin.WOO_CORE ) ).thenReturn(plugin) - whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 2)).thenReturn( WooPayload( WooError( type = WooErrorType.API_ERROR, @@ -156,7 +156,7 @@ class PaymentReceiptHelperTest : BaseUnitTest() { selectedSite.get(), ) ).thenReturn(listOf(plugin)) - whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 2)).thenReturn( WooPayload( WooError( type = WooErrorType.API_ERROR, @@ -187,7 +187,7 @@ class PaymentReceiptHelperTest : BaseUnitTest() { selectedSite.get(), ) ).thenReturn(listOf(plugin)) - whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 365)).thenReturn( + whenever(orderStore.fetchOrdersReceipt(site, 1, expirationDays = 2)).thenReturn( WooPayload(OrderReceiptResponse("url", "date")) ) whenever(isDevSiteSupported()).thenReturn(true) From 6e6bca9ab61f9c59d751df2e220a8829a4edbe98 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 14:46:11 +0100 Subject: [PATCH 129/160] Test for ReceiptPreviewViewModel --- .../preview/ReceiptPreviewViewModelTest.kt | 80 ++++++++++++++++++- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt index 3b26237b967..3a8d0fa2a80 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt @@ -1,13 +1,13 @@ package com.woocommerce.android.ui.payments.receipt.preview import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED import com.woocommerce.android.analytics.AnalyticsTrackerWrapper -import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading @@ -16,6 +16,7 @@ import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.STARTED import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.MultiLiveEvent import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -23,13 +24,11 @@ import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import org.wordpress.android.fluxc.model.SiteModel @ExperimentalCoroutinesApi class ReceiptPreviewViewModelTest : BaseUnitTest() { private lateinit var viewModel: ReceiptPreviewViewModel - private val selectedSite: SelectedSite = mock() private val tracker: AnalyticsTrackerWrapper = mock() private val paymentsFlowTracker: PaymentsFlowTracker = mock() private val paymentReceiptShare: PaymentReceiptShare = mock() @@ -43,7 +42,6 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { @Before fun setUp() { viewModel = ReceiptPreviewViewModel(savedState, tracker, paymentsFlowTracker, paymentReceiptShare) - whenever(selectedSite.get()).thenReturn(SiteModel().apply { name = "testName" }) } @Test @@ -82,6 +80,80 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { verify(tracker).track(RECEIPT_EMAIL_TAPPED) } + @Test + fun `given sharing success, when onShareClicked, then no events emitted`() = + testBlocking { + // GIVEN + whenever(paymentReceiptShare("testing url", 999L)).thenReturn( + PaymentReceiptShare.ReceiptShareResult.Success + ) + + // WHEN + viewModel.onShareClicked() + + // THEN + assertThat(viewModel.event.value).isInstanceOf(LoadUrl::class.java) + } + + @Test + fun `given sharing failed with file cretion, when onShareClicked, then ShowSnackbar emitted`() = + testBlocking { + // GIVEN + whenever(paymentReceiptShare("testing url", 999L)).thenReturn( + PaymentReceiptShare.ReceiptShareResult.Error.FileCreation + ) + + // WHEN + viewModel.onShareClicked() + + // THEN + assertThat((viewModel.event.value as MultiLiveEvent.Event.ShowSnackbar).message).isEqualTo( + R.string.card_reader_payment_receipt_can_not_be_stored + ) + verify(paymentsFlowTracker).trackPaymentsReceiptSharingFailed( + PaymentReceiptShare.ReceiptShareResult.Error.FileCreation + ) + } + + @Test + fun `given sharing failed with file downloading, when onShareClicked, then ShowSnackbar emitted`() = + testBlocking { + // GIVEN + whenever(paymentReceiptShare("testing url", 999L)).thenReturn( + PaymentReceiptShare.ReceiptShareResult.Error.FileDownload + ) + + // WHEN + viewModel.onShareClicked() + + // THEN + assertThat((viewModel.event.value as MultiLiveEvent.Event.ShowSnackbar).message).isEqualTo( + R.string.card_reader_payment_receipt_can_not_be_downloaded + ) + verify(paymentsFlowTracker).trackPaymentsReceiptSharingFailed( + PaymentReceiptShare.ReceiptShareResult.Error.FileDownload + ) + } + + @Test + fun `given sharing failed with file sharing, when onShareClicked, then ShowSnackbar emitted`() = + testBlocking { + // GIVEN + val sharing = PaymentReceiptShare.ReceiptShareResult.Error.Sharing(Exception()) + whenever(paymentReceiptShare("testing url", 999L)).thenReturn(sharing) + + // WHEN + viewModel.onShareClicked() + + // THEN + assertThat((viewModel.event.value as MultiLiveEvent.Event.ShowSnackbar).message).isEqualTo( + R.string.card_reader_payment_receipt_app_to_share_not_found + ) + verify(paymentsFlowTracker).trackPaymentsReceiptSharingFailed( + sharing + ) + } + @Test fun `when user clicks on print receipt, then print receipt event emitted`() = testBlocking { From ed6b58bbebec1080c8d247a748d45ad750eb0709 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 30 Jan 2024 15:06:16 +0100 Subject: [PATCH 130/160] Updated release notes --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 463fb94b49d..091f9a955bd 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,4 +1,8 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] +17.2 +----- +- [**] Every order have a receipt now. The receipts can be shared via many apps installed on the phone [https://github.com/woocommerce/woocommerce-android/pull/10650] + 17.1 ----- From 76ce02213f2bbedbfd9406ddc6eae851997fe9b3 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 11:57:18 +0100 Subject: [PATCH 131/160] Attach receipt source inside of PaymentFlowTracker --- .../payments/receipt/PaymentReceiptHelper.kt | 2 +- .../payments/tracking/PaymentsFlowTracker.kt | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 8832e65cfeb..daf1bbffaee 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -77,7 +77,7 @@ class PaymentReceiptHelper @Inject constructor( } } - private suspend fun isWCCanGenerateReceipts(): Boolean { + suspend fun isWCCanGenerateReceipts(): Boolean { val currentWooCoreVersion = getWoocommerceCorePluginVersion() return currentWooCoreVersion.semverCompareTo(WC_CAN_GENERATE_RECEIPTS_VERSION) >= 0 diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index 9cdce47de8a..b6959450f9c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -73,6 +73,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.STRIPE_EXTENSION_GATEWAY import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.WOOCOMMERCE_PAYMENTS import com.woocommerce.android.ui.payments.hub.PaymentsHubViewModel.CashOnDeliverySource +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus.Result.NotAvailable import javax.inject.Inject @@ -81,7 +82,8 @@ class PaymentsFlowTracker @Inject constructor( private val trackerWrapper: AnalyticsTrackerWrapper, private val appPrefsWrapper: AppPrefsWrapper, private val selectedSite: SelectedSite, - private val cardReaderTrackingInfoProvider: CardReaderTrackingInfoProvider + private val cardReaderTrackingInfoProvider: CardReaderTrackingInfoProvider, + private val paymentReceiptHelper: PaymentReceiptHelper, ) { @VisibleForTesting fun track( @@ -156,6 +158,13 @@ class PaymentsFlowTracker @Inject constructor( } } + private suspend fun getReceiptSource(): Pair = + if (paymentReceiptHelper.isWCCanGenerateReceipts()) { + "source" to "backend" + } else { + "source" to "local" + } + @Suppress("ComplexMethod") private fun getOnboardingNotCompletedReason(state: CardReaderOnboardingState): String? = when (state) { @@ -581,27 +590,30 @@ class PaymentsFlowTracker @Inject constructor( ) } - fun trackPaymentsReceiptSharingFailed(sharingResult: PaymentReceiptShare.ReceiptShareResult.Error) { + suspend fun trackPaymentsReceiptSharingFailed(sharingResult: PaymentReceiptShare.ReceiptShareResult.Error) { when (sharingResult) { is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { track( RECEIPT_EMAIL_FAILED, errorType = "file_creation_failed", - errorDescription = "File creation failed" + errorDescription = "File creation failed", + properties = mutableMapOf(getReceiptSource()) ) } is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { track( RECEIPT_EMAIL_FAILED, errorType = "file_download_failed", - errorDescription = "File download failed" + errorDescription = "File download failed", + properties = mutableMapOf(getReceiptSource()) ) } is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { track( RECEIPT_EMAIL_FAILED, errorType = "no_app_found", - errorDescription = sharingResult.exception.message + errorDescription = sharingResult.exception.message, + properties = mutableMapOf(getReceiptSource()) ) } } From 976762d5fef4b3a3a2b76ca049f53e0e072f792c Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:20:28 +0100 Subject: [PATCH 132/160] Added source prop to the all methods that track receipt events --- .../payments/tracking/PaymentsFlowTracker.kt | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index b6959450f9c..38b58c005a3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -421,28 +421,53 @@ class PaymentsFlowTracker @Inject constructor( ) } - fun trackPrintReceiptTapped() { - track(RECEIPT_PRINT_TAPPED) + suspend fun trackPrintReceiptTapped() { + track( + RECEIPT_PRINT_TAPPED, + properties = mutableMapOf(getReceiptSource()) + ) + } + + suspend fun trackEmailReceiptTapped() { + track( + RECEIPT_EMAIL_TAPPED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackEmailReceiptTapped() { - track(RECEIPT_EMAIL_TAPPED) + suspend fun trackEmailReceiptFailed() { + track( + RECEIPT_EMAIL_FAILED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackEmailReceiptFailed() { - track(RECEIPT_EMAIL_FAILED) + suspend fun trackPrintReceiptCancelled() { + track( + RECEIPT_PRINT_CANCELED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackPrintReceiptCancelled() { - track(RECEIPT_PRINT_CANCELED) + suspend fun trackPrintReceiptFailed() { + track( + RECEIPT_PRINT_FAILED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackPrintReceiptFailed() { - track(RECEIPT_PRINT_FAILED) + suspend fun trackPrintReceiptSucceeded() { + track( + RECEIPT_PRINT_SUCCESS, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackPrintReceiptSucceeded() { - track(RECEIPT_PRINT_SUCCESS) + suspend fun trackReceiptViewTapped() { + track( + AnalyticsEvent.RECEIPT_VIEW_TAPPED, + properties = mutableMapOf(getReceiptSource()) + ) } fun trackPaymentCancelled(currentPaymentState: String?) { From d510551fb907e5418180f44320425b0b5ae1503e Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:21:32 +0100 Subject: [PATCH 133/160] Removed redundant method --- .../cardreader/payment/CardReaderPaymentViewModel.kt | 5 ----- .../payments/cardreader/CardReaderPaymentViewModelTest.kt | 8 -------- 2 files changed, 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index fc52388c3f9..c42ffc05783 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -756,11 +756,6 @@ class CardReaderPaymentViewModel } } - fun onEmailActivityNotFound() { - tracker.trackEmailReceiptFailed() - triggerEvent(ShowSnackbarInDialog(R.string.card_reader_payment_receipt_app_to_share_not_found)) - } - fun onPrintResult(result: PrintJobResult) { showPaymentSuccessfulState() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index a9cbfa61c5f..3b47aaaf39c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -2568,14 +2568,6 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { assertThat(viewModel.event.value).isInstanceOf(Exit::class.java) } - @Test - fun `when email activity not found, then event tracked`() = - testBlocking { - viewModel.onEmailActivityNotFound() - - verify(tracker).trackEmailReceiptFailed() - } - @Test fun `given user presses back button, when re-fetching order, then ReFetchingOrderState shown`() = testBlocking { From 862156d6bed5a2d982f4e15a21d4946fa52b533e Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:23:55 +0100 Subject: [PATCH 134/160] Launch to track receipt events --- .../cardreader/payment/CardReaderPaymentViewModel.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index c42ffc05783..53ed4f52590 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -759,10 +759,12 @@ class CardReaderPaymentViewModel fun onPrintResult(result: PrintJobResult) { showPaymentSuccessfulState() - when (result) { - CANCELLED -> tracker.trackPrintReceiptCancelled() - FAILED -> tracker.trackPrintReceiptFailed() - STARTED -> tracker.trackPrintReceiptSucceeded() + launch { + when (result) { + CANCELLED -> tracker.trackPrintReceiptCancelled() + FAILED -> tracker.trackPrintReceiptFailed() + STARTED -> tracker.trackPrintReceiptSucceeded() + } } } From a466cc3d8a7f8cb02bd2fb5a19489cd3c35bce76 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:30:53 +0100 Subject: [PATCH 135/160] Track all receipt related events via tracker class --- .../preview/ReceiptPreviewViewModel.kt | 25 ++++++++----------- .../payments/tracking/PaymentsFlowTracker.kt | 7 ------ 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index ed7aa503ec5..1a24d00ab66 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -4,12 +4,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.R.string -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED -import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading @@ -29,7 +23,6 @@ import javax.inject.Inject class ReceiptPreviewViewModel @Inject constructor( savedState: SavedStateHandle, - private val tracker: AnalyticsTrackerWrapper, private val paymentsFlowTracker: PaymentsFlowTracker, private val paymentReceiptShare: PaymentReceiptShare, ) : ScopedViewModel(savedState) { @@ -47,15 +40,17 @@ class ReceiptPreviewViewModel } fun onPrintClicked() { - tracker.track(RECEIPT_PRINT_TAPPED) - triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}")) + launch { + paymentsFlowTracker.trackPrintReceiptTapped() + triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}")) + } } fun onShareClicked() { launch { viewState.value = Loading - tracker.track(RECEIPT_EMAIL_TAPPED) + paymentsFlowTracker.trackEmailReceiptTapped() when (val sharingResult = paymentReceiptShare(args.receiptUrl, args.orderId)) { is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) @@ -79,13 +74,13 @@ class ReceiptPreviewViewModel } fun onPrintResult(result: PrintJobResult) { - tracker.track( + launch { when (result) { - CANCELLED -> RECEIPT_PRINT_CANCELED - FAILED -> RECEIPT_PRINT_FAILED - STARTED -> RECEIPT_PRINT_SUCCESS + CANCELLED -> paymentsFlowTracker.trackPrintReceiptCancelled() + FAILED -> paymentsFlowTracker.trackPrintReceiptFailed() + STARTED -> paymentsFlowTracker.trackPrintReceiptSucceeded() } - ) + } } sealed class ReceiptPreviewViewState( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index 38b58c005a3..52271edf0cb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -435,13 +435,6 @@ class PaymentsFlowTracker @Inject constructor( ) } - suspend fun trackEmailReceiptFailed() { - track( - RECEIPT_EMAIL_FAILED, - properties = mutableMapOf(getReceiptSource()) - ) - } - suspend fun trackPrintReceiptCancelled() { track( RECEIPT_PRINT_CANCELED, From 70635adf45e0abc73d80c4a57679abe9a4fb681f Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:33:28 +0100 Subject: [PATCH 136/160] RECEIPT_URL_FETCHING_FAILS added an event --- .../kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt index 6b269a756c9..a6a748927a7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt @@ -452,6 +452,7 @@ enum class AnalyticsEvent(val siteless: Boolean = false) { RECEIPT_PRINT_CANCELED, RECEIPT_PRINT_SUCCESS, RECEIPT_VIEW_TAPPED, + RECEIPT_URL_FETCHING_FAILS, // -- Top-level navigation MAIN_MENU_SETTINGS_TAPPED, From 53adb50ef2d5975806726e8eae344849bf3ce9ed Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:38:48 +0100 Subject: [PATCH 137/160] RECEIPT_VIEW_TAPPED tracked --- .../android/ui/orders/details/OrderDetailTracker.kt | 9 +++++---- .../android/ui/payments/tracking/PaymentsFlowTracker.kt | 8 ++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt index b86ef2b5a92..195b3b88586 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt @@ -4,11 +4,13 @@ import com.woocommerce.android.analytics.AnalyticsEvent import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.model.Order +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import org.wordpress.android.fluxc.store.WCOrderStore import javax.inject.Inject class OrderDetailTracker @Inject constructor( - private val trackerWrapper: AnalyticsTrackerWrapper + private val trackerWrapper: AnalyticsTrackerWrapper, + private val paymentsFlowTracker: PaymentsFlowTracker, ) { fun trackCustomFieldsTapped() { trackerWrapper.track(AnalyticsEvent.ORDER_VIEW_CUSTOM_FIELDS_TAPPED) @@ -24,9 +26,8 @@ class OrderDetailTracker @Inject constructor( ) } - fun trackReceiptViewTapped(orderId: Long, orderStatus: Order.Status) { - trackerWrapper.track( - AnalyticsEvent.RECEIPT_VIEW_TAPPED, + suspend fun trackReceiptViewTapped(orderId: Long, orderStatus: Order.Status) { + paymentsFlowTracker.trackReceiptViewTapped( mapOf( AnalyticsTracker.KEY_ORDER_ID to orderId, AnalyticsTracker.KEY_STATUS to orderStatus diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index 52271edf0cb..105f4efa171 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -456,10 +456,14 @@ class PaymentsFlowTracker @Inject constructor( ) } - suspend fun trackReceiptViewTapped() { + suspend fun trackReceiptViewTapped(properties: Map) { track( AnalyticsEvent.RECEIPT_VIEW_TAPPED, - properties = mutableMapOf(getReceiptSource()) + properties = properties.toMutableMap().also { + it.putAll( + mapOf(getReceiptSource()) + ) + } ) } From fd3966a94bfca3898ab0de69bd4d24809cea1488 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:40:53 +0100 Subject: [PATCH 138/160] trackReceiptUrlFetchingFails method --- .../ui/payments/tracking/PaymentsFlowTracker.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index 105f4efa171..eea12f6ad83 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -53,6 +53,7 @@ import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED +import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_URL_FETCHING_FAILS import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_CASH_ON_DELIVERY_SOURCE import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_ERROR_DESC @@ -467,6 +468,18 @@ class PaymentsFlowTracker @Inject constructor( ) } + suspend fun trackReceiptUrlFetchingFails( + errorDescription: String, + errorType: String, + ) { + track( + RECEIPT_URL_FETCHING_FAILS, + properties = mutableMapOf(getReceiptSource()), + errorDescription = errorDescription, + errorType = errorType, + ) + } + fun trackPaymentCancelled(currentPaymentState: String?) { track( CARD_PRESENT_COLLECT_PAYMENT_CANCELLED, From bf278d4a0b9474f0b59ec99b62ca7b5108588788 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:41:52 +0100 Subject: [PATCH 139/160] RECEIPT_VIEW_TAPPED imported statically --- .../android/ui/payments/tracking/PaymentsFlowTracker.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index eea12f6ad83..bdbcc421ee9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -54,6 +54,7 @@ import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_URL_FETCHING_FAILS +import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_VIEW_TAPPED import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_CASH_ON_DELIVERY_SOURCE import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_ERROR_DESC @@ -459,7 +460,7 @@ class PaymentsFlowTracker @Inject constructor( suspend fun trackReceiptViewTapped(properties: Map) { track( - AnalyticsEvent.RECEIPT_VIEW_TAPPED, + RECEIPT_VIEW_TAPPED, properties = properties.toMutableMap().also { it.putAll( mapOf(getReceiptSource()) @@ -635,6 +636,7 @@ class PaymentsFlowTracker @Inject constructor( properties = mutableMapOf(getReceiptSource()) ) } + is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { track( RECEIPT_EMAIL_FAILED, @@ -643,6 +645,7 @@ class PaymentsFlowTracker @Inject constructor( properties = mutableMapOf(getReceiptSource()) ) } + is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { track( RECEIPT_EMAIL_FAILED, From 08122c25215cc9e68bfce5b70df417d3ff1049ca Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:44:46 +0100 Subject: [PATCH 140/160] trackReceiptUrlFetchingFails in the order details screen --- .../android/ui/orders/details/OrderDetailViewModel.kt | 3 +++ .../android/ui/payments/tracking/PaymentsFlowTracker.kt | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index b018d182f78..6b3df9c207f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -351,6 +351,9 @@ class OrderDetailViewModel @Inject constructor( if (receiptResult.isSuccess) { triggerEvent(PreviewReceipt(order.billingAddress.email, receiptResult.getOrThrow(), order.id)) } else { + paymentsFlowTracker.trackReceiptUrlFetchingFails( + errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error", + ) triggerEvent(ShowSnackbar(string.receipt_fetching_error)) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index bdbcc421ee9..ffd264fd820 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -469,15 +469,11 @@ class PaymentsFlowTracker @Inject constructor( ) } - suspend fun trackReceiptUrlFetchingFails( - errorDescription: String, - errorType: String, - ) { + suspend fun trackReceiptUrlFetchingFails(errorDescription: String) { track( RECEIPT_URL_FETCHING_FAILS, properties = mutableMapOf(getReceiptSource()), errorDescription = errorDescription, - errorType = errorType, ) } From efbed421734c0b6d1750d197c67dc6889f6cc28d Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 12:45:40 +0100 Subject: [PATCH 141/160] On card card reader restult screen --- .../cardreader/payment/CardReaderPaymentViewModel.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 53ed4f52590..1bcbfff35aa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -718,6 +718,9 @@ class CardReaderPaymentViewModel ) ) } else { + tracker.trackReceiptUrlFetchingFails( + errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error", + ) triggerEvent(ShowSnackbar(R.string.receipt_fetching_error)) } } @@ -749,6 +752,9 @@ class CardReaderPaymentViewModel } } } else { + tracker.trackReceiptUrlFetchingFails( + errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error", + ) triggerEvent(ShowSnackbar(R.string.receipt_fetching_error)) } From c6059f361b4f43ac21236a2eda80ad815e657f6b Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 16:02:14 +0100 Subject: [PATCH 142/160] Tests compilable --- .../CardReaderPaymentViewModelTest.kt | 6 +++--- .../preview/ReceiptPreviewViewModelTest.kt | 19 ++++++------------- .../tracking/PaymentsFlowTrackerTest.kt | 12 ++++++++---- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 3b47aaaf39c..5f1f98229d4 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -2337,21 +2337,21 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { } @Test - fun `when OS accepts the print request, then print success event tracked`() { + fun `when OS accepts the print request, then print success event tracked`() = testBlocking { viewModel.onPrintResult(STARTED) verify(tracker).trackPrintReceiptSucceeded() } @Test - fun `when OS refuses the print request, then print failed event tracked`() { + fun `when OS refuses the print request, then print failed event tracked`() = testBlocking { viewModel.onPrintResult(FAILED) verify(tracker).trackPrintReceiptFailed() } @Test - fun `when manually cancels the print request, then print cancelled event tracked`() { + fun `when manually cancels the print request, then print cancelled event tracked`() = testBlocking { viewModel.onPrintResult(CANCELLED) verify(tracker).trackPrintReceiptCancelled() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt index 3a8d0fa2a80..a76365c0dbc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt @@ -2,12 +2,6 @@ package com.woocommerce.android.ui.payments.receipt.preview import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.R -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED -import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading @@ -29,7 +23,6 @@ import org.mockito.kotlin.whenever class ReceiptPreviewViewModelTest : BaseUnitTest() { private lateinit var viewModel: ReceiptPreviewViewModel - private val tracker: AnalyticsTrackerWrapper = mock() private val paymentsFlowTracker: PaymentsFlowTracker = mock() private val paymentReceiptShare: PaymentReceiptShare = mock() @@ -41,7 +34,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { @Before fun setUp() { - viewModel = ReceiptPreviewViewModel(savedState, tracker, paymentsFlowTracker, paymentReceiptShare) + viewModel = ReceiptPreviewViewModel(savedState, paymentsFlowTracker, paymentReceiptShare) } @Test @@ -77,7 +70,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { testBlocking { viewModel.onShareClicked() - verify(tracker).track(RECEIPT_EMAIL_TAPPED) + verify(paymentsFlowTracker).trackEmailReceiptTapped() } @Test @@ -167,7 +160,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { testBlocking { viewModel.onPrintClicked() - verify(tracker).track(RECEIPT_PRINT_TAPPED) + verify(paymentsFlowTracker).trackPrintReceiptTapped() } @Test @@ -175,7 +168,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { testBlocking { viewModel.onPrintResult(FAILED) - verify(tracker).track(RECEIPT_PRINT_FAILED) + verify(paymentsFlowTracker).trackPrintReceiptFailed() } @Test @@ -183,7 +176,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { testBlocking { viewModel.onPrintResult(CANCELLED) - verify(tracker).track(RECEIPT_PRINT_CANCELED) + verify(paymentsFlowTracker).trackPrintReceiptCancelled() } @Test @@ -191,6 +184,6 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { testBlocking { viewModel.onPrintResult(STARTED) - verify(tracker).track(RECEIPT_PRINT_SUCCESS) + verify(paymentsFlowTracker).trackPrintReceiptSucceeded() } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt index 14e117d6353..e59c768f428 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt @@ -42,6 +42,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.STRI import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.WOOCOMMERCE_PAYMENTS import com.woocommerce.android.ui.payments.hub.PaymentsHubViewModel.CashOnDeliverySource.ONBOARDING import com.woocommerce.android.ui.payments.hub.PaymentsHubViewModel.CashOnDeliverySource.PAYMENTS_HUB +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.viewmodel.BaseUnitTest import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat @@ -93,11 +94,14 @@ class PaymentsFlowTrackerTest : BaseUnitTest() { ) } + private val paymentReceiptHelper: PaymentReceiptHelper = mock() + private val paymentsFlowTracker = PaymentsFlowTracker( trackerWrapper, appPrefsWrapper, selectedSite, - cardReaderTrackingInfoProvider + cardReaderTrackingInfoProvider, + paymentReceiptHelper, ) @Test @@ -1047,21 +1051,21 @@ class PaymentsFlowTrackerTest : BaseUnitTest() { } @Test - fun `when OS accepts the print request, then RECEIPT_PRINT_SUCCESS tracked`() { + fun `when OS accepts the print request, then RECEIPT_PRINT_SUCCESS tracked`() = testBlocking { paymentsFlowTracker.trackPrintReceiptSucceeded() verify(trackerWrapper).track(eq(RECEIPT_PRINT_SUCCESS), any()) } @Test - fun `when OS refuses the print request, then RECEIPT_PRINT_FAILED tracked`() { + fun `when OS refuses the print request, then RECEIPT_PRINT_FAILED tracked`() = testBlocking { paymentsFlowTracker.trackPrintReceiptFailed() verify(trackerWrapper).track(eq(RECEIPT_PRINT_FAILED), any()) } @Test - fun `when manually cancels the print request, then RECEIPT_PRINT_CANCELED tracked`() { + fun `when manually cancels the print request, then RECEIPT_PRINT_CANCELED tracked`() = testBlocking { paymentsFlowTracker.trackPrintReceiptCancelled() verify(trackerWrapper).track(eq(RECEIPT_PRINT_CANCELED), any()) From 6957125a316062db89ed81dfe6625ae5b0f0d3f9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 16:36:17 +0100 Subject: [PATCH 143/160] Tests of PaymentsFlowTracker --- .../payments/tracking/PaymentsFlowTracker.kt | 4 +- .../tracking/PaymentsFlowTrackerTest.kt | 157 +++++++++++++++--- 2 files changed, 140 insertions(+), 21 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index ffd264fd820..0ffc92e1cb3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -162,9 +162,9 @@ class PaymentsFlowTracker @Inject constructor( private suspend fun getReceiptSource(): Pair = if (paymentReceiptHelper.isWCCanGenerateReceipts()) { - "source" to "backend" + AnalyticsTracker.KEY_SOURCE to "backend" } else { - "source" to "local" + AnalyticsTracker.KEY_SOURCE to "local" } @Suppress("ComplexMethod") diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt index e59c768f428..4f78bbd6d19 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTrackerTest.kt @@ -26,7 +26,6 @@ import com.woocommerce.android.analytics.AnalyticsEvent.ENABLE_CASH_ON_DELIVERY_ import com.woocommerce.android.analytics.AnalyticsEvent.PAYMENTS_FLOW_ORDER_COLLECT_PAYMENT_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED import com.woocommerce.android.analytics.AnalyticsTracker @@ -77,13 +76,13 @@ class PaymentsFlowTrackerTest : BaseUnitTest() { } private val trackerWrapper: AnalyticsTrackerWrapper = mock() - private val appPrefsWrapper: AppPrefsWrapper = mock() { + private val appPrefsWrapper: AppPrefsWrapper = mock { on(it.getCardReaderPreferredPlugin(anyInt(), anyLong(), anyLong())).thenReturn(WOOCOMMERCE_PAYMENTS) } private val selectedSite: SelectedSite = mock { on(it.get()).thenReturn(SiteModel()) } - private val cardReaderTrackingInfoProvider: CardReaderTrackingInfoProvider = mock() { + private val cardReaderTrackingInfoProvider: CardReaderTrackingInfoProvider = mock { on { trackingInfo }.thenReturn( TrackingInfo( country = COUNTRY_CODE, @@ -1043,40 +1042,160 @@ class PaymentsFlowTrackerTest : BaseUnitTest() { } @Test - fun `when user clicks on print receipt button, then RECEIPT_PRINT_TAPPED tracked`() = + fun `given wc core can generate receipts, when trackPrintReceiptTapped, then RECEIPT_PRINT_TAPPED tracked`() = testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(true) + paymentsFlowTracker.trackPrintReceiptTapped() - verify(trackerWrapper).track(eq(RECEIPT_PRINT_TAPPED), any()) + verify(trackerWrapper).track( + eq(RECEIPT_PRINT_TAPPED), + check { assertThat(it["source"]).isEqualTo("backend") } + ) } @Test - fun `when OS accepts the print request, then RECEIPT_PRINT_SUCCESS tracked`() = testBlocking { - paymentsFlowTracker.trackPrintReceiptSucceeded() + fun `given new wc core can not generate receipts, when trackPrintReceiptTapped, then RECEIPT_PRINT_TAPPED tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(false) - verify(trackerWrapper).track(eq(RECEIPT_PRINT_SUCCESS), any()) - } + paymentsFlowTracker.trackPrintReceiptTapped() + + verify(trackerWrapper).track( + eq(RECEIPT_PRINT_TAPPED), + check { assertThat(it["source"]).isEqualTo("local") } + ) + } @Test - fun `when OS refuses the print request, then RECEIPT_PRINT_FAILED tracked`() = testBlocking { - paymentsFlowTracker.trackPrintReceiptFailed() + fun `given wc core can generate receipts, when trackPrintReceiptSucceeded, then RECEIPT_PRINT_SUCCESS tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(true) - verify(trackerWrapper).track(eq(RECEIPT_PRINT_FAILED), any()) - } + paymentsFlowTracker.trackPrintReceiptSucceeded() + + verify(trackerWrapper).track( + eq(RECEIPT_PRINT_SUCCESS), + check { assertThat(it["source"]).isEqualTo("backend") } + ) + } @Test - fun `when manually cancels the print request, then RECEIPT_PRINT_CANCELED tracked`() = testBlocking { - paymentsFlowTracker.trackPrintReceiptCancelled() + fun `given wc core can not generate receipts, when trackPrintReceiptSucceeded, then RECEIPT_PRINT_SUCCESS tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(false) - verify(trackerWrapper).track(eq(RECEIPT_PRINT_CANCELED), any()) - } + paymentsFlowTracker.trackPrintReceiptSucceeded() + + verify(trackerWrapper).track( + eq(RECEIPT_PRINT_SUCCESS), + check { assertThat(it["source"]).isEqualTo("local") } + ) + } + + @Test + fun `given wc core can generate receipts, when trackPrintReceiptCancelled, then RECEIPT_PRINT_CANCELED tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(true) + + paymentsFlowTracker.trackPrintReceiptCancelled() + + verify(trackerWrapper).track( + eq(RECEIPT_PRINT_CANCELED), + check { assertThat(it["source"]).isEqualTo("backend") } + ) + } + + @Test + fun `given wc core can not generate receipts, when trackPrintReceiptCancelled, then RECEIPT_PRINT_CANCELED tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(false) + + paymentsFlowTracker.trackPrintReceiptCancelled() + + verify(trackerWrapper).track( + eq(RECEIPT_PRINT_CANCELED), + check { assertThat(it["source"]).isEqualTo("local") } + ) + } @Test - fun `when user clicks on send receipt button, then RECEIPT_EMAIL_TAPPED tracked`() = + fun `given wc core can generate receipts, when trackEmailReceiptTapped, then RECEIPT_EMAIL_TAPPED tracked`() = testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(true) + paymentsFlowTracker.trackEmailReceiptTapped() - verify(trackerWrapper).track(eq(RECEIPT_EMAIL_TAPPED), any()) + verify(trackerWrapper).track( + eq(RECEIPT_EMAIL_TAPPED), + check { assertThat(it["source"]).isEqualTo("backend") } + ) + } + + @Test + fun `given wc core can generate receipts, when trackPrintReceiptFailed, then RECEIPT_PRINT_FAILED tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(true) + + paymentsFlowTracker.trackPrintReceiptFailed() + + verify(trackerWrapper).track( + eq(AnalyticsEvent.RECEIPT_PRINT_FAILED), + check { assertThat(it["source"]).isEqualTo("backend") } + ) + } + + @Test + fun `given wc core can not generate receipts, when trackPrintReceiptFailed, then RECEIPT_PRINT_FAILED tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(false) + + paymentsFlowTracker.trackPrintReceiptFailed() + + verify(trackerWrapper).track( + eq(AnalyticsEvent.RECEIPT_PRINT_FAILED), + check { assertThat(it["source"]).isEqualTo("local") } + ) + } + + @Test + fun `given wc core can not generate receipts, when trackReceiptViewTapped, then RECEIPT_VIEW_TAPPED tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(false) + + paymentsFlowTracker.trackReceiptViewTapped( + properties = mapOf( + "key" to "value" + ) + ) + + verify(trackerWrapper).track( + eq(AnalyticsEvent.RECEIPT_VIEW_TAPPED), + check { + assertThat(it["source"]).isEqualTo("local") + assertThat(it["key"]).isEqualTo("value") + } + ) + } + + @Test + fun `given wc core can generate receipts, when trackReceiptViewTapped, then RECEIPT_VIEW_TAPPED tracked`() = + testBlocking { + whenever(paymentReceiptHelper.isWCCanGenerateReceipts()).thenReturn(true) + + paymentsFlowTracker.trackReceiptViewTapped( + properties = mapOf( + "key" to "value" + ) + ) + + verify(trackerWrapper).track( + eq(AnalyticsEvent.RECEIPT_VIEW_TAPPED), + check { + assertThat(it["source"]).isEqualTo("backend") + assertThat(it["key"]).isEqualTo("value") + } + ) } @Test From 05560ccebb2ee6b369a1220ffa20a29875d2e69c Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 16:40:39 +0100 Subject: [PATCH 144/160] Added a test of OrderDetailViewModel --- .../android/ui/orders/OrderDetailViewModelTest.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt index bdbafe61f95..9edc1eec8cf 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/OrderDetailViewModelTest.kt @@ -1243,7 +1243,8 @@ class OrderDetailViewModelTest : BaseUnitTest() { whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(false) whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false) - whenever(paymentReceiptHelper.getReceiptUrl(order.id)).thenReturn(Result.failure(Exception(""))) + val errorMessage = "error" + whenever(paymentReceiptHelper.getReceiptUrl(order.id)).thenReturn(Result.failure(Exception(errorMessage))) // WHEN viewModel.start() @@ -1252,6 +1253,9 @@ class OrderDetailViewModelTest : BaseUnitTest() { // THEN assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo(string.receipt_fetching_error) + verify(paymentsFlowTracker).trackReceiptUrlFetchingFails( + errorDescription = errorMessage + ) } @Test From d143c97c57c9581cd1affb66d1034534ac167fd6 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 31 Jan 2024 18:15:56 +0100 Subject: [PATCH 145/160] Updated release notes --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 091f9a955bd..68bab4ccc5a 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,7 +1,7 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] 17.2 ----- -- [**] Every order have a receipt now. The receipts can be shared via many apps installed on the phone [https://github.com/woocommerce/woocommerce-android/pull/10650] +- [**] (For users with WooCommerce version of 8.7+) Every order have a receipt now. The receipts can be shared via many apps installed on the phone [https://github.com/woocommerce/woocommerce-android/pull/10650] 17.1 ----- From 217f8d692d9e21dde790ce3bb095310e6b469a16 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Wed, 31 Jan 2024 17:31:47 -0500 Subject: [PATCH 146/160] Created a dialog fragment that can survive config changes. --- .../android/ui/dialog/WooDialogFragment.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt new file mode 100644 index 00000000000..83dd8d2c247 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt @@ -0,0 +1,60 @@ +package com.woocommerce.android.ui.dialog + +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class WooDialogFragment : DialogFragment() { + + private var dialogInteractionListener: DialogInteractionListener? = null + + fun setDialogInteractionListener(listener: DialogInteractionListener) { + dialogInteractionListener = listener + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + @Suppress("DEPRECATION") val params = requireArguments().getParcelable(ARG_DIALOG_PARAMS)!! + + val builder = MaterialAlertDialogBuilder(requireContext()) + .setCancelable(params.cancelable) + + params.titleId?.let { builder.setTitle(it) } + params.messageId?.let { builder.setMessage(it) } + params.positiveButtonId?.let { posId -> + builder.setPositiveButton(posId) { _, _ -> + dialogInteractionListener?.onPositiveButtonClicked() + } + } + params.negativeButtonId?.let { negId -> + builder.setNegativeButton(negId) { _, _ -> + dialogInteractionListener?.onNegativeButtonClicked() + } + } + params.neutralButtonId?.let { neutId -> + builder.setNeutralButton(neutId) { _, _ -> + dialogInteractionListener?.onNeutralButtonClicked() + } + } + + return builder.create() + } + + interface DialogInteractionListener { + fun onPositiveButtonClicked() + fun onNegativeButtonClicked() + fun onNeutralButtonClicked() + } + + companion object { + const val ARG_DIALOG_PARAMS = "dialog_params" + const val TAG = "WooDialogFragment" + fun newInstance(params: DialogParams): WooDialogFragment { + return WooDialogFragment().apply { + arguments = Bundle().apply { + putParcelable(ARG_DIALOG_PARAMS, params) + } + } + } + } +} From 1dc828047f9caeb65087da08727a85f5fb04fc83 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Wed, 31 Jan 2024 17:32:07 -0500 Subject: [PATCH 147/160] Added it to the MultiLiveEvent --- .../android/viewmodel/MultiLiveEvent.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt index 2c8dbcca8d8..8622d77c3de 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt @@ -12,7 +12,9 @@ import com.google.android.material.snackbar.Snackbar import com.woocommerce.android.R.string import com.woocommerce.android.model.UiString import com.woocommerce.android.support.help.HelpOrigin +import com.woocommerce.android.ui.dialog.DialogParams import com.woocommerce.android.ui.dialog.WooDialog +import com.woocommerce.android.ui.dialog.WooDialogFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import java.util.concurrent.atomic.AtomicBoolean @@ -129,6 +131,33 @@ open class MultiLiveEvent : MutableLiveData() { data class LaunchUrlInChromeTab(val url: String) : Event() + data class ShowDialogFragment( + @StringRes val titleId: Int? = null, + @StringRes val messageId: Int? = null, + @StringRes val positiveButtonId: Int? = null, + @StringRes val negativeButtonId: Int? = null, + @StringRes val neutralButtonId: Int? = null, + val positiveActionName: String? = null, + val negativeActionName: String? = null, + val neutralActionName: String? = null, + val cancelable: Boolean = true + ) : Event() { + fun showIn(fragmentManager: androidx.fragment.app.FragmentManager, listener: WooDialogFragment.DialogInteractionListener) { + val dialogParams = DialogParams( + titleId = titleId, + messageId = messageId, + positiveButtonId = positiveButtonId, + negativeButtonId = negativeButtonId, + neutralButtonId = neutralButtonId, + cancelable = cancelable + ) + val dialogFragment = WooDialogFragment.newInstance(dialogParams).apply { + setDialogInteractionListener(listener) + } + dialogFragment.show(fragmentManager, WooDialogFragment.TAG) + } + } + data class ShowDialog( @StringRes val titleId: Int? = null, @StringRes val messageId: Int? = null, From 3ed4e859ed5b1886e3862c06c041e54b206d7130 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Wed, 31 Jan 2024 17:32:20 -0500 Subject: [PATCH 148/160] Created dialog params for the dialog fragment --- .../woocommerce/android/ui/dialog/DialogParams.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt new file mode 100644 index 00000000000..e484ec323b0 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt @@ -0,0 +1,15 @@ +package com.woocommerce.android.ui.dialog + +import android.os.Parcelable +import androidx.annotation.StringRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DialogParams( + @StringRes val titleId: Int? = null, + @StringRes val messageId: Int? = null, + @StringRes val positiveButtonId: Int? = null, + @StringRes val negativeButtonId: Int? = null, + @StringRes val neutralButtonId: Int? = null, + val cancelable: Boolean = true +) : Parcelable From f7dccacea64e001af14b2fbb95d8de766f230bdc Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Wed, 31 Jan 2024 17:40:09 -0500 Subject: [PATCH 149/160] Fixed detekt error. --- .../com/woocommerce/android/viewmodel/MultiLiveEvent.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt index 8622d77c3de..6bc238d610b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt @@ -142,7 +142,10 @@ open class MultiLiveEvent : MutableLiveData() { val neutralActionName: String? = null, val cancelable: Boolean = true ) : Event() { - fun showIn(fragmentManager: androidx.fragment.app.FragmentManager, listener: WooDialogFragment.DialogInteractionListener) { + fun showIn( + fragmentManager: androidx.fragment.app.FragmentManager, + listener: WooDialogFragment.DialogInteractionListener + ) { val dialogParams = DialogParams( titleId = titleId, messageId = messageId, From e7e651ca19b60d8f346b760e2b25db9d78f50afd Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Wed, 31 Jan 2024 17:40:29 -0500 Subject: [PATCH 150/160] Implemented the show dialog fragment for variations. --- .../variations/VariationDetailFragment.kt | 26 ++++++++++++++++++- .../variations/VariationDetailViewModel.kt | 26 ++++++++++--------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt index 20f3f4a9d24..ccf61aacef7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt @@ -31,6 +31,8 @@ import com.woocommerce.android.model.VariantOption import com.woocommerce.android.ui.aztec.AztecEditorFragment import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver +import com.woocommerce.android.ui.dialog.WooDialogFragment +import com.woocommerce.android.ui.dialog.WooDialogFragment.DialogInteractionListener import com.woocommerce.android.ui.main.MainActivity.Companion.BackPressListener import com.woocommerce.android.ui.products.BaseProductEditorFragment import com.woocommerce.android.ui.products.ProductInventoryViewModel.InventoryData @@ -48,6 +50,7 @@ import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowActionSnackbar import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialogFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.widgets.CustomProgressDialog import com.woocommerce.android.widgets.SkeletonView @@ -61,7 +64,8 @@ class VariationDetailFragment : BaseFragment(R.layout.fragment_variation_detail), BackPressListener, OnGalleryImageInteractionListener, - MenuProvider { + MenuProvider, + DialogInteractionListener { companion object { private const val LIST_STATE_KEY = "list_state" const val KEY_VARIATION_DETAILS_RESULT = "key_variation_details_result" @@ -95,11 +99,18 @@ class VariationDetailFragment : _binding = FragmentVariationDetailBinding.bind(view) + reattachDialogInteractionListener() + requireActivity().addMenuProvider(this, viewLifecycleOwner) initializeViews(savedInstanceState) initializeViewModel() } + private fun reattachDialogInteractionListener() { + val dialogFragment = parentFragmentManager.findFragmentByTag(WooDialogFragment.TAG) as? WooDialogFragment + dialogFragment?.setDialogInteractionListener(this) + } + override fun onDestroyView() { skeletonView.hide() imageUploadErrorsSnackbar?.dismiss() @@ -276,12 +287,25 @@ class VariationDetailFragment : is ExitWithResult<*> -> navigateBackWithResult(KEY_VARIATION_DETAILS_RESULT, event.data) is ShowDialog -> event.showDialog() + is ShowDialogFragment -> event.showIn(parentFragmentManager, this) is Exit -> requireActivity().onBackPressedDispatcher.onBackPressed() else -> event.isHandled = false } } } + override fun onPositiveButtonClicked() { + viewModel.onDeleteVariationConfirmed() + } + + override fun onNegativeButtonClicked() { + viewModel.onDeleteVariationCancelled() + } + + override fun onNeutralButtonClicked() { + // no-op + } + private fun showVariationDetails(variation: ProductVariation) { if (variation.image == null && !viewModel.isUploadingImages()) { binding.imageGallery.hide() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt index f3d5941718f..8b502da15bd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt @@ -131,18 +131,7 @@ class VariationDetailViewModel @Inject constructor( fun onDeleteVariationClicked() { triggerEvent( - Event.ShowDialog( - positiveBtnAction = { _, _ -> - AnalyticsTracker.track( - AnalyticsEvent.PRODUCT_VARIATION_REMOVE_BUTTON_TAPPED, - mapOf(KEY_PRODUCT_ID to viewState.parentProduct?.remoteId) - ) - viewState = viewState.copy(isConfirmingDeletion = false) - deleteVariation() - }, - negativeBtnAction = { _, _ -> - viewState = viewState.copy(isConfirmingDeletion = false) - }, + Event.ShowDialogFragment( messageId = string.variation_confirm_delete, positiveButtonId = string.delete, negativeButtonId = string.cancel @@ -150,6 +139,19 @@ class VariationDetailViewModel @Inject constructor( ) } + fun onDeleteVariationConfirmed() { + AnalyticsTracker.track( + AnalyticsEvent.PRODUCT_VARIATION_REMOVE_BUTTON_TAPPED, + mapOf(KEY_PRODUCT_ID to viewState.parentProduct?.remoteId) + ) + viewState = viewState.copy(isConfirmingDeletion = false) + deleteVariation() + } + + fun onDeleteVariationCancelled() { + viewState = viewState.copy(isConfirmingDeletion = false) + } + fun onExit() { when { isUploadingImages() -> { From f7dea512e6e20565c5b7a96638c12db833d59bc0 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Wed, 31 Jan 2024 18:32:15 -0500 Subject: [PATCH 151/160] Disable refresh during search. --- .../com/woocommerce/android/ui/products/ProductListFragment.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index 084ef52d3a6..49866a9841a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -417,6 +417,9 @@ class ProductListFragment : one = R.string.product_selection_count_single ) } + new.isSearchActive?.takeIfNotEqualTo(old?.isSearchActive) { isSearchActive -> + binding.productsRefreshLayout.isEnabled = !isSearchActive + } } viewModel.productList.observe(viewLifecycleOwner) { From 113660e15c0b907d63bfb958eef4c2bcbf363aef Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 1 Feb 2024 05:21:13 +0100 Subject: [PATCH 152/160] Revert "Implemented the show dialog fragment for variations." This reverts commit e7e651ca19b60d8f346b760e2b25db9d78f50afd. --- .../variations/VariationDetailFragment.kt | 26 +------------------ .../variations/VariationDetailViewModel.kt | 26 +++++++++---------- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt index ccf61aacef7..20f3f4a9d24 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailFragment.kt @@ -31,8 +31,6 @@ import com.woocommerce.android.model.VariantOption import com.woocommerce.android.ui.aztec.AztecEditorFragment import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver -import com.woocommerce.android.ui.dialog.WooDialogFragment -import com.woocommerce.android.ui.dialog.WooDialogFragment.DialogInteractionListener import com.woocommerce.android.ui.main.MainActivity.Companion.BackPressListener import com.woocommerce.android.ui.products.BaseProductEditorFragment import com.woocommerce.android.ui.products.ProductInventoryViewModel.InventoryData @@ -50,7 +48,6 @@ import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowActionSnackbar import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog -import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialogFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.widgets.CustomProgressDialog import com.woocommerce.android.widgets.SkeletonView @@ -64,8 +61,7 @@ class VariationDetailFragment : BaseFragment(R.layout.fragment_variation_detail), BackPressListener, OnGalleryImageInteractionListener, - MenuProvider, - DialogInteractionListener { + MenuProvider { companion object { private const val LIST_STATE_KEY = "list_state" const val KEY_VARIATION_DETAILS_RESULT = "key_variation_details_result" @@ -99,18 +95,11 @@ class VariationDetailFragment : _binding = FragmentVariationDetailBinding.bind(view) - reattachDialogInteractionListener() - requireActivity().addMenuProvider(this, viewLifecycleOwner) initializeViews(savedInstanceState) initializeViewModel() } - private fun reattachDialogInteractionListener() { - val dialogFragment = parentFragmentManager.findFragmentByTag(WooDialogFragment.TAG) as? WooDialogFragment - dialogFragment?.setDialogInteractionListener(this) - } - override fun onDestroyView() { skeletonView.hide() imageUploadErrorsSnackbar?.dismiss() @@ -287,25 +276,12 @@ class VariationDetailFragment : is ExitWithResult<*> -> navigateBackWithResult(KEY_VARIATION_DETAILS_RESULT, event.data) is ShowDialog -> event.showDialog() - is ShowDialogFragment -> event.showIn(parentFragmentManager, this) is Exit -> requireActivity().onBackPressedDispatcher.onBackPressed() else -> event.isHandled = false } } } - override fun onPositiveButtonClicked() { - viewModel.onDeleteVariationConfirmed() - } - - override fun onNegativeButtonClicked() { - viewModel.onDeleteVariationCancelled() - } - - override fun onNeutralButtonClicked() { - // no-op - } - private fun showVariationDetails(variation: ProductVariation) { if (variation.image == null && !viewModel.isUploadingImages()) { binding.imageGallery.hide() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt index 8b502da15bd..f3d5941718f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModel.kt @@ -131,7 +131,18 @@ class VariationDetailViewModel @Inject constructor( fun onDeleteVariationClicked() { triggerEvent( - Event.ShowDialogFragment( + Event.ShowDialog( + positiveBtnAction = { _, _ -> + AnalyticsTracker.track( + AnalyticsEvent.PRODUCT_VARIATION_REMOVE_BUTTON_TAPPED, + mapOf(KEY_PRODUCT_ID to viewState.parentProduct?.remoteId) + ) + viewState = viewState.copy(isConfirmingDeletion = false) + deleteVariation() + }, + negativeBtnAction = { _, _ -> + viewState = viewState.copy(isConfirmingDeletion = false) + }, messageId = string.variation_confirm_delete, positiveButtonId = string.delete, negativeButtonId = string.cancel @@ -139,19 +150,6 @@ class VariationDetailViewModel @Inject constructor( ) } - fun onDeleteVariationConfirmed() { - AnalyticsTracker.track( - AnalyticsEvent.PRODUCT_VARIATION_REMOVE_BUTTON_TAPPED, - mapOf(KEY_PRODUCT_ID to viewState.parentProduct?.remoteId) - ) - viewState = viewState.copy(isConfirmingDeletion = false) - deleteVariation() - } - - fun onDeleteVariationCancelled() { - viewState = viewState.copy(isConfirmingDeletion = false) - } - fun onExit() { when { isUploadingImages() -> { From df916a05044997e507e881f1ee21c1798128c906 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 1 Feb 2024 05:21:13 +0100 Subject: [PATCH 153/160] Revert "Fixed detekt error." This reverts commit f7dccacea64e001af14b2fbb95d8de766f230bdc. --- .../com/woocommerce/android/viewmodel/MultiLiveEvent.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt index 6bc238d610b..8622d77c3de 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt @@ -142,10 +142,7 @@ open class MultiLiveEvent : MutableLiveData() { val neutralActionName: String? = null, val cancelable: Boolean = true ) : Event() { - fun showIn( - fragmentManager: androidx.fragment.app.FragmentManager, - listener: WooDialogFragment.DialogInteractionListener - ) { + fun showIn(fragmentManager: androidx.fragment.app.FragmentManager, listener: WooDialogFragment.DialogInteractionListener) { val dialogParams = DialogParams( titleId = titleId, messageId = messageId, From 6e23fb353e812d77d83872fb3819e3dc17b0cee1 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 1 Feb 2024 05:21:13 +0100 Subject: [PATCH 154/160] Revert "Created dialog params for the dialog fragment" This reverts commit 3ed4e859ed5b1886e3862c06c041e54b206d7130. --- .../woocommerce/android/ui/dialog/DialogParams.kt | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt deleted file mode 100644 index e484ec323b0..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/DialogParams.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.woocommerce.android.ui.dialog - -import android.os.Parcelable -import androidx.annotation.StringRes -import kotlinx.parcelize.Parcelize - -@Parcelize -data class DialogParams( - @StringRes val titleId: Int? = null, - @StringRes val messageId: Int? = null, - @StringRes val positiveButtonId: Int? = null, - @StringRes val negativeButtonId: Int? = null, - @StringRes val neutralButtonId: Int? = null, - val cancelable: Boolean = true -) : Parcelable From 94e32eec2538d9c678830d604af30f6013172ae5 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 1 Feb 2024 05:21:13 +0100 Subject: [PATCH 155/160] Revert "Added it to the MultiLiveEvent" This reverts commit 1dc828047f9caeb65087da08727a85f5fb04fc83. --- .../android/viewmodel/MultiLiveEvent.kt | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt index 8622d77c3de..2c8dbcca8d8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/viewmodel/MultiLiveEvent.kt @@ -12,9 +12,7 @@ import com.google.android.material.snackbar.Snackbar import com.woocommerce.android.R.string import com.woocommerce.android.model.UiString import com.woocommerce.android.support.help.HelpOrigin -import com.woocommerce.android.ui.dialog.DialogParams import com.woocommerce.android.ui.dialog.WooDialog -import com.woocommerce.android.ui.dialog.WooDialogFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event import java.util.concurrent.atomic.AtomicBoolean @@ -131,33 +129,6 @@ open class MultiLiveEvent : MutableLiveData() { data class LaunchUrlInChromeTab(val url: String) : Event() - data class ShowDialogFragment( - @StringRes val titleId: Int? = null, - @StringRes val messageId: Int? = null, - @StringRes val positiveButtonId: Int? = null, - @StringRes val negativeButtonId: Int? = null, - @StringRes val neutralButtonId: Int? = null, - val positiveActionName: String? = null, - val negativeActionName: String? = null, - val neutralActionName: String? = null, - val cancelable: Boolean = true - ) : Event() { - fun showIn(fragmentManager: androidx.fragment.app.FragmentManager, listener: WooDialogFragment.DialogInteractionListener) { - val dialogParams = DialogParams( - titleId = titleId, - messageId = messageId, - positiveButtonId = positiveButtonId, - negativeButtonId = negativeButtonId, - neutralButtonId = neutralButtonId, - cancelable = cancelable - ) - val dialogFragment = WooDialogFragment.newInstance(dialogParams).apply { - setDialogInteractionListener(listener) - } - dialogFragment.show(fragmentManager, WooDialogFragment.TAG) - } - } - data class ShowDialog( @StringRes val titleId: Int? = null, @StringRes val messageId: Int? = null, From 6bdba76c76667282f4add6c3930b063fb9bf6433 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 1 Feb 2024 05:21:14 +0100 Subject: [PATCH 156/160] Revert "Created a dialog fragment that can survive config changes." This reverts commit 217f8d692d9e21dde790ce3bb095310e6b469a16. --- .../android/ui/dialog/WooDialogFragment.kt | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt deleted file mode 100644 index 83dd8d2c247..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dialog/WooDialogFragment.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.woocommerce.android.ui.dialog - -import android.app.Dialog -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder - -class WooDialogFragment : DialogFragment() { - - private var dialogInteractionListener: DialogInteractionListener? = null - - fun setDialogInteractionListener(listener: DialogInteractionListener) { - dialogInteractionListener = listener - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - @Suppress("DEPRECATION") val params = requireArguments().getParcelable(ARG_DIALOG_PARAMS)!! - - val builder = MaterialAlertDialogBuilder(requireContext()) - .setCancelable(params.cancelable) - - params.titleId?.let { builder.setTitle(it) } - params.messageId?.let { builder.setMessage(it) } - params.positiveButtonId?.let { posId -> - builder.setPositiveButton(posId) { _, _ -> - dialogInteractionListener?.onPositiveButtonClicked() - } - } - params.negativeButtonId?.let { negId -> - builder.setNegativeButton(negId) { _, _ -> - dialogInteractionListener?.onNegativeButtonClicked() - } - } - params.neutralButtonId?.let { neutId -> - builder.setNeutralButton(neutId) { _, _ -> - dialogInteractionListener?.onNeutralButtonClicked() - } - } - - return builder.create() - } - - interface DialogInteractionListener { - fun onPositiveButtonClicked() - fun onNegativeButtonClicked() - fun onNeutralButtonClicked() - } - - companion object { - const val ARG_DIALOG_PARAMS = "dialog_params" - const val TAG = "WooDialogFragment" - fun newInstance(params: DialogParams): WooDialogFragment { - return WooDialogFragment().apply { - arguments = Bundle().apply { - putParcelable(ARG_DIALOG_PARAMS, params) - } - } - } - } -} From 6898b8354fdf0c3d1ce39252bb7fb40f5bdc02e4 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 Feb 2024 10:55:26 +0100 Subject: [PATCH 157/160] Reverted string renaming to avoid unnecessary changes --- .../payments/cardreader/payment/CardReaderPaymentViewModel.kt | 4 ++-- .../ui/payments/receipt/preview/ReceiptPreviewViewModel.kt | 2 +- WooCommerce/src/main/res/values-ar/strings.xml | 2 +- WooCommerce/src/main/res/values-de/strings.xml | 2 +- WooCommerce/src/main/res/values-es/strings.xml | 2 +- WooCommerce/src/main/res/values-fr/strings.xml | 2 +- WooCommerce/src/main/res/values-he/strings.xml | 2 +- WooCommerce/src/main/res/values-id/strings.xml | 2 +- WooCommerce/src/main/res/values-it/strings.xml | 2 +- WooCommerce/src/main/res/values-ja/strings.xml | 2 +- WooCommerce/src/main/res/values-ko/strings.xml | 2 +- WooCommerce/src/main/res/values-nl/strings.xml | 2 +- WooCommerce/src/main/res/values-pt-rBR/strings.xml | 2 +- WooCommerce/src/main/res/values-ru/strings.xml | 2 +- WooCommerce/src/main/res/values-sv/strings.xml | 2 +- WooCommerce/src/main/res/values-tr/strings.xml | 2 +- WooCommerce/src/main/res/values-zh-rCN/strings.xml | 2 +- WooCommerce/src/main/res/values-zh-rTW/strings.xml | 2 +- WooCommerce/src/main/res/values/strings.xml | 2 +- .../ui/payments/cardreader/CardReaderPaymentViewModelTest.kt | 2 +- .../payments/receipt/preview/ReceiptPreviewViewModelTest.kt | 2 +- 21 files changed, 22 insertions(+), 22 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index fc52388c3f9..de62a7ab603 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -742,7 +742,7 @@ class CardReaderPaymentViewModel } is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { tracker.trackPaymentsReceiptSharingFailed(sharingResult) - triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_app_to_share_not_found)) + triggerEvent(ShowSnackbar(R.string.card_reader_payment_email_client_not_found)) } PaymentReceiptShare.ReceiptShareResult.Success -> { // no-op @@ -758,7 +758,7 @@ class CardReaderPaymentViewModel fun onEmailActivityNotFound() { tracker.trackEmailReceiptFailed() - triggerEvent(ShowSnackbarInDialog(R.string.card_reader_payment_receipt_app_to_share_not_found)) + triggerEvent(ShowSnackbarInDialog(R.string.card_reader_payment_email_client_not_found)) } fun onPrintResult(result: PrintJobResult) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index ed7aa503ec5..b74846b8e71 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -67,7 +67,7 @@ class ReceiptPreviewViewModel } is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) - triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_app_to_share_not_found)) + triggerEvent(ShowSnackbar(string.card_reader_payment_email_client_not_found)) } PaymentReceiptShare.ReceiptShareResult.Success -> { // no-op diff --git a/WooCommerce/src/main/res/values-ar/strings.xml b/WooCommerce/src/main/res/values-ar/strings.xml index 76aedcc43e9..cc49beffa7f 100644 --- a/WooCommerce/src/main/res/values-ar/strings.xml +++ b/WooCommerce/src/main/res/values-ar/strings.xml @@ -1910,7 +1910,7 @@ Language: ar سمات المجموعات تشغيل بلوتوث الجهاز المحمول حدث خطأ في أثناء إحضار الطلب. قد تكون حالة الطلب الموجودة في التطبيق قديمة. - لا يمكن اكتشاف تطبيق عميل البريد الإلكتروني الخاص بك + لا يمكن اكتشاف تطبيق عميل البريد الإلكتروني الخاص بك إيصالك من %s تحديث الطلب تحديث حالة التطبيق diff --git a/WooCommerce/src/main/res/values-de/strings.xml b/WooCommerce/src/main/res/values-de/strings.xml index cf4d312a3a1..f069ba46c0f 100644 --- a/WooCommerce/src/main/res/values-de/strings.xml +++ b/WooCommerce/src/main/res/values-de/strings.xml @@ -1910,7 +1910,7 @@ Language: de Variantenattribute Aktiviere Bluetooth auf dem Mobilgerät Fehler beim Abrufen der Bestellung Der Bestellstatus in der App ist eventuell nicht aktuell. - E-Mail-Client-App wird nicht erkannt. + E-Mail-Client-App wird nicht erkannt. Dein Beleg vom %s Bestellung aktualisieren App-Status aktualisieren diff --git a/WooCommerce/src/main/res/values-es/strings.xml b/WooCommerce/src/main/res/values-es/strings.xml index fe0e0d5bd54..8e32a914850 100644 --- a/WooCommerce/src/main/res/values-es/strings.xml +++ b/WooCommerce/src/main/res/values-es/strings.xml @@ -1910,7 +1910,7 @@ Language: es Atributos de variaciones Activa Bluetooth en el dispositivo móvil Error al recuperar el pedido. El estado del pedido en la aplicación podría estar desactualizado. - No se pudo detectar una aplicación cliente de correo electrónico + No se pudo detectar una aplicación cliente de correo electrónico Tu recibo de %s Actualizando pedido Actualizando estado en la aplicación diff --git a/WooCommerce/src/main/res/values-fr/strings.xml b/WooCommerce/src/main/res/values-fr/strings.xml index f18596537c6..2fdba76735a 100644 --- a/WooCommerce/src/main/res/values-fr/strings.xml +++ b/WooCommerce/src/main/res/values-fr/strings.xml @@ -1910,7 +1910,7 @@ Language: fr Attributs des variantes Activer le Bluetooth de l’appareil mobile Erreur lors de l’extraction de la commande. L’état de la commande dans l’application est peut-être obsolète. - Aucune application de messagerie détectée + Aucune application de messagerie détectée Votre reçu de %s Actualisation de la commande Mise à jour de l’état de l’application diff --git a/WooCommerce/src/main/res/values-he/strings.xml b/WooCommerce/src/main/res/values-he/strings.xml index 8622401ba37..4e5a67434b2 100644 --- a/WooCommerce/src/main/res/values-he/strings.xml +++ b/WooCommerce/src/main/res/values-he/strings.xml @@ -1910,7 +1910,7 @@ Language: he_IL מאפייני סוגים יש להפעיל את חיבור ה-Bluetooth של המכשיר הנייד שגיאה בהבאת ההזמנה. ייתכן שמצב ההזמנה באפליקציה לא מעודכן. - לא ניתן לאתר את האפליקציה של שירות האימייל שלך + לא ניתן לאתר את האפליקציה של שירות האימייל שלך הקבלה שלך מאת %s מרענן את ההזמנה מעדכן את מצב האפליקציה diff --git a/WooCommerce/src/main/res/values-id/strings.xml b/WooCommerce/src/main/res/values-id/strings.xml index b7315da284f..fa29a8b2c73 100644 --- a/WooCommerce/src/main/res/values-id/strings.xml +++ b/WooCommerce/src/main/res/values-id/strings.xml @@ -1910,7 +1910,7 @@ Language: id Atribut variasi Aktifkan bluetooth perangkat seluler Terjadi error saat mengambil pesanan. Status pesanan pada aplikasi mungkin telah usang. - Tidak dapat mendeteksi aplikasi klien email Anda + Tidak dapat mendeteksi aplikasi klien email Anda Tanda terima Anda dari %s Menyegarkan pesanan Memperbarui status aplikasi diff --git a/WooCommerce/src/main/res/values-it/strings.xml b/WooCommerce/src/main/res/values-it/strings.xml index cae8e5ca689..4f1d803552e 100644 --- a/WooCommerce/src/main/res/values-it/strings.xml +++ b/WooCommerce/src/main/res/values-it/strings.xml @@ -1910,7 +1910,7 @@ Language: it Attributi della variante Attiva il Bluetooth del dispositivo mobile Errore durante il recupero dell\'ordine. Lo stato nell\'ordine dell\'app potrebbe essere obsoleto. - Impossibile rilevare il client e-mail della tua app + Impossibile rilevare il client e-mail della tua app La tua ricevuta da %s Aggiornamento dell\'ordine Aggiornamento dello stato dell\'app diff --git a/WooCommerce/src/main/res/values-ja/strings.xml b/WooCommerce/src/main/res/values-ja/strings.xml index 57f251429ea..ce4d1990c3b 100644 --- a/WooCommerce/src/main/res/values-ja/strings.xml +++ b/WooCommerce/src/main/res/values-ja/strings.xml @@ -1910,7 +1910,7 @@ Language: ja_JP バリエーション属性 モバイルデバイスの Bluetooth をオンにする 注文を取得する際にエラーが発生しました。 アプリの注文状態が古い可能性があります。 - メールクライアントアプリを検出できません + メールクライアントアプリを検出できません %s のレシート 注文を更新する アプリの状態を更新する diff --git a/WooCommerce/src/main/res/values-ko/strings.xml b/WooCommerce/src/main/res/values-ko/strings.xml index f445739f0b5..70f0935b61d 100644 --- a/WooCommerce/src/main/res/values-ko/strings.xml +++ b/WooCommerce/src/main/res/values-ko/strings.xml @@ -1910,7 +1910,7 @@ Language: ko_KR 변형 속성 모바일 장치 블루투스 켜기 주문을 가져오는 중 오류가 발생했습니다. 앱의 주문 상태가 기한이 지났을 수 있습니다. - 이메일 클라이언트 앱을 감지할 수 없습니다. + 이메일 클라이언트 앱을 감지할 수 없습니다. %s에서 받은 영수증 주문 새로 고치기 앱 상태 업데이트하기 diff --git a/WooCommerce/src/main/res/values-nl/strings.xml b/WooCommerce/src/main/res/values-nl/strings.xml index b60a6ba5886..e53a15e7869 100644 --- a/WooCommerce/src/main/res/values-nl/strings.xml +++ b/WooCommerce/src/main/res/values-nl/strings.xml @@ -1903,7 +1903,7 @@ Language: nl Variatie-eigenschappen Zet de Bluetooth aan op je mobiele apparaat Fout bij ophalen bestelling. De bestellingsstatus in de app kan verouderd zijn. - Detectie van mailclient-app mislukt + Detectie van mailclient-app mislukt Je kwitantie van %s Bestelling aan het vernieuwen… Status in de app aan het bijwerken… diff --git a/WooCommerce/src/main/res/values-pt-rBR/strings.xml b/WooCommerce/src/main/res/values-pt-rBR/strings.xml index ecde05b45f3..09d637b857d 100644 --- a/WooCommerce/src/main/res/values-pt-rBR/strings.xml +++ b/WooCommerce/src/main/res/values-pt-rBR/strings.xml @@ -1910,7 +1910,7 @@ Language: pt_BR Atributos das variações Ativar o Bluetooth do dispositivo móvel Erro ao buscar o pedido. O status do pedido pode estar desatualizado no aplicativo. - Não é possível detectar seu aplicativo cliente de e-mail + Não é possível detectar seu aplicativo cliente de e-mail Seu recibo de %s Atualizando pedido Atualizando estado do aplicativo diff --git a/WooCommerce/src/main/res/values-ru/strings.xml b/WooCommerce/src/main/res/values-ru/strings.xml index cc35e4e790d..a04d66d7e2f 100644 --- a/WooCommerce/src/main/res/values-ru/strings.xml +++ b/WooCommerce/src/main/res/values-ru/strings.xml @@ -1910,7 +1910,7 @@ Language: ru Атрибуты вариантов Включите Bluetooth на мобильном устройстве Ошибка при загрузке заказа. Состояние заказа в приложении могло устареть. - Не удалось определить ваш почтовый клиент + Не удалось определить ваш почтовый клиент Ваша квитанция от %s Обновление заказа Обновление состояния приложения diff --git a/WooCommerce/src/main/res/values-sv/strings.xml b/WooCommerce/src/main/res/values-sv/strings.xml index 9e4e1cf2148..977cd03c403 100644 --- a/WooCommerce/src/main/res/values-sv/strings.xml +++ b/WooCommerce/src/main/res/values-sv/strings.xml @@ -1917,7 +1917,7 @@ Language: sv_SE Börja sälja idag genom att lägga till din första produkt i butiken. Variationsattribut Aktivera Bluetooth på den mobila enheten - Lyckas inte hitta ditt e-postprogram + Lyckas inte hitta ditt e-postprogram Uppdaterar beställning Uppdaterar appstatusen Det gick inte att hämta beställningen. Beställningens status i appen kan vara föråldrad. diff --git a/WooCommerce/src/main/res/values-tr/strings.xml b/WooCommerce/src/main/res/values-tr/strings.xml index bf6d75a1ca7..70eec362196 100644 --- a/WooCommerce/src/main/res/values-tr/strings.xml +++ b/WooCommerce/src/main/res/values-tr/strings.xml @@ -1910,7 +1910,7 @@ Language: tr Varyasyon nitelikleri. Mobil cihazda Bluetooth\'u açın Sipariş alınırken hata oluştu Uygulamadaki sipariş durumu eski olabilir. - E-posta istemcisi uygulamanızı tespit edemiyoruz + E-posta istemcisi uygulamanızı tespit edemiyoruz %s makbuzunuz Sipariş yenileniyor Uygulama durumu güncelleniyor diff --git a/WooCommerce/src/main/res/values-zh-rCN/strings.xml b/WooCommerce/src/main/res/values-zh-rCN/strings.xml index 1a835ed6868..2094348f61f 100644 --- a/WooCommerce/src/main/res/values-zh-rCN/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rCN/strings.xml @@ -1909,7 +1909,7 @@ Language: zh_CN 添加产品 变体属性 打开移动设备蓝牙 - 未侦测到您的电子邮件客户端应用 + 未侦测到您的电子邮件客户端应用 来自 %s 的收据 刷新订单 更新应用程序状态 diff --git a/WooCommerce/src/main/res/values-zh-rTW/strings.xml b/WooCommerce/src/main/res/values-zh-rTW/strings.xml index 2dcd4dcbef0..0c9892b1cae 100644 --- a/WooCommerce/src/main/res/values-zh-rTW/strings.xml +++ b/WooCommerce/src/main/res/values-zh-rTW/strings.xml @@ -1910,7 +1910,7 @@ Language: zh_TW 款式屬性 開啟行動裝置的藍牙 擷取訂單時發生錯誤。 應用程式中的訂單狀態可能已過期。 - 無法偵測電子郵件用戶端應用程式 + 無法偵測電子郵件用戶端應用程式 你的 %s 收據 正在重新整理訂單 正在更新應用程式狀態 diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 7a855966ae3..7ef7a5426aa 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -1507,7 +1507,7 @@ In-Person Payment for Order #%1$s for %2$s blog_id %3$s. Your receipt from %s Thank you for your purchase! Click the link below for your payment receipt.\n\n%s - Unable to detect any application to which the receipt can be shared + Unable to detect any application to which the receipt can be shared Unable to download the receipt Unable to store the receipt Error fetching order. Order state in the app might be outdated. diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index a9cbfa61c5f..d735c8f6aa2 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -2440,7 +2440,7 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onSecondaryActionClicked.invoke() assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo( - R.string.card_reader_payment_receipt_app_to_share_not_found + R.string.card_reader_payment_email_client_not_found ) verify(tracker).trackPaymentsReceiptSharingFailed(sharing) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt index 3a8d0fa2a80..b4e002726cd 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelTest.kt @@ -147,7 +147,7 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() { // THEN assertThat((viewModel.event.value as MultiLiveEvent.Event.ShowSnackbar).message).isEqualTo( - R.string.card_reader_payment_receipt_app_to_share_not_found + R.string.card_reader_payment_email_client_not_found ) verify(paymentsFlowTracker).trackPaymentsReceiptSharingFailed( sharing From c283af71e87b8160fe9955471c0e3b9c332e92f9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 Feb 2024 11:00:59 +0100 Subject: [PATCH 158/160] Better wording for release notes so its clear for the release manager --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 68bab4ccc5a..f72f48c6e92 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,7 +1,7 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] 17.2 ----- -- [**] (For users with WooCommerce version of 8.7+) Every order have a receipt now. The receipts can be shared via many apps installed on the phone [https://github.com/woocommerce/woocommerce-android/pull/10650] +- [**] [Available for users with WooCommerce version of 8.7+, which is not released yet] Every order have a receipt now. The receipts can be shared via many apps installed on the phone [https://github.com/woocommerce/woocommerce-android/pull/10650] 17.1 ----- From 169758b11d516d2b4547fd9b0189f95fdd61a9c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:35:15 +0000 Subject: [PATCH 159/160] Bump androidx.core:core-ktx from 1.8.0 to 1.12.0 Bumps androidx.core:core-ktx from 1.8.0 to 1.12.0. --- updated-dependencies: - dependency-name: androidx.core:core-ktx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 55a46108b90..06dba49ced5 100644 --- a/build.gradle +++ b/build.gradle @@ -108,7 +108,7 @@ ext { aztecVersion = 'v1.3.45' flipperVersion = '0.176.1' stateMachineVersion = '0.2.0' - coreKtxVersion = '1.8.0' + coreKtxVersion = '1.12.0' appCompatVersion = '1.4.2' materialVersion = '1.6.1' hiltJetpackVersion = '1.0.0' From bd89dfb4c2229dc1e227daf8638acb3358e08666 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 2 Feb 2024 09:08:18 +0100 Subject: [PATCH 160/160] Updated fluxc release --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 55a46108b90..47c8eb55add 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2948-235834ae9467ae3ca367a4b13c4f9c6aea0f31bf' + fluxCVersion = 'trunk-b34cc5eed609ca5274fbb442991f657f2380de17' glideVersion = '4.13.2' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0'