From 2d9add64874eb6bbb1249f828804659336adfc6d Mon Sep 17 00:00:00 2001 From: VaiTon Date: Wed, 7 Apr 2021 16:22:11 +0200 Subject: [PATCH] feat: use lc when querying server. Closes https://github.com/openfoodfacts/openfoodfacts-androidapp/issues/3848 ref: renamed AbstractProductSearch to Search fix: fixed Unit Tests using robolectric --- app/build.gradle.kts | 1 + .../openfood/features/ImagesManageActivity.kt | 6 +- .../EmbCodeAutoCompleteAdapter.kt | 10 +- .../PeriodAfterOpeningAutoCompleteAdapter.kt | 6 +- .../edit/ProductEditIngredientsFragment.kt | 6 +- .../edit/ProductEditNutritionFactsFragment.kt | 6 +- .../edit/ProductEditOverviewFragment.kt | 19 +- .../environment/EnvironmentProductFragment.kt | 25 ++- .../features/search/ProductSearchActivity.kt | 10 +- .../{AbstractProductSearch.kt => Search.kt} | 2 +- .../scrachx/openfood/network/ApiFields.kt | 2 + .../openfood/network/OpenFoodAPIClient.kt | 165 ++++++++++-------- .../openfood/network/services/ProductsAPI.kt | 93 +++++----- .../scrachx/openfood/utils/FileDownloader.kt | 4 +- .../openfood/models/SendProductTest.kt | 7 +- .../openfood/network/ProductsAPITest.kt | 50 +++--- 16 files changed, 216 insertions(+), 196 deletions(-) rename app/src/main/java/openfoodfacts/github/scrachx/openfood/models/{AbstractProductSearch.kt => Search.kt} (96%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5726646f567d..25b6a1331e79 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -175,6 +175,7 @@ dependencies { // Unit Testing testImplementation("junit:junit:4.13.2") + testImplementation("org.robolectric:robolectric:4.4") testImplementation("org.mockito:mockito-core:3.8.0") testImplementation("net.javacrumbs.json-unit:json-unit-fluent:2.24.0") testImplementation("com.google.truth:truth:1.1.2") diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/ImagesManageActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/ImagesManageActivity.kt index 034c2cacd4fd..639d69fe26a5 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/ImagesManageActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/ImagesManageActivity.kt @@ -48,7 +48,6 @@ import openfoodfacts.github.scrachx.openfood.models.Product import openfoodfacts.github.scrachx.openfood.models.ProductImageField import openfoodfacts.github.scrachx.openfood.network.ApiFields import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient -import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI import openfoodfacts.github.scrachx.openfood.utils.* import openfoodfacts.github.scrachx.openfood.utils.FileDownloader.download import openfoodfacts.github.scrachx.openfood.utils.LocaleHelper.LanguageData @@ -79,9 +78,6 @@ class ImagesManageActivity : BaseActivity() { @Inject lateinit var picasso: Picasso - @Inject - lateinit var productsApi: ProductsAPI - private val disp = CompositeDisposable() private var lastViewedImage: File? = null @@ -466,7 +462,7 @@ class ImagesManageActivity : BaseActivity() { private fun editPhoto(productImageField: ProductImageField?, transformation: ImageTransformation) { if (transformation.isNotEmpty()) { - download(this, transformation.imageUrl!!, productsApi) + download(this, transformation.imageUrl!!, client) .observeOn(AndroidSchedulers.mainThread()) .subscribe { file: File? -> //to delete the file after: diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/EmbCodeAutoCompleteAdapter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/EmbCodeAutoCompleteAdapter.kt index 2b586fdbdfd8..7f1f4a06c47d 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/EmbCodeAutoCompleteAdapter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/EmbCodeAutoCompleteAdapter.kt @@ -4,15 +4,15 @@ import android.content.Context import android.widget.ArrayAdapter import android.widget.Filter import android.widget.Filterable -import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI +import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import org.apache.commons.lang3.StringUtils import java.util.* class EmbCodeAutoCompleteAdapter( - context: Context?, + context: Context, textViewResourceId: Int, - private val productsApi: ProductsAPI -) : ArrayAdapter(context!!, textViewResourceId), Filterable { + private val productsApi: OpenFoodAPIClient +) : ArrayAdapter(context, textViewResourceId), Filterable { private val codeList: MutableList = arrayListOf() @@ -28,7 +28,7 @@ class EmbCodeAutoCompleteAdapter( if (constraint == null) return FilterResults().apply { count = 0 } // Retrieve the autocomplete results from server. - val list = productsApi.getEMBCodeSuggestions(constraint.toString()).blockingGet() + val list = productsApi.rawApi.getEMBCodeSuggestions(constraint.toString()).blockingGet() // Assign the data to the FilterResults return FilterResults().apply { diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/PeriodAfterOpeningAutoCompleteAdapter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/PeriodAfterOpeningAutoCompleteAdapter.kt index 949a1788c04b..6cf69330db6e 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/PeriodAfterOpeningAutoCompleteAdapter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/adapters/autocomplete/PeriodAfterOpeningAutoCompleteAdapter.kt @@ -4,14 +4,14 @@ import android.content.Context import android.widget.ArrayAdapter import android.widget.Filter import android.widget.Filterable -import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI +import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import org.apache.commons.lang3.StringUtils import java.util.* class PeriodAfterOpeningAutoCompleteAdapter( context: Context?, textViewResourceId: Int, - private val productsApi: ProductsAPI + private val client: OpenFoodAPIClient ) : ArrayAdapter(context!!, textViewResourceId), Filterable { private val periodsList = mutableListOf() @@ -26,7 +26,7 @@ class PeriodAfterOpeningAutoCompleteAdapter( if (constraint == null) return FilterResults().apply { count = 0 } // Retrieve the autocomplete results from server. - val list = productsApi.getPeriodAfterOpeningSuggestions(constraint.toString()).blockingGet() + val list = client.rawApi.getPeriodAfterOpeningSuggestions(constraint.toString()).blockingGet() // Assign the data to the FilterResults return FilterResults().apply { diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditIngredientsFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditIngredientsFragment.kt index 8426d3096bcc..e1a42ae5fb9a 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditIngredientsFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditIngredientsFragment.kt @@ -47,7 +47,7 @@ import openfoodfacts.github.scrachx.openfood.models.entities.allergen.AllergenNa import openfoodfacts.github.scrachx.openfood.models.entities.allergen.AllergenNameDao import openfoodfacts.github.scrachx.openfood.network.ApiFields import openfoodfacts.github.scrachx.openfood.network.ApiFields.Keys.lcIngredientsKey -import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI +import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import openfoodfacts.github.scrachx.openfood.utils.* import openfoodfacts.github.scrachx.openfood.utils.FileDownloader.download import openfoodfacts.github.scrachx.openfood.utils.LocaleHelper.getLanguage @@ -73,7 +73,7 @@ class ProductEditIngredientsFragment : ProductEditFragment() { lateinit var picasso: Picasso @Inject - lateinit var productsApi: ProductsAPI + lateinit var client: OpenFoodAPIClient @Inject lateinit var matomoAnalytics: MatomoAnalytics @@ -324,7 +324,7 @@ class ProductEditIngredientsFragment : ProductEditFragment() { imagePath == null -> editIngredientsImage() photoFile != null -> cropRotateImage(photoFile, getString(R.string.ingredients_picture)) else -> { - download(requireContext(), imagePath!!, productsApi) + download(requireContext(), imagePath!!, client) .observeOn(AndroidSchedulers.mainThread()) .subscribe { file -> photoFile = file diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditNutritionFactsFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditNutritionFactsFragment.kt index 7585d31890cb..86ab53f7a3c6 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditNutritionFactsFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditNutritionFactsFragment.kt @@ -54,7 +54,7 @@ import openfoodfacts.github.scrachx.openfood.models.entities.OfflineSavedProduct import openfoodfacts.github.scrachx.openfood.network.ApiFields import openfoodfacts.github.scrachx.openfood.network.ApiFields.Defaults.NUTRITION_DATA_PER_100G import openfoodfacts.github.scrachx.openfood.network.ApiFields.Defaults.NUTRITION_DATA_PER_SERVING -import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI +import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import openfoodfacts.github.scrachx.openfood.utils.* import openfoodfacts.github.scrachx.openfood.utils.FileDownloader.download import openfoodfacts.github.scrachx.openfood.utils.UnitUtils.UNIT_IU @@ -106,7 +106,7 @@ class ProductEditNutritionFactsFragment : ProductEditFragment() { lateinit var picasso: Picasso @Inject - lateinit var productsApi: ProductsAPI + lateinit var client: OpenFoodAPIClient @Inject lateinit var matomoAnalytics: MatomoAnalytics @@ -409,7 +409,7 @@ class ProductEditNutritionFactsFragment : ProductEditFragment() { if (photoFile != null) { cropRotateImage(photoFile, getString(R.string.nutrition_facts_picture)) } else { - download(requireContext(), path, productsApi) + download(requireContext(), path, client) .observeOn(AndroidSchedulers.mainThread()) .subscribe { photoFile = it diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditOverviewFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditOverviewFragment.kt index fc978d1fd62a..79e7b3791a3d 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditOverviewFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditOverviewFragment.kt @@ -38,7 +38,6 @@ import com.theartofdev.edmodo.cropper.CropImage import dagger.hilt.android.AndroidEntryPoint import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxkotlin.addTo -import io.reactivex.schedulers.Schedulers import openfoodfacts.github.scrachx.openfood.AppFlavors.OBF import openfoodfacts.github.scrachx.openfood.AppFlavors.OFF import openfoodfacts.github.scrachx.openfood.AppFlavors.OPF @@ -72,7 +71,7 @@ import openfoodfacts.github.scrachx.openfood.models.entities.store.StoreNameDao import openfoodfacts.github.scrachx.openfood.models.entities.tag.TagDao import openfoodfacts.github.scrachx.openfood.network.ApiFields import openfoodfacts.github.scrachx.openfood.network.ApiFields.Keys.lcProductNameKey -import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI +import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import openfoodfacts.github.scrachx.openfood.utils.* import openfoodfacts.github.scrachx.openfood.utils.FileDownloader.download import openfoodfacts.github.scrachx.openfood.utils.LocaleHelper.getLCOrDefault @@ -97,7 +96,7 @@ class ProductEditOverviewFragment : ProductEditFragment() { lateinit var picasso: Picasso @Inject - lateinit var productsApi: ProductsAPI + lateinit var client: OpenFoodAPIClient @Inject lateinit var matomoAnalytics: MatomoAnalytics @@ -530,7 +529,7 @@ class ProductEditOverviewFragment : ProductEditFragment() { (operation.result as List).mapTo(countries) { it.name } val adapter = ArrayAdapter(requireActivity(), android.R.layout.simple_dropdown_item_1line, countries) - val embAdapter = EmbCodeAutoCompleteAdapter(activity, android.R.layout.simple_dropdown_item_1line, productsApi) + val embAdapter = EmbCodeAutoCompleteAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, client) binding.originOfIngredients.setAdapter(adapter) binding.countryWherePurchased.setAdapter(adapter) @@ -576,8 +575,11 @@ class ProductEditOverviewFragment : ProductEditFragment() { if (isFlavors(OBF)) { binding.periodOfTimeAfterOpeningTil.visibility = View.VISIBLE - val customAdapter = PeriodAfterOpeningAutoCompleteAdapter(activity, - android.R.layout.simple_dropdown_item_1line, productsApi) + val customAdapter = PeriodAfterOpeningAutoCompleteAdapter( + activity, + android.R.layout.simple_dropdown_item_1line, + client + ) binding.periodOfTimeAfterOpening.setAdapter(customAdapter) } } @@ -598,8 +600,7 @@ class ProductEditOverviewFragment : ProductEditFragment() { if (editionMode) { loadFrontImage(lang) val fields = "ingredients_text_$lang,product_name_$lang" - productsApi.getProductByBarcode(product!!.code, fields, getUserAgent(Utils.HEADER_USER_AGENT_SEARCH)) - .subscribeOn(Schedulers.io()) + client.getProductStateFull(product!!.code, fields) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { binding.name.setText(getString(R.string.txtLoading)) @@ -652,7 +653,7 @@ class ProductEditOverviewFragment : ProductEditFragment() { // Image found, download it if necessary and edit it isFrontImagePresent = true if (photoFile == null) { - download(requireContext(), frontImageUrl!!, productsApi) + download(requireContext(), frontImageUrl!!, client) .observeOn(AndroidSchedulers.mainThread()) .subscribe { file: File? -> photoFile = file diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/environment/EnvironmentProductFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/environment/EnvironmentProductFragment.kt index d951d7cbaa94..22ab8ff2ea6a 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/environment/EnvironmentProductFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/environment/EnvironmentProductFragment.kt @@ -9,6 +9,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.text.HtmlCompat +import androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT import androidx.core.text.bold import com.squareup.picasso.Picasso import dagger.hilt.android.AndroidEntryPoint @@ -26,6 +27,7 @@ import openfoodfacts.github.scrachx.openfood.models.Nutriments import openfoodfacts.github.scrachx.openfood.models.Product import openfoodfacts.github.scrachx.openfood.models.ProductImageField import openfoodfacts.github.scrachx.openfood.models.ProductState +import openfoodfacts.github.scrachx.openfood.network.ApiFields import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import openfoodfacts.github.scrachx.openfood.utils.* import java.io.File @@ -99,15 +101,15 @@ class EnvironmentProductFragment : BaseFragment() { if (carbonFootprintNutriment != null) { binding.textCarbonFootprint.text = SpannableStringBuilder() .bold { append(getString(R.string.textCarbonFootprint)) } - binding.textCarbonFootprint.append(carbonFootprintNutriment.for100gInUnits) - binding.textCarbonFootprint.append(carbonFootprintNutriment.unit) + .append(carbonFootprintNutriment.for100gInUnits) + .append(carbonFootprintNutriment.unit) } else { binding.carbonFootprintCv.visibility = View.GONE } val environmentInfoCard = product.environmentInfoCard if (!environmentInfoCard.isNullOrEmpty()) { - binding.environmentInfoText.append(HtmlCompat.fromHtml(environmentInfoCard, HtmlCompat.FROM_HTML_MODE_COMPACT)) + binding.environmentInfoText.append(HtmlCompat.fromHtml(environmentInfoCard, FROM_HTML_MODE_COMPACT)) binding.environmentInfoText.movementMethod = LinkMovementMethod.getInstance() } else { binding.environmentInfoCv.visibility = View.GONE @@ -117,8 +119,8 @@ class EnvironmentProductFragment : BaseFragment() { if (!packaging.isNullOrEmpty()) { binding.packagingText.text = SpannableStringBuilder() .bold { append(getString(R.string.packaging_environmentTab)) } - binding.packagingText.append(" ") - binding.packagingText.append(packaging.split(',').toString().removeSurrounding("[", "]")) + .append(" ") + .append(packaging.replace(",", ", ")) } else { binding.packagingCv.visibility = View.GONE } @@ -128,7 +130,7 @@ class EnvironmentProductFragment : BaseFragment() { // TODO: 02/03/2021 i18n binding.recyclingInstructionToDiscard.text = SpannableStringBuilder() .bold { append("Recycling instructions - To discard: ") } - binding.recyclingInstructionToDiscard.append(recyclingInstructionsToDiscard) + .append(recyclingInstructionsToDiscard) } else { binding.recyclingInstructionsDiscardCv.visibility = View.GONE } @@ -138,7 +140,7 @@ class EnvironmentProductFragment : BaseFragment() { // TODO: 02/03/2021 i18n binding.recyclingInstructionToRecycle.text = SpannableStringBuilder() .bold { append("Recycling instructions - To recycle: ") } - binding.recyclingInstructionToRecycle.append(recyclingInstructionsToRecycle) + .append(recyclingInstructionsToRecycle) } else { binding.recyclingInstructionsRecycleCv.visibility = View.GONE } @@ -188,7 +190,7 @@ class EnvironmentProductFragment : BaseFragment() { // Load into view binding.addPhotoLabel.visibility = View.GONE mUrlImage = photoFile.absolutePath - Picasso.get() + picasso .load(photoFile) .fit() .into(binding.imageViewPackaging) @@ -197,21 +199,18 @@ class EnvironmentProductFragment : BaseFragment() { //checks the product states_tags to determine which prompt to be shown private fun refreshTagsPrompt() { val statesTags = product.statesTags - showLabelsPrompt = statesTags.contains("en:labels-to-be-completed") - showOriginsPrompt = statesTags.contains("en:origins-to-be-completed") + showLabelsPrompt = ApiFields.StateTags.LABELS_TO_BE_COMPLETED in statesTags + showOriginsPrompt = ApiFields.StateTags.ORIGINS_TO_BE_COMPLETED in statesTags binding.addLabelOriginPrompt.visibility = View.VISIBLE when { showLabelsPrompt && showOriginsPrompt -> { - // showLabelsPrompt and showOriginsPrompt true binding.addLabelOriginPrompt.text = getString(R.string.add_labels_origins_prompt_text) } showLabelsPrompt -> { - // showLabelsPrompt true binding.addLabelOriginPrompt.text = getString(R.string.add_labels_prompt_text) } showOriginsPrompt -> { - // showOriginsPrompt true binding.addLabelOriginPrompt.text = getString(R.string.add_origins_prompt_text) } else -> binding.addLabelOriginPrompt.visibility = View.GONE diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/search/ProductSearchActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/search/ProductSearchActivity.kt index 122152c40ce3..4d84d614be04 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/search/ProductSearchActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/search/ProductSearchActivity.kt @@ -38,7 +38,7 @@ import openfoodfacts.github.scrachx.openfood.features.listeners.EndlessRecyclerV import openfoodfacts.github.scrachx.openfood.features.listeners.RecyclerItemClickListener import openfoodfacts.github.scrachx.openfood.features.scan.ContinuousScanActivity import openfoodfacts.github.scrachx.openfood.features.shared.BaseActivity -import openfoodfacts.github.scrachx.openfood.models.AbstractProductSearch +import openfoodfacts.github.scrachx.openfood.models.Search import openfoodfacts.github.scrachx.openfood.models.SearchInfo import openfoodfacts.github.scrachx.openfood.models.SearchProduct import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient @@ -344,7 +344,7 @@ class ProductSearchActivity : BaseActivity() { } - private fun Single.startSearch(@StringRes noMatchMsg: Int, @StringRes extendedMsg: Int = -1) { + private fun Single.startSearch(@StringRes noMatchMsg: Int, @StringRes extendedMsg: Int = -1) { observeOn(AndroidSchedulers.mainThread()).subscribe { search, throwable -> displaySearch(throwable == null, search, noMatchMsg, extendedMsg) }.addTo(disp) @@ -372,7 +372,7 @@ class ProductSearchActivity : BaseActivity() { } } - private fun showResponse(isResponseOk: Boolean, response: AbstractProductSearch?) { + private fun showResponse(isResponseOk: Boolean, response: Search?) { if (isResponseOk && response != null) { showSuccessfulResponse(response) } else { @@ -380,7 +380,7 @@ class ProductSearchActivity : BaseActivity() { } } - private fun showSuccessfulResponse(response: AbstractProductSearch) { + private fun showSuccessfulResponse(response: Search) { mCountProducts = response.count.toInt() if (pageAddress == 1) { val number = NumberFormat.getInstance(Locale.getDefault()).format(response.count.toLong()) @@ -447,7 +447,7 @@ class ProductSearchActivity : BaseActivity() { */ private fun displaySearch( isResponseSuccessful: Boolean, - response: AbstractProductSearch?, + response: Search?, @StringRes emptyMessage: Int, @StringRes extendedMessage: Int = -1 ) = if (response == null) { diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/AbstractProductSearch.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Search.kt similarity index 96% rename from app/src/main/java/openfoodfacts/github/scrachx/openfood/models/AbstractProductSearch.kt rename to app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Search.kt index 4ec0f1fc4b9d..760831857d04 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/AbstractProductSearch.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Search.kt @@ -5,7 +5,7 @@ import java.io.Serializable @JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder("page_size", "count", "skip", "page", "products") -data class AbstractProductSearch( +data class Search( @JsonProperty("page_size") val pageSize: String, @JsonProperty("count") val count: String, @JsonProperty("skip") val skip: Int, diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/ApiFields.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/ApiFields.kt index 0f1e50a6c00f..5021751388ef 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/ApiFields.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/ApiFields.kt @@ -28,6 +28,8 @@ object ApiFields { object StateTags { const val CATEGORIES_TO_BE_COMPLETED = "en:categories-to-be-completed" const val NUTRITION_FACTS_TO_BE_COMPLETED = "en:nutrition-facts-to-be-completed" + const val LABELS_TO_BE_COMPLETED = "en:labels-to-be-completed" + const val ORIGINS_TO_BE_COMPLETED = "en:origins-to-be-completed" const val INGREDIENTS_COMPLETED = "en:ingredients-completed" } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/OpenFoodAPIClient.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/OpenFoodAPIClient.kt index 564fd0d608c3..7c56dbfb35f4 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/OpenFoodAPIClient.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/OpenFoodAPIClient.kt @@ -50,18 +50,25 @@ import openfoodfacts.github.scrachx.openfood.features.product.view.ProductViewAc class OpenFoodAPIClient @Inject constructor( @ApplicationContext private val context: Context, private val daoSession: DaoSession, - private val productsApi: ProductsAPI, + val rawApi: ProductsAPI, private val sentryAnalytics: SentryAnalytics ) { private var historySyncDisp = CompositeDisposable() - fun getProductStateFull(barcode: String, customHeader: String = Utils.HEADER_USER_AGENT_SEARCH): Single { + fun getProductStateFull( + barcode: String, + customFields: String = getAllFields(), + customHeader: String = Utils.HEADER_USER_AGENT_SEARCH + ): Single { sentryAnalytics.setBarcode(barcode) - return productsApi.getProductByBarcode(barcode, getAllFields(), getUserAgent(customHeader)) + return rawApi.getProductByBarcode(barcode, customFields, getLanguage(context), getUserAgent(customHeader)) } - fun getProductsByBarcode(codes: List, customHeader: String = Utils.HEADER_USER_AGENT_SEARCH): Single> { - return productsApi.getProductsByBarcode(codes.joinToString(","), getAllFields(), customHeader) + fun getProductsByBarcode( + codes: List, + customHeader: String = Utils.HEADER_USER_AGENT_SEARCH + ): Single> { + return rawApi.getProductsByBarcode(codes.joinToString(","), getAllFields(), customHeader) .map { it.products } } @@ -104,9 +111,10 @@ class OpenFoodAPIClient @Inject constructor( val fields = ApiFields.Keys.PRODUCT_IMAGES_FIELDS.toMutableSet().also { it += ApiFields.Keys.lcProductNameKey(getLanguage(context)) }.joinToString(",") - return productsApi.getProductByBarcode( + return rawApi.getProductByBarcode( barcode, fields, + getLanguage(context), getUserAgent(Utils.HEADER_USER_AGENT_SEARCH) ) } @@ -120,38 +128,41 @@ class OpenFoodAPIClient @Inject constructor( * @param activity */ fun openProduct(barcode: String, activity: Activity): Disposable = - productsApi.getProductByBarcode(barcode, getAllFields(), getUserAgent(Utils.HEADER_USER_AGENT_SEARCH)) - .doOnError { - if (it is IOException) { - Toast.makeText(activity, R.string.something_went_wrong, Toast.LENGTH_LONG).show() - return@doOnError - } else { - productNotFoundDialogBuilder(activity, barcode).show() - } - } - .subscribe { state -> - if (state.status == 0L) { - productNotFoundDialogBuilder(activity, barcode) - .onNegative { _, _ -> activity.onBackPressed() } - .show() - } else { - addToHistory(state.product!!).subscribe() - startProductViewActivity(activity, state) - } - } + rawApi.getProductByBarcode( + barcode, + getAllFields(), + getLanguage(context), + getUserAgent(Utils.HEADER_USER_AGENT_SEARCH) + ).doOnError { + if (it is IOException) { + Toast.makeText(activity, R.string.something_went_wrong, Toast.LENGTH_LONG).show() + return@doOnError + } else { + productNotFoundDialogBuilder(activity, barcode).show() + } + }.subscribe { state -> + if (state.status == 0L) { + productNotFoundDialogBuilder(activity, barcode) + .onNegative { _, _ -> activity.onBackPressed() } + .show() + } else { + addToHistory(state.product!!).subscribe() + startProductViewActivity(activity, state) + } + } fun getIngredients(product: Product) = getIngredients(product.code) fun searchProductsByName(name: String, page: Int) = - productsApi.searchProductByName(name, fieldsToFetchFacets, page) + rawApi.searchProductByName(name, fieldsToFetchFacets, page) /** * @param barcode * @return a single containing a list of product ingredients (can be empty) */ // TODO: This or the field inside Product.kt? - fun getIngredients(barcode: String?) = productsApi.getIngredientsByBarcode(barcode).map { productState -> + fun getIngredients(barcode: String?) = rawApi.getIngredientsByBarcode(barcode).map { productState -> productState["product"][ApiFields.Keys.INGREDIENTS]?.map { ProductIngredient( it["id"].asText(), @@ -162,7 +173,7 @@ class OpenFoodAPIClient @Inject constructor( } fun getProductsByCountry(country: String, page: Int) = - productsApi.getProductsByCountry(country, page, fieldsToFetchFacets) + rawApi.getProductsByCountry(country, page, fieldsToFetchFacets) /** * Returns a map for images uploaded for product/ingredients/nutrition/other images @@ -196,10 +207,10 @@ class OpenFoodAPIClient @Inject constructor( } fun getProductsByCategory(category: String, page: Int) = - productsApi.getProductByCategory(category, page, fieldsToFetchFacets) + rawApi.getProductByCategory(category, page, fieldsToFetchFacets) fun getProductsByLabel(label: String, page: Int) = - productsApi.getProductsByLabel(label, page, fieldsToFetchFacets) + rawApi.getProductsByLabel(label, page, fieldsToFetchFacets) /** * Add a product to ScanHistory asynchronously @@ -207,7 +218,7 @@ class OpenFoodAPIClient @Inject constructor( fun addToHistory(product: Product) = Completable.fromAction { daoSession.historyProductDao.addToHistorySync(product, this) } fun getProductsByContributor(contributor: String, page: Int) = - productsApi.getProductsByContributor(contributor, page, fieldsToFetchFacets).subscribeOn(Schedulers.io()) + rawApi.getProductsByContributor(contributor, page, fieldsToFetchFacets).subscribeOn(Schedulers.io()) /** * upload images in offline mode @@ -226,7 +237,7 @@ class OpenFoodAPIClient @Inject constructor( return@mapNotNull null } val productImage = ProductImage(product.barcode, product.productField, imageFile, getLanguage(context)) - return@mapNotNull productsApi.saveImageSingle(getUploadableMap(productImage)) + return@mapNotNull rawApi.saveImageSingle(getUploadableMap(productImage)) .flatMapCompletable { jsonNode: JsonNode? -> if (jsonNode != null) { Log.d("onResponse", jsonNode.toString()) @@ -246,11 +257,11 @@ class OpenFoodAPIClient @Inject constructor( } }.flatMapCompletable { Completable.merge(it) } - fun getProductsByPackaging(packaging: String, page: Int): Single = - productsApi.getProductsByPackaging(packaging, page, fieldsToFetchFacets) + fun getProductsByPackaging(packaging: String, page: Int): Single = + rawApi.getProductsByPackaging(packaging, page, fieldsToFetchFacets) - fun getProductsByStore(store: String, page: Int): Single = - productsApi.getProductByStores(store, page, fieldsToFetchFacets) + fun getProductsByStore(store: String, page: Int): Single = + rawApi.getProductByStores(store, page, fieldsToFetchFacets) /** * Search for products using bran name @@ -258,11 +269,11 @@ class OpenFoodAPIClient @Inject constructor( * @param brand search query for product * @param page page numbers */ - fun getProductsByBrand(brand: String, page: Int): Single = - productsApi.getProductByBrands(brand, page, fieldsToFetchFacets) + fun getProductsByBrand(brand: String, page: Int): Single = + rawApi.getProductByBrands(brand, page, fieldsToFetchFacets) fun postImg(image: ProductImage, setAsDefault: Boolean = false): Completable { - return productsApi.saveImageSingle(getUploadableMap(image)) + return rawApi.saveImageSingle(getUploadableMap(image)) .flatMapCompletable { body: JsonNode -> if (!body.isObject) { throw IOException("body is not an object") @@ -291,7 +302,7 @@ class OpenFoodAPIClient @Inject constructor( IMG_ID to body["image"][IMG_ID].asText(), "id" to body["imagefield"].asText() ) - return productsApi.editImageSingle(image.barcode, addUserInfo(queryMap)) + return rawApi.editImageSingle(image.barcode, addUserInfo(queryMap)) .flatMapCompletable { jsonNode: JsonNode -> if ("status ok" == jsonNode[ApiFields.Keys.STATUS].asText()) { return@flatMapCompletable Completable.complete() @@ -301,7 +312,7 @@ class OpenFoodAPIClient @Inject constructor( } } - fun editImage(code: String, imgMap: MutableMap) = productsApi.editImages(code, addUserInfo(imgMap)) + fun editImage(code: String, imgMap: MutableMap) = rawApi.editImages(code, addUserInfo(imgMap)) /** * Unselect the image from the product code. @@ -310,46 +321,50 @@ class OpenFoodAPIClient @Inject constructor( */ fun unSelectImage(code: String, field: ProductImageField, language: String): Single { val imgMap = hashMapOf(IMAGE_STRING_ID to getImageStringKey(field, language)) - return productsApi.unSelectImage(code, addUserInfo(imgMap)) + return rawApi.unSelectImage(code, addUserInfo(imgMap)) } fun getProductsByOrigin(origin: String, page: Int) = - productsApi.getProductsByOrigin(origin, page, fieldsToFetchFacets) + rawApi.getProductsByOrigin(origin, page, fieldsToFetchFacets) fun syncOldHistory() { val fields = "image_small_url,product_name,brands,quantity,image_url,nutrition_grade_fr,code" historySyncDisp.clear() daoSession.historyProductDao.loadAll().forEach { historyProduct -> - productsApi.getProductByBarcode(historyProduct.barcode, fields, getUserAgent(Utils.HEADER_USER_AGENT_SEARCH)) - .map { state -> - if (state.status != 0L) { - val product = state.product!! - val hp = HistoryProduct( - product.productName, - product.brands, - product.getImageSmallUrl(getLanguage(context)), - product.code, - product.quantity, - product.nutritionGradeFr, - product.ecoscore, - product.novaGroups - ) - Log.d("syncOldHistory", hp.toString()) - hp.lastSeen = historyProduct.lastSeen - daoSession.historyProductDao.insertOrReplace(hp) - } - context.getSharedPreferences("prefs", 0).edit { - putBoolean("is_old_history_data_synced", true) - } - }.ignoreElement().subscribe().addTo(historySyncDisp) + rawApi.getProductByBarcode( + historyProduct.barcode, + fields, + getLanguage(context), + getUserAgent(Utils.HEADER_USER_AGENT_SEARCH) + ).map { state -> + if (state.status != 0L) { + val product = state.product!! + val hp = HistoryProduct( + product.productName, + product.brands, + product.getImageSmallUrl(getLanguage(context)), + product.code, + product.quantity, + product.nutritionGradeFr, + product.ecoscore, + product.novaGroups + ) + Log.d("syncOldHistory", hp.toString()) + hp.lastSeen = historyProduct.lastSeen + daoSession.historyProductDao.insertOrReplace(hp) + } + context.getSharedPreferences("prefs", 0).edit { + putBoolean("is_old_history_data_synced", true) + } + }.ignoreElement().subscribe().addTo(historySyncDisp) } } fun getInfoAddedIncompleteProductsSingle(contributor: String, page: Int) = - productsApi.getInfoAddedIncompleteProductsSingle(contributor, page) + rawApi.getInfoAddedIncompleteProductsSingle(contributor, page) fun getProductsByManufacturingPlace(manufacturingPlace: String, page: Int) = - productsApi.getProductsByManufacturingPlace(manufacturingPlace, page, fieldsToFetchFacets) + rawApi.getProductsByManufacturingPlace(manufacturingPlace, page, fieldsToFetchFacets) /** * call API service to return products using Additives @@ -358,25 +373,25 @@ class OpenFoodAPIClient @Inject constructor( * @param page number of pages */ fun getProductsByAdditive(additive: String, page: Int) = - productsApi.getProductsByAdditive(additive, page, fieldsToFetchFacets) + rawApi.getProductsByAdditive(additive, page, fieldsToFetchFacets) fun getProductsByAllergen(allergen: String, page: Int) = - productsApi.getProductsByAllergen(allergen, page, fieldsToFetchFacets) + rawApi.getProductsByAllergen(allergen, page, fieldsToFetchFacets) fun getToBeCompletedProductsByContributor(contributor: String, page: Int) = - productsApi.getToBeCompletedProductsByContributor(contributor, page) + rawApi.getToBeCompletedProductsByContributor(contributor, page) fun getPicturesContributedProducts(contributor: String, page: Int) = - productsApi.getPicturesContributedProducts(contributor, page) + rawApi.getPicturesContributedProducts(contributor, page) fun getPicturesContributedIncompleteProducts(contributor: String?, page: Int) = - productsApi.getPicturesContributedIncompleteProducts(contributor, page) + rawApi.getPicturesContributedIncompleteProducts(contributor, page) - fun getInfoAddedProducts(contributor: String?, page: Int) = productsApi.getInfoAddedProducts(contributor, page) + fun getInfoAddedProducts(contributor: String?, page: Int) = rawApi.getInfoAddedProducts(contributor, page) - fun getIncompleteProducts(page: Int) = productsApi.getIncompleteProducts(page, fieldsToFetchFacets) + fun getIncompleteProducts(page: Int) = rawApi.getIncompleteProducts(page, fieldsToFetchFacets) - fun getProductsByStates(state: String?, page: Int) = productsApi.getProductsByState(state, page, fieldsToFetchFacets) + fun getProductsByStates(state: String?, page: Int) = rawApi.getProductsByState(state, page, fieldsToFetchFacets) companion object { val MIME_TEXT: MediaType = MediaType.get("text/plain") diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/services/ProductsAPI.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/services/ProductsAPI.kt index 35caf347d9c3..8ad16ca14db8 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/services/ProductsAPI.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/network/services/ProductsAPI.kt @@ -21,8 +21,8 @@ import io.reactivex.Single import okhttp3.RequestBody import okhttp3.ResponseBody import openfoodfacts.github.scrachx.openfood.BuildConfig.FLAVOR -import openfoodfacts.github.scrachx.openfood.models.AbstractProductSearch import openfoodfacts.github.scrachx.openfood.models.ProductState +import openfoodfacts.github.scrachx.openfood.models.Search import openfoodfacts.github.scrachx.openfood.models.TagLineLanguage import openfoodfacts.github.scrachx.openfood.network.ApiFields import retrofit2.Call @@ -50,6 +50,7 @@ interface ProductsAPI { fun getProductByBarcode( @Path("barcode") barcode: String, @Query("fields") fields: String, + @Query("lc") locale: String, @Header("User-Agent") header: String ): Single @@ -61,7 +62,7 @@ interface ProductsAPI { @Query("code") barcodes: String, @Query("fields") fields: String, @Header("User-Agent") header: String - ): Single + ): Single @FormUrlEncoded @POST("cgi/product_jqm2.pl") @@ -76,7 +77,7 @@ interface ProductsAPI { @Query("search_terms") name: String, @Query("fields") fields: String, @Query("page") page: Int - ): Single + ): Single @FormUrlEncoded @POST("/cgi/session.pl") @@ -121,7 +122,7 @@ interface ProductsAPI { @Path("brand") brand: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single /** * call API service to return products using Additives @@ -134,186 +135,186 @@ interface ProductsAPI { @Path("additive") additive: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("allergen/{allergen}/{page}.json") fun getProductsByAllergen( @Path("allergen") allergen: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("country/{country}/{page}.json") fun getProductsByCountry( @Path("country") country: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("origin/{origin}/{page}.json") fun getProductsByOrigin( @Path("origin") origin: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("manufacturing-place/{manufacturing-place}/{page}.json") fun getProductsByManufacturingPlace( @Path("manufacturing-place") manufacturingPlace: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("store/{store}/{page}.json") fun getProductByStores( @Path("store") store: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("packaging/{packaging}/{page}.json") fun getProductsByPackaging( @Path("packaging") packaging: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("label/{label}/{page}.json") fun getProductsByLabel( @Path("label") label: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("category/{category}/{page}.json") fun getProductByCategory( @Path("category") category: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("contributor/{contributor}/{page}.json?nocache=1") fun getProductsByContributor( @Path("contributor") contributor: String, @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single @GET("language/{language}.json") - fun getProductsByLanguage(@Path("language") language: String): Single + fun getProductsByLanguage(@Path("language") language: String): Single @GET("label/{label}.json") - fun getProductsByLabel(@Path("label") label: String): Single + fun getProductsByLabel(@Path("label") label: String): Single @GET("category/{category}.json") - fun getProductsByCategory(@Path("category") category: String): Single + fun getProductsByCategory(@Path("category") category: String): Single @GET("state/{state}.json") fun getProductsByState( @Path("state") state: String, @Query("fields") fields: String - ): Single + ): Single @GET("packaging/{packaging}.json") - fun getProductsByPackaging(@Path("packaging") packaging: String): Single + fun getProductsByPackaging(@Path("packaging") packaging: String): Single @GET("brand/{brand}.json") - fun getProductsByBrand(@Path("brand") brand: String): Single + fun getProductsByBrand(@Path("brand") brand: String): Single @GET("purchase-place/{purchasePlace}.json") - fun getProductsByPurchasePlace(@Path("purchasePlace") purchasePlace: String): Single + fun getProductsByPurchasePlace(@Path("purchasePlace") purchasePlace: String): Single @GET("store/{store}.json") - fun getProductsByStore(@Path("store") store: String): Single + fun getProductsByStore(@Path("store") store: String): Single @GET("country/{country}.json") - fun byCountry(@Path("country") country: String): Single + fun byCountry(@Path("country") country: String): Single @GET("trace/{trace}.json") - fun getProductsByTrace(@Path("trace") trace: String): Single + fun getProductsByTrace(@Path("trace") trace: String): Single @GET("packager-code/{packager_code}.json") - fun getProductsByPackagerCode(@Path("packager_code") packagerCode: String): Single + fun getProductsByPackagerCode(@Path("packager_code") packagerCode: String): Single @GET("city/{city}.json") - fun getProducsByCity(@Path("city") city: String): Single + fun getProducsByCity(@Path("city") city: String): Single @GET("nutrition-grade/{nutriscore}.json") - fun getProductsByNutriScore(@Path("nutriscore") nutritionGrade: String): Single + fun getProductsByNutriScore(@Path("nutriscore") nutritionGrade: String): Single @GET("nutrient-level/{nutrient_level}.json") - fun byNutrientLevel(@Path("nutrient_level") nutrientLevel: String): Single + fun byNutrientLevel(@Path("nutrient_level") nutrientLevel: String): Single @GET("contributor/{contributor}.json?nocache=1") - fun byContributor(@Path("contributor") contributor: String): Single + fun byContributor(@Path("contributor") contributor: String): Single @GET("contributor/{contributor}/state/to-be-completed/{page}.json?nocache=1") fun getToBeCompletedProductsByContributor( @Path("contributor") contributor: String, @Path("page") page: Int - ): Single + ): Single @GET("/photographer/{contributor}/{page}.json?nocache=1") fun getPicturesContributedProducts( @Path("contributor") contributor: String, @Path("page") page: Int - ): Single + ): Single @GET("photographer/{Photographer}.json?nocache=1") - fun getProductsByPhotographer(@Path("Photographer") photographer: String): Single + fun getProductsByPhotographer(@Path("Photographer") photographer: String): Single @GET("photographer/{contributor}/state/to-be-completed/{page}.json?nocache=1") fun getPicturesContributedIncompleteProducts( @Path("contributor") contributor: String?, @Path("page") page: Int - ): Single + ): Single @GET("informer/{informer}.json?nocache=1") - fun getProductsByInformer(@Path("informer") informer: String?): Single + fun getProductsByInformer(@Path("informer") informer: String?): Single @GET("informer/{contributor}/{page}.json?nocache=1") - fun getInfoAddedProducts(@Path("contributor") contributor: String?, @Path("page") page: Int): Single + fun getInfoAddedProducts(@Path("contributor") contributor: String?, @Path("page") page: Int): Single @GET("informer/{contributor}/state/to-be-completed/{page}.json?nocache=1") fun getInfoAddedIncompleteProductsSingle( @Path("contributor") contributor: String, @Path("page") page: Int - ): Single + ): Single @GET("last-edit-date/{LastEditDate}.json") - fun getProductsByLastEditDate(@Path("LastEditDate") lastEditDate: String): Single + fun getProductsByLastEditDate(@Path("LastEditDate") lastEditDate: String): Single @GET("entry-dates/{EntryDates}.json") - fun getProductsByEntryDates(@Path("EntryDates") entryDates: String): Single + fun getProductsByEntryDates(@Path("EntryDates") entryDates: String): Single @GET("unknown-nutrient/{UnknownNutrient}.json") - fun getProductsByUnknownNutrient(@Path("UnknownNutrient") unknownNutrient: String): Single + fun getProductsByUnknownNutrient(@Path("UnknownNutrient") unknownNutrient: String): Single @GET("additive/{Additive}.json") fun getProductsByAdditive( @Path("Additive") additive: String?, @Query("fields") fields: String? - ): Single + ): Single @GET("code/{Code}.json") - fun getProductsByBarcode(@Path("Code") code: String): Single + fun getProductsByBarcode(@Path("Code") code: String): Single @GET("state/{State}/{page}.json") fun getProductsByState( @Path("State") state: String?, @Path("page") page: Int, @Query("fields") fields: String? - ): Single + ): Single /* * Open Beauty Facts experimental and specific APIs */ @Deprecated("") @GET("period-after-opening/{PeriodAfterOpening}.json") - fun getProductsByPeriodAfterOpening(@Path("PeriodAfterOpening") periodAfterOpening: String): Call + fun getProductsByPeriodAfterOpening(@Path("PeriodAfterOpening") periodAfterOpening: String): Call @GET("ingredient/{ingredient}.json") - fun getProductsByIngredient(@Path("ingredient") ingredient: String): Single + fun getProductsByIngredient(@Path("ingredient") ingredient: String): Single /** * This method gives a list of incomplete products @@ -322,13 +323,13 @@ interface ProductsAPI { fun getIncompleteProducts( @Path("page") page: Int, @Query("fields") fields: String - ): Single + ): Single /** * This method is used to get the number of products on Open X Facts */ @GET("/1.json?fields=null") - fun getTotalProductCount(@Header("User-Agent") header: String): Single + fun getTotalProductCount(@Header("User-Agent") header: String): Single /** * This method gives the news in all languages diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileDownloader.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileDownloader.kt index 4222f2549cab..f2cf2226b665 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileDownloader.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileDownloader.kt @@ -6,7 +6,7 @@ import androidx.core.net.toUri import io.reactivex.Maybe import io.reactivex.schedulers.Schedulers import okhttp3.ResponseBody -import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI +import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import openfoodfacts.github.scrachx.openfood.utils.Utils.makeOrGetPictureDirectory import java.io.File import java.io.FileOutputStream @@ -31,7 +31,7 @@ object FileDownloader { * @param fileUrl provides the URL of the file to download. * @return [Maybe] */ - fun download(context: Context, fileUrl: String, productsApi: ProductsAPI) = productsApi + fun download(context: Context, fileUrl: String, client: OpenFoodAPIClient) = client.rawApi .downloadFile(fileUrl) .flatMapMaybe { responseBody -> Log.d(LOG_TAG, "server contacted and has file") diff --git a/app/src/test/java/openfoodfacts/github/scrachx/openfood/models/SendProductTest.kt b/app/src/test/java/openfoodfacts/github/scrachx/openfood/models/SendProductTest.kt index 908aa058c454..6a7d0895b86c 100644 --- a/app/src/test/java/openfoodfacts/github/scrachx/openfood/models/SendProductTest.kt +++ b/app/src/test/java/openfoodfacts/github/scrachx/openfood/models/SendProductTest.kt @@ -4,10 +4,13 @@ import com.google.common.truth.Truth.assertThat import openfoodfacts.github.scrachx.openfood.models.entities.SendProduct import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner /** * Tests for [SendProduct] */ +@RunWith(RobolectricTestRunner::class) class SendProductTest { private var sendProduct: SendProduct? = null @@ -36,8 +39,8 @@ class SendProductTest { @Test fun copy_copiesFromAnotherSendProduct() { - val product1 = SendProduct(ID, BARCODE, LANG, NAME, BRANDS, WEIGHT, WEIGHT_UNIT, IMG_UPLOAD_FRONT, - IMG_UPLOAD_INGREDIENTS, IMG_UPLOAD_NUTRITION, IMG_UPLOAD_PACKAGING) + val product1 = SendProduct(ID, BARCODE, LANG, NAME, BRANDS, WEIGHT, IMG_UPLOAD_FRONT, IMG_UPLOAD_INGREDIENTS, + IMG_UPLOAD_NUTRITION, IMG_UPLOAD_PACKAGING, WEIGHT_UNIT) val product2 = SendProduct(product1) assertThat(product2.barcode).isEqualTo(BARCODE) assertThat(product2.lang).isEqualTo(LANG) diff --git a/app/src/testOff/java/openfoodfacts/github/scrachx/openfood/network/ProductsAPITest.kt b/app/src/testOff/java/openfoodfacts/github/scrachx/openfood/network/ProductsAPITest.kt index 31bc650ffee1..739e5f173d33 100644 --- a/app/src/testOff/java/openfoodfacts/github/scrachx/openfood/network/ProductsAPITest.kt +++ b/app/src/testOff/java/openfoodfacts/github/scrachx/openfood/network/ProductsAPITest.kt @@ -6,8 +6,8 @@ import io.reactivex.schedulers.Schedulers import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import openfoodfacts.github.scrachx.openfood.BuildConfig -import openfoodfacts.github.scrachx.openfood.models.AbstractProductSearch import openfoodfacts.github.scrachx.openfood.models.ProductState +import openfoodfacts.github.scrachx.openfood.models.Search import openfoodfacts.github.scrachx.openfood.models.entities.SendProduct import openfoodfacts.github.scrachx.openfood.network.services.ProductsAPI import openfoodfacts.github.scrachx.openfood.utils.Utils @@ -23,21 +23,21 @@ import java.time.Duration class ProductsAPITest { @Test fun byLanguage() { - val search = prodClient.getProductsByLanguage("italian").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByLanguage("italian").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun byLabel() { - val search = prodClient.getProductsByLabel("utz-certified").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByLabel("utz-certified").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun byCategory() { - val search = prodClient.getProductsByCategory("baby-foods").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByCategory("baby-foods").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @@ -45,88 +45,88 @@ class ProductsAPITest { @Test fun byState() { val fieldsToFetchFacets = "brands,product_name,image_small_url,quantity,nutrition_grades_tags" - val search = prodClient.getProductsByState("complete", fieldsToFetchFacets).blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByState("complete", fieldsToFetchFacets).blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun byPackaging() { - val search = prodClient.getProductsByPackaging("cardboard").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByPackaging("cardboard").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun byBrand() { - val search = prodClient.getProductsByBrand("monoprix").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByBrand("monoprix").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun byPurchasePlace() { - val search = prodClient.getProductsByPurchasePlace("marseille-5").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByPurchasePlace("marseille-5").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun byStore() { - val search = prodClient.getProductsByStore("super-u").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByStore("super-u").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun byCountry() { - val search = prodClient.byCountry("france").blockingGet() as AbstractProductSearch + val search = prodClient.byCountry("france").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotEmpty() } @Test fun byIngredient() { - val search = prodClient.getProductsByIngredient("sucre").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByIngredient("sucre").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotEmpty() } @Test fun byTrace() { - val search = prodClient.getProductsByTrace("eggs").blockingGet() as AbstractProductSearch + val search = prodClient.getProductsByTrace("eggs").blockingGet() as Search assertThat(search).isNotNull() assertThat(search.products).isNotNull() } @Test fun productByTrace_eggs_productsFound() { - val response = prodClient.getProductsByTrace("eggs").blockingGet() as AbstractProductSearch + val response = prodClient.getProductsByTrace("eggs").blockingGet() as Search response.assertProductsFound() } @Test fun productByPackagerCode_emb35069c_productsFound() { - val response = prodClient.getProductsByPackagerCode("emb-35069c").blockingGet() as AbstractProductSearch + val response = prodClient.getProductsByPackagerCode("emb-35069c").blockingGet() as Search response.assertProductsFound() } @Test fun productByNutritionGrade_a_productsFound() { - val res = prodClient.getProductsByNutriScore("a").blockingGet() as AbstractProductSearch + val res = prodClient.getProductsByNutriScore("a").blockingGet() as Search res.assertProductsFound() } @Test fun productByCity_Paris_noProductFound() { - val response = prodClient.getProducsByCity("paris").blockingGet() as AbstractProductSearch + val response = prodClient.getProducsByCity("paris").blockingGet() as Search response.assertNoProductsFound() } @Test fun productByAdditive_e301_productsFound() { val fieldsToFetchFacets = "brands,product_name,image_small_url,quantity,nutrition_grades_tags" - val response = prodClient.getProductsByAdditive("e301-sodium-ascorbate", fieldsToFetchFacets).blockingGet() as AbstractProductSearch + val response = prodClient.getProductsByAdditive("e301-sodium-ascorbate", fieldsToFetchFacets).blockingGet() as Search response.assertProductsFound() } @@ -136,8 +136,8 @@ class ProductsAPITest { prodClient.getProductByBarcode( barcode, "code", - getUserAgent(Utils.HEADER_USER_AGENT_SEARCH) - ).subscribe({ productState -> + LC, + getUserAgent(Utils.HEADER_USER_AGENT_SEARCH)).subscribe({ productState -> assertThat(productState.status).isEqualTo(0) assertThat(productState.statusVerbose).isEqualTo("product not found") assertThat(productState.code).isEqualTo(barcode) @@ -149,13 +149,14 @@ class ProductsAPITest { @Test fun post_product() { + val product = SendProduct().apply { barcode = "1234567890" name = "ProductName" brands = "productbrand" weight = "123" weightUnit = "g" - lang = "en" + lang = LC } val productDetails = mapOf( @@ -177,8 +178,8 @@ class ProductsAPITest { val response = devClientWithAuth.getProductByBarcode( product.barcode, fields, - getUserAgent(Utils.HEADER_USER_AGENT_SEARCH) - ).blockingGet() as ProductState + LC, + getUserAgent(Utils.HEADER_USER_AGENT_SEARCH)).blockingGet() as ProductState assertThat(response.product).isNotNull() val savedProduct = response.product!! @@ -189,6 +190,7 @@ class ProductsAPITest { } companion object { + const val LC = "en" private const val DEV_API = "https://world.openfoodfacts.dev" /** @@ -228,12 +230,12 @@ class ProductsAPITest { .create(ProductsAPI::class.java) } - private fun AbstractProductSearch.assertProductsFound() { + private fun Search.assertProductsFound() { assertThat(count.toInt()).isGreaterThan(0) assertThat(products).isNotEmpty() } - private fun AbstractProductSearch.assertNoProductsFound() { + private fun Search.assertNoProductsFound() { assertThat(count.toInt()).isEqualTo(0) assertThat(products).isEmpty() }