Skip to content

Commit

Permalink
Merge branch 'trunk' into issue/connecting-shipping-rates
Browse files Browse the repository at this point in the history
  • Loading branch information
atorresveiga authored Dec 11, 2024
2 parents 063540b + 31ff1ad commit 023c863
Show file tree
Hide file tree
Showing 33 changed files with 814 additions and 350 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [*] Blaze Campaign Intro screen now offers creating a new product if the site has no products yet [https://github.com/woocommerce/woocommerce-android/pull/13001]
- [Internal] Updated androidx-lifecycle to 2.8.7. [https://github.com/woocommerce/woocommerce-android/pull/13046/]
- [*] When entering a wrong WordPress.com account for login, retrying will bring the step back to the email input screen [https://github.com/woocommerce/woocommerce-android/pull/13024]
- [Internal] Replaces a function in WCSSRExt that then removes the need for commons-io dependency. [https://github.com/woocommerce/woocommerce-android/pull/13073]
- [Internal] Removes coupons feature announcement banner [https://github.com/woocommerce/woocommerce-android/pull/13077]

- [Internal] Updated androidx-compose-bom to 2024.09.00 [https://github.com/woocommerce/woocommerce-android/pull/13060]
Expand Down
1 change: 0 additions & 1 deletion WooCommerce/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ dependencies {
testImplementation(libs.cashapp.turbine)

implementation(libs.apache.commons.text)
implementation(libs.commons.io)

implementation(libs.tinder.statemachine)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.woocommerce.android.extensions

import org.apache.commons.text.StringEscapeUtils
import java.text.DecimalFormat
import java.util.Locale
import kotlin.math.log10
import kotlin.math.pow

const val BYTES_IN_KILOBYTE = 1024.0

/**
* Checks if a given string is a Float
Expand Down Expand Up @@ -100,3 +105,27 @@ fun String.toCamelCase(delimiter: String = " "): String {
fun String.capitalize(locale: Locale = Locale.getDefault()) = replaceFirstChar {
if (it.isLowerCase()) it.titlecase(locale) else it.toString()
}

/**
* Converts a numeric string representing bytes into a human-readable file size string.
*
* Source: https://stackoverflow.com/a/5599842
*
* Examples:
* "1024".readableFileSize() -> "1 kB"
* "1500000".readableFileSize() -> "1.4 MB"
* "0".readableFileSize() -> "0"
* Invalid input returns "0"
*
* @return Formatted string with size and unit (e.g., "1.5 GB")
*/
fun String.readableFileSize(): String {
val size = this.toLongOrNull()
if (size == null || size <= 0) return "0"

val units = arrayOf("B", "kB", "MB", "GB", "TB", "PB", "EB")
val digitGroups = (log10(size.toDouble()) / log10(BYTES_IN_KILOBYTE)).toInt()

return DecimalFormat("#,##0.#")
.format(size / BYTES_IN_KILOBYTE.pow(digitGroups.toDouble())) + " " + units[digitGroups]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package com.woocommerce.android.extensions

import androidx.core.text.HtmlCompat
import com.woocommerce.android.util.WooLog
import org.apache.commons.io.FileUtils.byteCountToDisplaySize
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
Expand Down Expand Up @@ -38,7 +37,7 @@ fun WCSSRModel.formatResult(): String {
sb.append(HEADING_SSR)

// Environment
environment?.let { it ->
environment?.let {
try {
sb.append(formatEnvironmentData(JSONObject(it)))
} catch (e: JSONException) {
Expand Down Expand Up @@ -110,7 +109,7 @@ private fun formatEnvironmentData(data: JSONObject): String {

val memoryLimit = data.optString("wp_memory_limit", MISSING_VALUE)
if (memoryLimit != MISSING_VALUE) {
sb.append("WP Memory Limit: ${byteCountToDisplaySize(memoryLimit.toLong())}\n")
sb.append("WP Memory Limit: ${memoryLimit.readableFileSize()}\n")
}

sb.append("WP Debug Mode: ${checkIfTrue(data.optBoolean("wp_debug_mode", false))}\n")
Expand All @@ -123,7 +122,7 @@ private fun formatEnvironmentData(data: JSONObject): String {

val postMaxSize = data.optString("php_post_max_size", MISSING_VALUE)
if (postMaxSize != MISSING_VALUE) {
sb.append("PHP Post Max Size: ${byteCountToDisplaySize(postMaxSize.toLong())}\n")
sb.append("PHP Post Max Size: ${postMaxSize.readableFileSize()}\n")
}

sb.append("PHP Time Limit: ${data.optString("php_max_execution_time", MISSING_VALUE)} s\n")
Expand All @@ -134,7 +133,7 @@ private fun formatEnvironmentData(data: JSONObject): String {

val maxUploadSize = data.optString("max_upload_size", MISSING_VALUE)
if (maxUploadSize != MISSING_VALUE) {
sb.append("PHP Post Max Size: ${byteCountToDisplaySize(maxUploadSize.toLong())}\n")
sb.append("Max Upload Size: ${maxUploadSize.readableFileSize()}\n")
}

sb.append("Default Timezone: ${data.optString("default_timezone", MISSING_VALUE)}\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ import com.woocommerce.android.ui.products.ParameterRepository
import com.woocommerce.android.ui.products.ProductRestriction
import com.woocommerce.android.ui.products.ProductStatus
import com.woocommerce.android.ui.products.ProductType
import com.woocommerce.android.ui.products.inventory.FetchProductBySKU
import com.woocommerce.android.ui.products.inventory.FetchProductByIdentifier
import com.woocommerce.android.ui.products.selector.ProductSelectorViewModel.SelectedItem
import com.woocommerce.android.ui.products.selector.ProductSelectorViewModel.SelectedItem.Product
import com.woocommerce.android.ui.products.selector.variationIds
Expand Down Expand Up @@ -209,7 +209,7 @@ class OrderCreateEditViewModel @Inject constructor(
private val currencySymbolFinder: CurrencySymbolFinder,
private val totalsHelper: OrderCreateEditTotalsHelper,
private val feedbackRepository: FeedbackRepository,
private val fetchProductBySKU: FetchProductBySKU,
private val fetchProductByIdentifier: FetchProductByIdentifier,
dateUtils: DateUtils,
autoSyncOrder: AutoSyncOrder,
autoSyncPriceModifier: AutoSyncPriceModifier,
Expand Down Expand Up @@ -917,7 +917,7 @@ class OrderCreateEditViewModel @Inject constructor(
}.orEmpty()
viewModelScope.launch {
viewState = viewState.copy(isUpdatingOrderDraft = true)
val result = fetchProductBySKU(barcodeOptions.sku, barcodeOptions.barcodeFormat)
val result = fetchProductByIdentifier(barcodeOptions.sku, barcodeOptions.barcodeFormat)
if (result.isSuccess) {
val product = result.getOrNull()
if (product != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.woocommerce.android.ui.products.inventory

import com.woocommerce.android.model.Product
import com.woocommerce.android.ui.orders.creation.CheckDigitRemoverFactory
import com.woocommerce.android.ui.orders.creation.GoogleBarcodeFormatMapper
import com.woocommerce.android.ui.products.list.ProductListRepository
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.wordpress.android.fluxc.store.WCProductStore
import javax.inject.Inject

class FetchProductByIdentifier @Inject constructor(
private val productRepository: ProductListRepository,
private val checkDigitRemoverFactory: CheckDigitRemoverFactory,
) {
suspend operator fun invoke(
codeScannerResultCode: String,
codeScannerResultFormat: GoogleBarcodeFormatMapper.BarcodeFormat
): Result<Product> = coroutineScope {
val globalUniqueIdentifierSearch = async {
searchProductByGlobalUniqueIdentifier(
codeScannerResultCode,
codeScannerResultFormat
)
}
val skuSearch = async { searchProductBySku(codeScannerResultCode, codeScannerResultFormat) }

val product = globalUniqueIdentifierSearch.await() ?: skuSearch.await()

if (product != null) {
Result.success(product)
} else {
Result.failure(Exception("Product not found"))
}
}

private suspend fun searchProductBySku(
codeScannerResultCode: String,
codeScannerResultFormat: GoogleBarcodeFormatMapper.BarcodeFormat
): Product? {
val product = productRepository.searchProductList(
searchQuery = codeScannerResultCode,
skuSearchOptions = WCProductStore.SkuSearchOptions.ExactSearch
)?.firstOrNull()
?: removeCheckDigitIfPossible(
codeScannerResultCode = codeScannerResultCode,
codeScannerResultFormat = codeScannerResultFormat
)?.let {
productRepository.searchProductList(
searchQuery = it,
skuSearchOptions = WCProductStore.SkuSearchOptions.ExactSearch
)?.firstOrNull()
}
return product
}

private suspend fun searchProductByGlobalUniqueIdentifier(
codeScannerResultCode: String,
codeScannerResultFormat: GoogleBarcodeFormatMapper.BarcodeFormat
): Product? {
val product = productRepository.searchProductListByGlobalUniqueId(
globalUniqueId = codeScannerResultCode
)?.firstOrNull() ?: removeCheckDigitIfPossible(
codeScannerResultCode = codeScannerResultCode,
codeScannerResultFormat = codeScannerResultFormat
)?.let {
productRepository.searchProductListByGlobalUniqueId(
globalUniqueId = codeScannerResultCode
)?.firstOrNull()
}

return product
}

private fun removeCheckDigitIfPossible(
codeScannerResultCode: String,
codeScannerResultFormat: GoogleBarcodeFormatMapper.BarcodeFormat
): String? {
if (codeScannerResultFormat.isEAN() || codeScannerResultFormat.isUPC()) {
return checkDigitRemoverFactory.getCheckDigitRemoverFor(codeScannerResultFormat)
.getSKUWithoutCheckDigit(codeScannerResultCode)
}

return null
}

private fun GoogleBarcodeFormatMapper.BarcodeFormat.isUPC() =
this == GoogleBarcodeFormatMapper.BarcodeFormat.FormatUPCA ||
this == GoogleBarcodeFormatMapper.BarcodeFormat.FormatUPCE

private fun GoogleBarcodeFormatMapper.BarcodeFormat.isEAN() =
this == GoogleBarcodeFormatMapper.BarcodeFormat.FormatEAN13 ||
this == GoogleBarcodeFormatMapper.BarcodeFormat.FormatEAN8
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import javax.inject.Inject
@HiltViewModel
class ScanToUpdateInventoryViewModel @Inject constructor(
savedState: SavedStateHandle,
private val fetchProductBySKU: FetchProductBySKU,
private val fetchProductByIdentifier: FetchProductByIdentifier,
private val resourceProvider: ResourceProvider,
private val productRepository: ProductDetailRepository,
private val variationRepository: VariationDetailRepository,
Expand Down Expand Up @@ -62,7 +62,7 @@ class ScanToUpdateInventoryViewModel @Inject constructor(

private fun handleBarcodeScanningSuccess(status: CodeScannerStatus.Success) = launch {
_viewState.value = ViewState.Loading
val productResult: Result<Product> = fetchProductBySKU(status.code, status.format)
val productResult: Result<Product> = fetchProductByIdentifier(status.code, status.format)
if (productResult.isSuccess) {
val product = productResult.getOrNull()
if (product != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.wordpress.android.fluxc.action.WCProductAction
import org.wordpress.android.fluxc.generated.WCProductActionBuilder
import org.wordpress.android.fluxc.model.WCProductModel
import org.wordpress.android.fluxc.store.WCProductStore
import org.wordpress.android.fluxc.store.WCProductStore.SkuSearchOptions
import javax.inject.Inject

class ProductListRepository @Inject constructor(
Expand All @@ -42,7 +43,8 @@ class ProductListRepository @Inject constructor(
private const val PRODUCT_PAGE_SIZE = WCProductStore.DEFAULT_PRODUCT_PAGE_SIZE
}

private var searchContinuation = ContinuationWrapper<List<Product>>(WooLog.T.PRODUCTS)
private var searchBySKUContinuation = ContinuationWrapper<List<Product>>(WooLog.T.PRODUCTS)
private var searchByGlobalUniqueIdContinuation = ContinuationWrapper<List<Product>>(WooLog.T.PRODUCTS)
private var trashContinuation = ContinuationWrapper<Boolean>(WooLog.T.PRODUCTS)
private var offset = 0

Expand Down Expand Up @@ -131,7 +133,7 @@ class ProductListRepository @Inject constructor(
excludedProductIds: List<Long>? = null,
productFilterOptions: Map<WCProductStore.ProductFilterOption, String> = emptyMap(),
): List<Product>? {
val result = searchContinuation.callAndWaitUntilTimeout(AppConstants.REQUEST_TIMEOUT) {
val result = searchBySKUContinuation.callAndWaitUntilTimeout(AppConstants.REQUEST_TIMEOUT) {
offset = if (loadMore) offset + PRODUCT_PAGE_SIZE else 0
lastSearchQuery = searchQuery
lastIsSkuSearch = skuSearchOptions
Expand All @@ -154,6 +156,32 @@ class ProductListRepository @Inject constructor(
}
}

suspend fun searchProductListByGlobalUniqueId(
globalUniqueId: String,
loadMore: Boolean = false,
excludedProductIds: List<Long>? = null,
productFilterOptions: Map<WCProductStore.ProductFilterOption, String> = emptyMap(),
): List<Product>? {
val result = searchByGlobalUniqueIdContinuation.callAndWaitUntilTimeout(AppConstants.REQUEST_TIMEOUT) {
offset = if (loadMore) offset + PRODUCT_PAGE_SIZE else 0
val payload = WCProductStore.SearchProductsByGlobalUniqueIdPayload(
site = selectedSite.get(),
globalUniqueId = globalUniqueId,
pageSize = PRODUCT_PAGE_SIZE,
offset = offset,
sorting = productSortingChoice,
excludedProductIds = excludedProductIds,
filterOptions = productFilterOptions
)
dispatcher.dispatch(WCProductActionBuilder.newSearchProductsByGlobalUniqueIdAction(payload))
}

return when (result) {
is ContinuationWrapper.ContinuationResult.Cancellation -> null
is ContinuationWrapper.ContinuationResult.Success -> result.value
}
}

/**
* Dispatches a request to trash a specific product
*/
Expand Down Expand Up @@ -240,12 +268,18 @@ class ProductListRepository @Inject constructor(
@Suppress("unused")
@Subscribe(threadMode = ThreadMode.MAIN)
fun onProductsSearched(event: WCProductStore.OnProductsSearched) {
val continuation = if (event.globalUniqueIdSearchQuery != null) {
searchByGlobalUniqueIdContinuation
} else {
searchBySKUContinuation
}

if (event.isError) {
searchContinuation.continueWith(emptyList())
continuation.continueWith(emptyList())
} else {
canLoadMoreProducts = event.canLoadMore
val products = event.searchResults.map { it.toAppModel() }
searchContinuation.continueWith(products)
continuation.continueWith(products)
}
}

Expand Down
Loading

0 comments on commit 023c863

Please sign in to comment.