diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt index 02c195b0d0bd..651a2ccea90c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.datastore.DataStoreType.ANALYTICS_UI_CACHE import com.woocommerce.android.datastore.DataStoreType.COUPONS import com.woocommerce.android.datastore.DataStoreType.DASHBOARD_STATS import com.woocommerce.android.datastore.DataStoreType.LAST_UPDATE +import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_ADDRESS import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_CONFIGURATION import com.woocommerce.android.datastore.DataStoreType.TOP_PERFORMER_PRODUCTS import com.woocommerce.android.datastore.DataStoreType.TRACKER @@ -179,4 +180,24 @@ class DataStoreModule { }, scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) + + @Provides + @Singleton + @DataStoreQualifier(SHIPPING_LABEL_ADDRESS) + fun provideShippingLabelAddressDataStore( + appContext: Context, + crashLogging: CrashLogging, + @AppCoroutineScope appCoroutineScope: CoroutineScope + ) = PreferenceDataStoreFactory.create( + produceFile = { + appContext.preferencesDataStoreFile("shipping_label_address") + }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent( + "Corrupted data store. DataStore Type: ${SHIPPING_LABEL_ADDRESS.name}" + ) + emptyPreferences() + }, + scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) + ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt index 1a0d37df1456..6295e0746180 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt @@ -8,5 +8,6 @@ enum class DataStoreType { TOP_PERFORMER_PRODUCTS, COUPONS, LAST_UPDATE, - SHIPPING_LABEL_CONFIGURATION + SHIPPING_LABEL_CONFIGURATION, + SHIPPING_LABEL_ADDRESS } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveOriginAddresses.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveOriginAddresses.kt deleted file mode 100644 index 7546f6b03b5a..000000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveOriginAddresses.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.woocommerce.android.ui.orders.wooshippinglabels - -import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -class ObserveOriginAddresses @Inject constructor() { - @Suppress("MagicNumber") - suspend operator fun invoke(): Flow> { - delay(200) - val addresses = listOf( - OriginShippingAddress( - firstName = "", - lastName = "", - company = "Shut up and sip", - phone = "55512345", - address1 = "60 29TH ST PMB 343", - address2 = "", - city = "SAN FRANCISCO", - postcode = "94110-4929", - email = "alejandro.torres@mail.com", - country = "US", - state = "CA", - id = "store_details", - isDefault = true, - isVerified = true - ), - OriginShippingAddress( - firstName = "first name", - lastName = "last name", - company = "Company", - phone = "", - address1 = "Another huge address that should be truncated", - address2 = "", - city = "Oakland", - postcode = "", - email = "email", - country = "USA", - state = "California", - id = "id_1", - isDefault = false, - isVerified = true - ), - OriginShippingAddress( - firstName = "first name", - lastName = "last name", - company = "Company", - phone = "", - address1 = "Small address", - address2 = "", - city = "Palo Alto", - postcode = "", - email = "email", - country = "USA", - state = "California", - id = "id_1", - isDefault = false, - isVerified = true - ) - ) - return flowOf(addresses) - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptions.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptions.kt index 175335705237..93824f4f964a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptions.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptions.kt @@ -1,22 +1,23 @@ package com.woocommerce.android.ui.orders.wooshippinglabels -import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.ShippingLabelsDataStore +import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingConfigurationDataStore import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.transformLatest import javax.inject.Inject class ObserveStoreOptions @Inject constructor( - val dataStore: ShippingLabelsDataStore, - val fetchAccountSettings: FetchAccountSettings, + private val configurationDataStore: WooShippingConfigurationDataStore, + private val fetchAccountSettings: FetchAccountSettings, ) { private var isFirstValue = true @OptIn(ExperimentalCoroutinesApi::class) // We will use data store as the source of truth and after the first emission we will refresh the values async. - operator fun invoke() = dataStore.observeStoreOptions().transformLatest { options -> + operator fun invoke() = configurationDataStore.observeStoreOptions().transformLatest { options -> when { isFirstValue && options == null -> { // If there is no cached data, refresh the store options before emitting any value + isFirstValue = false if (fetchAccountSettings().isFailure) { // We will use null as not available emit(null) @@ -25,6 +26,7 @@ class ObserveStoreOptions @Inject constructor( isFirstValue && options != null -> { // If there is cached data, emit cached values and refresh the store options async + isFirstValue = false emit(options) if (fetchAccountSettings().isFailure) { emit(null) @@ -33,6 +35,5 @@ class ObserveStoreOptions @Inject constructor( else -> emit(options) } - isFirstValue = false } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt index 7463c1a6934e..b2731736b264 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt @@ -46,6 +46,10 @@ import com.woocommerce.android.extensions.appendWithIfNotEmpty import com.woocommerce.android.model.Address import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.orders.wooshippinglabels.address.AddressSectionLandscape +import com.woocommerce.android.ui.orders.wooshippinglabels.address.AddressSectionPortrait +import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipFrom +import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipTo import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.util.StringUtils import kotlinx.coroutines.launch diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt index 15220c02cbc5..dd55c39225de 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt @@ -59,6 +59,8 @@ import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.DataAvailable import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.NotSelected +import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipFrom +import com.woocommerce.android.ui.orders.wooshippinglabels.address.getShipTo import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.packages.ui.PackageData import com.woocommerce.android.ui.orders.wooshippinglabels.rates.ui.ShippingRateUI diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt index d27739f1309d..1eeb961476d3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModel.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.model.Order import com.woocommerce.android.ui.orders.details.OrderDetailRepository import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.DataAvailable import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PackageSelectionState.NotSelected +import com.woocommerce.android.ui.orders.wooshippinglabels.address.ObserveOriginAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippableItemModel import com.woocommerce.android.ui.orders.wooshippinglabels.models.StoreOptionsModel @@ -205,7 +206,7 @@ class WooShippingLabelCreationViewModel @Inject constructor( private suspend fun getShippingAddresses() { order.combine(observeOriginAddresses()) { order, originAddresses -> - if (order != null && originAddresses.isNotEmpty()) { + if (order != null && !originAddresses.isNullOrEmpty()) { val selectedOriginAddress = getSelectedOriginAddress(originAddresses) WooShippingAddresses( shipFrom = selectedOriginAddress, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/AddressSection.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/AddressSection.kt similarity index 96% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/AddressSection.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/AddressSection.kt index a1d8610331c1..c59c721b07b1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/AddressSection.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/AddressSection.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.orders.wooshippinglabels +package com.woocommerce.android.ui.orders.wooshippinglabels.address import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.IntrinsicSize @@ -30,12 +30,17 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension import com.woocommerce.android.R import com.woocommerce.android.model.Address import com.woocommerce.android.model.AmbiguousLocation import com.woocommerce.android.model.Location import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.orders.wooshippinglabels.RoundedCornerBoxWithBorder +import com.woocommerce.android.ui.orders.wooshippinglabels.VerticalDivider +import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import com.woocommerce.android.ui.orders.wooshippinglabels.toShippingFromString @Composable @Suppress("DestructuringDeclarationWithTooManyEntries", "UnusedParameter") @@ -87,7 +92,7 @@ internal fun AddressSectionPortrait( top.linkTo(shipFromLabel.top) start.linkTo(shipFromLabel.end) end.linkTo(endBarrier) - width = androidx.constraintlayout.compose.Dimension.fillToConstraints + width = Dimension.fillToConstraints } .padding( top = dimensionResource(R.dimen.major_100), @@ -160,7 +165,7 @@ internal fun AddressSectionPortrait( top.linkTo(shipToLabel.top) start.linkTo(barrier) end.linkTo(shipToEdit.start) - width = androidx.constraintlayout.compose.Dimension.fillToConstraints + width = Dimension.fillToConstraints } .padding( top = dimensionResource(R.dimen.major_100), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/FetchOriginAddresses.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/FetchOriginAddresses.kt new file mode 100644 index 000000000000..e452f4688df5 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/FetchOriginAddresses.kt @@ -0,0 +1,29 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.address + +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import com.woocommerce.android.ui.orders.wooshippinglabels.networking.WooShippingLabelRepository +import javax.inject.Inject + +class FetchOriginAddresses @Inject constructor( + private val shippingRepository: WooShippingLabelRepository, + private val selectedSite: SelectedSite +) { + suspend operator fun invoke(): Result> { + return selectedSite.getOrNull()?.let { + val response = shippingRepository.fetchOriginAddresses(it) + val result = response.model + when { + response.isError.not() && !result.isNullOrEmpty() -> { + Result.success(result) + } + + else -> { + val message = + response.error?.message ?: if (result.isNullOrEmpty()) "Empty result" else "Unknown error" + Result.failure(Exception(message)) + } + } + } ?: Result.failure(Exception("No site selected")) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveOriginAddresses.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveOriginAddresses.kt new file mode 100644 index 000000000000..1051fb210c6c --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveOriginAddresses.kt @@ -0,0 +1,39 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.address + +import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingAddressDataStore +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.transformLatest +import javax.inject.Inject + +class ObserveOriginAddresses @Inject constructor( + private val addressDataStore: WooShippingAddressDataStore, + private val fetchOriginAddresses: FetchOriginAddresses +) { + private var isFirstValue = true + + @OptIn(ExperimentalCoroutinesApi::class) + // We will use data store as the source of truth and after the first emission we will refresh the values async. + operator fun invoke() = addressDataStore.observeOriginAddresses().transformLatest { addresses -> + when { + isFirstValue && addresses == null -> { + // If there is no cached data, refresh the origin addresses before emitting any value + isFirstValue = false + if (fetchOriginAddresses().isFailure) { + // We will use null as not available + emit(null) + } + } + + isFirstValue && addresses != null -> { + // If there is cached data, emit cached values and refresh the origin addresses async + isFirstValue = false + emit(addresses) + if (fetchOriginAddresses().isFailure) { + emit(null) + } + } + + else -> emit(addresses) + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/WooShippingAddressDataStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/WooShippingAddressDataStore.kt new file mode 100644 index 000000000000..e9bd0484432c --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/WooShippingAddressDataStore.kt @@ -0,0 +1,39 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.datasource + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.woocommerce.android.datastore.DataStoreQualifier +import com.woocommerce.android.datastore.DataStoreType +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class WooShippingAddressDataStore @Inject constructor( + @DataStoreQualifier(DataStoreType.SHIPPING_LABEL_ADDRESS) private val dataStore: DataStore, + private val gson: Gson, + private val selectedSite: SelectedSite +) { + private fun getOriginAddressesKey() = "${selectedSite.getOrNull()?.siteId ?: ""}OriginAddresses" + + fun observeOriginAddresses(): Flow?> { + val typeToken = object : TypeToken>() {}.type + return dataStore.data.map { prefs -> + val storeOptions = prefs[stringPreferencesKey(getOriginAddressesKey())] + runCatching { + gson.fromJson>(storeOptions, typeToken) + }.getOrNull() + } + } + + suspend fun saveOriginAddresses(addresses: List) { + dataStore.edit { preferences -> + preferences[stringPreferencesKey(getOriginAddressesKey())] = gson.toJson(addresses) + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/ShippingLabelsDataStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/WooShippingConfigurationDataStore.kt similarity index 96% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/ShippingLabelsDataStore.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/WooShippingConfigurationDataStore.kt index 674158f8329c..49f81ee29b8f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/ShippingLabelsDataStore.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/datasource/WooShippingConfigurationDataStore.kt @@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject -class ShippingLabelsDataStore @Inject constructor( +class WooShippingConfigurationDataStore @Inject constructor( @DataStoreQualifier(DataStoreType.SHIPPING_LABEL_CONFIGURATION) private val dataStore: DataStore, private val gson: Gson, private val selectedSite: SelectedSite diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/DTOs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/DTOs.kt index c193a6dc0b45..0414574fad8f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/DTOs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/DTOs.kt @@ -76,3 +76,21 @@ data class ShippingRatePurchaseResponseDTO( val deliveryDateGuaranteed: Boolean, val deliveryDate: String? ) + +data class OriginAddressDTO( + val id: String? = null, + @SerializedName("address_1") val address: String? = null, + @SerializedName("address_2") val address2: String? = null, + val city: String? = null, + val company: String? = null, + val country: String? = null, + val name: String? = null, + val phone: String? = null, + val postcode: String? = null, + val state: String? = null, + val email: String? = null, + @SerializedName("first_name") val firstName: String? = null, + @SerializedName("last_name") val lastName: String? = null, + @SerializedName("is_verified") val isVerified: Boolean = false, + @SerializedName("default_address") val defaultAddress: Boolean = false, +) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRepository.kt index 08986dca8cb8..a88521b9888a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRepository.kt @@ -1,7 +1,8 @@ package com.woocommerce.android.ui.orders.wooshippinglabels.networking import com.woocommerce.android.model.Address -import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.ShippingLabelsDataStore +import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingAddressDataStore +import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingConfigurationDataStore import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.PurchasedLabelData import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippingLabelStatus @@ -14,7 +15,8 @@ import javax.inject.Inject class WooShippingLabelRepository @Inject constructor( private val restClient: WooShippingLabelRestClient, private val mapper: WooShippingNetworkingMapper, - private val dataStore: ShippingLabelsDataStore + private val configurationDataStore: WooShippingConfigurationDataStore, + private val addressDataStore: WooShippingAddressDataStore ) { suspend fun fetchShippingLabelPrinting( site: SiteModel, @@ -34,7 +36,9 @@ class WooShippingLabelRepository @Inject constructor( .also { response -> response.model ?.takeIf { response.isError.not() } - ?.let { dataStore.saveStoreOptions(it) } + ?.let { + configurationDataStore.saveStoreOptions(it) + } } suspend fun fetchPurchasedShippingLabels( @@ -90,4 +94,16 @@ class WooShippingLabelRepository @Inject constructor( markOrderComplete = lastOrderComplete ).asWooResult { mapper(it) } } + + suspend fun fetchOriginAddresses( + site: SiteModel + ) = restClient.fetchOriginAddresses(site = site) + .asWooResult { mapper(it) } + .also { response -> + response.model + ?.takeIf { response.isError.not() } + ?.let { + addressDataStore.saveOriginAddresses(it) + } + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRestClient.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRestClient.kt index 9eed218d8d66..56f992051538 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRestClient.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingLabelRestClient.kt @@ -107,4 +107,15 @@ class WooShippingLabelRestClient @Inject constructor( clazz = PurchasedShippingLabelResponseDTO::class.java, ).toWooPayload() } + + suspend fun fetchOriginAddresses( + site: SiteModel, + ): WooPayload> { + val url = "/wcshipping/v1/origin-addresses/" + return wooNetwork.executeGetGsonRequest( + site = site, + path = url, + clazz = Array::class.java, + ).toWooPayload() + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingNetworkingMapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingNetworkingMapper.kt index 2dcca6b10fb1..5f86b0bad2f8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingNetworkingMapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/networking/WooShippingNetworkingMapper.kt @@ -102,6 +102,27 @@ class WooShippingNetworkingMapper @Inject constructor( ) } + operator fun invoke(addressListDTO: Array): List { + return addressListDTO.map { + OriginShippingAddress( + id = it.id.orEmpty(), + address1 = it.address.orEmpty(), + address2 = it.address2.orEmpty(), + city = it.city.orEmpty(), + state = it.state.orEmpty(), + postcode = it.postcode.orEmpty(), + country = it.country.orEmpty(), + firstName = it.firstName.orEmpty(), + lastName = it.lastName.orEmpty(), + company = it.company.orEmpty(), + phone = it.phone.orEmpty(), + email = it.email.orEmpty(), + isDefault = it.defaultAddress, + isVerified = it.isVerified, + ) + } + } + fun toOriginAddressPurchaseDTO(address: OriginShippingAddress): OriginAddressPurchaseDTO { return OriginAddressPurchaseDTO( id = address.id, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptionsTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptionsTest.kt index f34f01f77ab2..b353c3d44427 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptionsTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ObserveStoreOptionsTest.kt @@ -1,6 +1,6 @@ package com.woocommerce.android.ui.orders.wooshippinglabels -import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.ShippingLabelsDataStore +import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingConfigurationDataStore import com.woocommerce.android.ui.orders.wooshippinglabels.models.StoreOptionsModel import com.woocommerce.android.viewmodel.BaseUnitTest import junit.framework.TestCase.assertTrue @@ -15,9 +15,9 @@ import kotlin.test.Test @OptIn(ExperimentalCoroutinesApi::class) class ObserveStoreOptionsTest : BaseUnitTest() { - private val dataStore: ShippingLabelsDataStore = mock() + private val dataStore: WooShippingConfigurationDataStore = mock() private val fetchAccountSettings: FetchAccountSettings = mock() - val defaultStoreOptions = StoreOptionsModel( + private val defaultStoreOptions = StoreOptionsModel( weightUnit = "kg", currencySymbol = "$", dimensionUnit = "cm", @@ -25,7 +25,7 @@ class ObserveStoreOptionsTest : BaseUnitTest() { ) val sut = ObserveStoreOptions( - dataStore = dataStore, + configurationDataStore = dataStore, fetchAccountSettings = fetchAccountSettings ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt index 42b4c66f0a91..cbddcaf50baf 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationViewModelTest.kt @@ -11,6 +11,7 @@ import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreat import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.PurchaseState import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.WooShippingViewState import com.woocommerce.android.ui.orders.wooshippinglabels.WooShippingLabelCreationViewModel.WooShippingViewState.DataState +import com.woocommerce.android.ui.orders.wooshippinglabels.address.ObserveOriginAddresses import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress import com.woocommerce.android.ui.orders.wooshippinglabels.models.PurchasedLabelData import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippableItemModel diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/FetchOriginAddressesTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/FetchOriginAddressesTest.kt new file mode 100644 index 000000000000..12ab2285212c --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/FetchOriginAddressesTest.kt @@ -0,0 +1,86 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.address + +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import com.woocommerce.android.ui.orders.wooshippinglabels.networking.WooShippingLabelRepository +import com.woocommerce.android.viewmodel.BaseUnitTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.SiteModel +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.WooResult +import kotlin.test.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class FetchOriginAddressesTest : BaseUnitTest() { + private val shippingRepository: WooShippingLabelRepository = mock() + private val selectedSite: SelectedSite = mock { + on { getOrNull() } doReturn SiteModel().apply { + url = "https://example.com" + } + } + + private val defaultOriginAddresses = OriginShippingAddress( + id = "1", + firstName = "John", + lastName = "Doe", + company = "", + address1 = "123 Main St", + address2 = "", + city = "Anytown", + state = "CA", + postcode = "12345", + country = "US", + email = "william.henry.harrison@example-pet-store.com", + phone = "555-555-5555", + isDefault = true, + isVerified = true + ) + + val sut = FetchOriginAddresses( + shippingRepository = shippingRepository, + selectedSite = selectedSite + ) + + @Test + fun `when selected site is null then return failure`() = testBlocking { + whenever(selectedSite.getOrNull()).doReturn(null) + val result = sut.invoke() + assert(result.isFailure) + } + + @Test + fun `when fetch origin addresses fails then return failure`() = testBlocking { + whenever(shippingRepository.fetchOriginAddresses(any())).doReturn( + WooResult(WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN)) + ) + + val result = sut.invoke() + assert(result.isFailure) + } + + @Test + fun `when fetch origin addresses is empty then return failure`() = testBlocking { + whenever(shippingRepository.fetchOriginAddresses(any())).doReturn( + WooResult(emptyList()) + ) + + val result = sut.invoke() + assert(result.isFailure) + } + + @Test + fun `when fetch origin addresses succeed then return success`() = testBlocking { + whenever(shippingRepository.fetchOriginAddresses(any())).doReturn( + WooResult(listOf(defaultOriginAddresses)) + ) + + val result = sut.invoke() + assert(result.isSuccess) + } +} diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveOriginAddressesTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveOriginAddressesTest.kt new file mode 100644 index 000000000000..c25b2ef6ab99 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/address/ObserveOriginAddressesTest.kt @@ -0,0 +1,77 @@ +package com.woocommerce.android.ui.orders.wooshippinglabels.address + +import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingAddressDataStore +import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress +import com.woocommerce.android.viewmodel.BaseUnitTest +import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import kotlin.test.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class ObserveOriginAddressesTest : BaseUnitTest() { + private val dataStore: WooShippingAddressDataStore = mock() + private val fetchOriginAddresses: FetchOriginAddresses = mock() + private val defaultOriginAddresses = OriginShippingAddress( + id = "1", + firstName = "John", + lastName = "Doe", + company = "", + address1 = "123 Main St", + address2 = "", + city = "Anytown", + state = "CA", + postcode = "12345", + country = "US", + email = "william.henry.harrison@example-pet-store.com", + phone = "555-555-5555", + isDefault = true, + isVerified = true + ) + + val sut = ObserveOriginAddresses( + addressDataStore = dataStore, + fetchOriginAddresses = fetchOriginAddresses + ) + + @Test + fun `when there is NO cached data and fetch account settings fails then return null`() = testBlocking { + whenever(dataStore.observeOriginAddresses()).doReturn(flowOf(null)) + whenever(fetchOriginAddresses.invoke()).doReturn(Result.failure(Exception("Random error"))) + val result = sut.invoke().toList() + + assertTrue(result.size == 1) + assertTrue(result.first() == null) + } + + @Test + fun `when there is cached data and fetch account settings fails then return null`() = testBlocking { + val cachedResult = listOf(defaultOriginAddresses) + whenever(dataStore.observeOriginAddresses()).doReturn(flowOf(cachedResult)) + whenever(fetchOriginAddresses.invoke()).doReturn(Result.failure(Exception("Random error"))) + val result = sut.invoke().toList() + + assertTrue(result.size == 2) + assertTrue(result.first() == cachedResult) + assertTrue(result.last() == null) + } + + @Test + fun `refresh data only once`() = testBlocking { + val firstResult = listOf(defaultOriginAddresses) + val secondResult = listOf(defaultOriginAddresses.copy(id = "updated id")) + whenever(dataStore.observeOriginAddresses()).doReturn( + flowOf(firstResult, secondResult) + ) + whenever(fetchOriginAddresses.invoke()).doReturn(Result.failure(Exception("Random error"))) + + sut.invoke().toList() + + verify(fetchOriginAddresses).invoke() + } +}