From 7ef7b94eb290cdc31e07a54b02400e914217037e Mon Sep 17 00:00:00 2001 From: VaiTon Date: Thu, 7 Apr 2022 18:09:08 +0200 Subject: [PATCH] fix: NPE in ProductCompareAdapter (#4631) * fix: NPE in ProductCompareAdapter Closes https://github.com/openfoodfacts/openfoodfacts-androidapp/issues/4614 * build: use 2G ram max for gradle build --- .../features/compare/ProductCompareAdapter.kt | 69 ++--------- .../ProductEditNutritionFactsFragment.kt | 2 +- .../product/view/CalculateDetailsActivity.kt | 22 ++-- .../environment/EnvironmentProductFragment.kt | 5 +- .../nutrition/NutritionProductFragment.kt | 110 +++++++----------- .../view/summary/SummaryProductFragment.kt | 83 ++++--------- .../openfood/models/NutrimentListItem.kt | 2 +- .../github/scrachx/openfood/models/Product.kt | 2 + .../openfood/models/ProductNutriments.kt | 41 ++++--- .../scrachx/openfood/utils/Measurement.kt | 15 ++- .../scrachx/openfood/utils/UnitUtilsTest.kt | 4 +- gradle.properties | 14 +-- 12 files changed, 139 insertions(+), 230 deletions(-) diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/compare/ProductCompareAdapter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/compare/ProductCompareAdapter.kt index a72af00398a8..a5f7571d2334 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/compare/ProductCompareAdapter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/compare/ProductCompareAdapter.kt @@ -17,6 +17,7 @@ package openfoodfacts.github.scrachx.openfood.features.compare import android.Manifest.permission import android.app.Activity +import android.content.Context import android.content.pm.PackageManager.PERMISSION_GRANTED import android.os.Build import android.view.LayoutInflater @@ -219,68 +220,18 @@ class ProductCompareAdapter( } private fun getLevelItems(product: Product): List { - val levelItems = mutableListOf() - val nutriments = product.nutriments val nutrientLevels = product.nutrientLevels - var fat: NutrimentLevel? = null - var saturatedFat: NutrimentLevel? = null - var sugars: NutrimentLevel? = null - var salt: NutrimentLevel? = null - - if (nutrientLevels != null) { - fat = nutrientLevels.fat - saturatedFat = nutrientLevels.saturatedFat - sugars = nutrientLevels.sugars - salt = nutrientLevels.salt - } - - if (fat != null || salt != null || saturatedFat != null || sugars != null) { - val fatNutriment = nutriments[Nutriment.FAT] - if (fat != null && fatNutriment != null) { - val fatNutrimentLevel = fat.getLocalize(activity) - levelItems += NutrientLevelItem( - activity.getString(R.string.compare_fat), - fatNutriment.getPer100gDisplayString(), - fatNutrimentLevel, - fat.getImgRes() - ) - } - val saturatedFatNutriment = nutriments[Nutriment.SATURATED_FAT] - if (saturatedFat != null && saturatedFatNutriment != null) { - val saturatedFatLocalize = saturatedFat.getLocalize(activity) - levelItems += NutrientLevelItem( - activity.getString(R.string.compare_saturated_fat), - saturatedFatNutriment.getPer100gDisplayString(), - saturatedFatLocalize, - saturatedFat.getImgRes() - ) - } - val sugarsNutriment = nutriments[Nutriment.SUGARS] - if (sugars != null && sugarsNutriment != null) { - val sugarsLocalize = sugars.getLocalize(activity) - levelItems += NutrientLevelItem( - activity.getString(R.string.compare_sugars), - sugarsNutriment.getPer100gDisplayString(), - sugarsLocalize, - sugars.getImgRes() - ) - } - val saltNutriment = nutriments[Nutriment.SALT] - if (salt != null && saltNutriment != null) { - val saltLocalize = salt.getLocalize(activity) - levelItems += NutrientLevelItem( - activity.getString(R.string.compare_salt), - saltNutriment.getPer100gDisplayString(), - saltLocalize, - salt.getImgRes() - ) - } - } - return levelItems + return listOfNotNull( + nutriments.buildLevelItem(activity, Nutriment.FAT, nutrientLevels?.fat), + nutriments.buildLevelItem(activity, Nutriment.SATURATED_FAT, nutrientLevels?.saturatedFat), + nutriments.buildLevelItem(activity, Nutriment.SUGARS, nutrientLevels?.sugars), + nutriments.buildLevelItem(activity, Nutriment.SALT, nutrientLevels?.salt) + ) } + fun onImageReturned(file: File) { val pos = imageReturnedPosition checkNotNull(pos) { "Position null." } @@ -321,5 +272,9 @@ class ProductCompareAdapter( binding.fullProductButton.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_fullscreen_blue_18dp, 0, 0, 0) } } + + companion object { + + } } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/nutrition/ProductEditNutritionFactsFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/nutrition/ProductEditNutritionFactsFragment.kt index 97eb0888b4fc..40b531f651e3 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/nutrition/ProductEditNutritionFactsFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/nutrition/ProductEditNutritionFactsFragment.kt @@ -644,7 +644,7 @@ class ProductEditNutritionFactsFragment : ProductEditFragment() { oldUnit = oldProductNutriment.unit oldMod = oldProductNutriment.modifier oldValue = if (isDataPer100g) - oldProductNutriment.per100gInUnit.value + oldProductNutriment.per100gInUnit!!.value else oldProductNutriment.perServingInUnit!!.value } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CalculateDetailsActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CalculateDetailsActivity.kt index a14358ecf4eb..0fabb77ac498 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CalculateDetailsActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CalculateDetailsActivity.kt @@ -72,7 +72,7 @@ class CalculateDetailsActivity : BaseActivity() { if (energyKcal != null) { nutrimentListItems += NutrimentListItem( getString(R.string.nutrition_energy_short_name), - calculateCalories(portion).value, + calculateCalories(portion)?.value, energyKcal.perServingInUnit?.value, MeasurementUnit.ENERGY_KCAL, energyKcal.modifier, @@ -82,7 +82,7 @@ class CalculateDetailsActivity : BaseActivity() { if (energyKj != null) { nutrimentListItems += NutrimentListItem( getString(R.string.nutrition_energy_short_name), - calculateKj(portion).value, + calculateKj(portion)?.value, energyKj.perServingInUnit?.value, MeasurementUnit.ENERGY_KJ, energyKj.modifier, @@ -94,7 +94,7 @@ class CalculateDetailsActivity : BaseActivity() { if (fat != null) { nutrimentListItems += NutrimentListItem( getString(R.string.nutrition_fat), - fat.getForPortion(portion).value, + fat.getForPortion(portion)?.value, fat.perServingInUnit?.value, fat.unit, fat.modifier @@ -107,7 +107,7 @@ class CalculateDetailsActivity : BaseActivity() { if (carbohydrates != null) { nutrimentListItems += NutrimentListItem( getString(R.string.nutrition_carbohydrate), - carbohydrates.getForPortion(portion).value, + carbohydrates.getForPortion(portion)?.value, carbohydrates.perServingInUnit?.value, carbohydrates.unit, carbohydrates.modifier @@ -123,7 +123,7 @@ class CalculateDetailsActivity : BaseActivity() { if (proteins != null) { nutrimentListItems += NutrimentListItem( getString(R.string.nutrition_proteins), - proteins.getForPortion(portion).value, + proteins.getForPortion(portion)?.value, proteins.perServingInUnit?.value, proteins.unit, proteins.modifier @@ -155,12 +155,14 @@ class CalculateDetailsActivity : BaseActivity() { } private fun getNutrimentItems(nutriments: ProductNutriments, nutrimentMap: Map): List { + val portion = measure(weight, unitOfMeasurement) + return nutrimentMap.mapNotNull { (name, stringRes) -> val nutriment = nutriments[name] ?: return@mapNotNull null NutrimentListItem( getString(stringRes), - nutriment.getForPortion(measure(weight, unitOfMeasurement)).value, + nutriment.getForPortion(portion)?.value, nutriment.perServingInUnit?.value, nutriment.unit, nutriment.modifier @@ -168,14 +170,14 @@ class CalculateDetailsActivity : BaseActivity() { } } - private fun calculateCalories(portion: Measurement): Measurement { - val energy100gCal = product.nutriments[Nutriment.ENERGY_KCAL]!!.per100gInG + private fun calculateCalories(portion: Measurement): Measurement? { + val energy100gCal = product.nutriments[Nutriment.ENERGY_KCAL]?.per100gInG ?: return null val portionGrams = portion.grams.value return Measurement(energy100gCal.value / 100 * portionGrams, energy100gCal.unit) } - private fun calculateKj(portion: Measurement): Measurement { - val energy100gKj = product.nutriments[Nutriment.ENERGY_KJ]!!.per100gInG + private fun calculateKj(portion: Measurement): Measurement? { + val energy100gKj = product.nutriments[Nutriment.ENERGY_KJ]?.per100gInG ?: return null val weightGrams = portion.grams.value return Measurement(energy100gKj.value / 100 * weightGrams, energy100gKj.unit) } 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 e6825ef91e91..279c10b1a047 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 @@ -106,10 +106,11 @@ class EnvironmentProductFragment : BaseFragment() { } val carbonFootprintNutriment = nutriments[Nutriment.CARBON_FOOTPRINT] - if (carbonFootprintNutriment != null) { + val carbonFootprintMeasure = carbonFootprintNutriment?.per100gInUnit + if (carbonFootprintMeasure != null) { binding.textCarbonFootprint.text = buildSpannedString { bold { append(getString(R.string.textCarbonFootprint)) } - append(getRoundNumber(carbonFootprintNutriment.per100gInUnit)) + append(getRoundNumber(carbonFootprintMeasure)) append(carbonFootprintNutriment.unit.sym) } } else { diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/nutrition/NutritionProductFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/nutrition/NutritionProductFragment.kt index 1e68af637055..d1477d7b1972 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/nutrition/NutritionProductFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/nutrition/NutritionProductFragment.kt @@ -40,6 +40,7 @@ import androidx.core.net.toUri import androidx.core.text.bold import androidx.core.text.buildSpannedString import androidx.core.text.inSpans +import androidx.core.view.isGone import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration @@ -59,8 +60,8 @@ import openfoodfacts.github.scrachx.openfood.customtabs.CustomTabsHelper import openfoodfacts.github.scrachx.openfood.customtabs.WebViewFallback import openfoodfacts.github.scrachx.openfood.databinding.FragmentNutritionProductBinding import openfoodfacts.github.scrachx.openfood.features.FullScreenActivityOpener -import openfoodfacts.github.scrachx.openfood.features.images.manage.ImagesManageActivity import openfoodfacts.github.scrachx.openfood.features.adapters.NutrimentsGridAdapter +import openfoodfacts.github.scrachx.openfood.features.images.manage.ImagesManageActivity import openfoodfacts.github.scrachx.openfood.features.login.LoginActivity import openfoodfacts.github.scrachx.openfood.features.product.edit.ProductEditActivity import openfoodfacts.github.scrachx.openfood.features.product.edit.ProductEditActivity.Companion.KEY_STATE @@ -70,9 +71,10 @@ import openfoodfacts.github.scrachx.openfood.features.shared.BaseFragment import openfoodfacts.github.scrachx.openfood.features.shared.adapters.NutrientLevelListAdapter import openfoodfacts.github.scrachx.openfood.images.ProductImage import openfoodfacts.github.scrachx.openfood.models.* -import openfoodfacts.github.scrachx.openfood.models.MeasurementUnit.* +import openfoodfacts.github.scrachx.openfood.models.MeasurementUnit.ENERGY_KCAL +import openfoodfacts.github.scrachx.openfood.models.MeasurementUnit.ENERGY_KJ import openfoodfacts.github.scrachx.openfood.models.entities.SendProduct -import openfoodfacts.github.scrachx.openfood.network.ApiFields +import openfoodfacts.github.scrachx.openfood.network.ApiFields.StateTags import openfoodfacts.github.scrachx.openfood.repositories.ProductRepository import openfoodfacts.github.scrachx.openfood.utils.* import pl.aprilapps.easyphotopicker.EasyImage @@ -370,22 +372,16 @@ class NutritionProductFragment : BaseFragment(), CustomTabActivityHelper.Connect private fun setupNutrientItems(nutriments: ProductNutriments) { val levelItemList = mutableListOf() val nutrientLevels = product.nutrientLevels - var fat: NutrimentLevel? = null - var saturatedFat: NutrimentLevel? = null - var sugars: NutrimentLevel? = null - var salt: NutrimentLevel? = null - - if (nutrientLevels != null) { - fat = nutrientLevels.fat - saturatedFat = nutrientLevels.saturatedFat - sugars = nutrientLevels.sugars - salt = nutrientLevels.salt - } + + val fat = nutrientLevels?.fat + val saturatedFat = nutrientLevels?.saturatedFat + val sugars = nutrientLevels?.sugars + val salt = nutrientLevels?.salt if (fat == null && salt == null && saturatedFat == null && sugars == null) { - binding.nutrientLevelsCardView.visibility = GONE levelItemList += NutrientLevelItem("", "", "", NO_ID) - binding.imageGrade.visibility = GONE + binding.nutrientLevelsCardView.visibility = GONE + updateNutritionGradeImg(false) } else { // prefetch the uri customTabActivityHelper = CustomTabActivityHelper() @@ -393,62 +389,37 @@ class NutritionProductFragment : BaseFragment(), CustomTabActivityHelper.Connect nutritionScoreUri = getString(R.string.nutriscore_uri).toUri() customTabActivityHelper!!.mayLaunchUrl(nutritionScoreUri, null, null) - val fatNutriment = nutriments[Nutriment.FAT] - if (fat != null && fatNutriment != null) { - val fatNutrimentLevel = fat.getLocalize(requireActivity()) - levelItemList += NutrientLevelItem( - getString(R.string.txtFat), - fatNutriment.getPer100gDisplayString(), - fatNutrimentLevel, - fat.getImgRes(), - ) - } - - val saturatedFatNutriment = nutriments[Nutriment.SATURATED_FAT] - if (saturatedFat != null && saturatedFatNutriment != null) { - val saturatedFatLocalize = saturatedFat.getLocalize(requireActivity()) - levelItemList += NutrientLevelItem( - getString(R.string.txtSaturatedFat), - saturatedFatNutriment.getPer100gDisplayString(), - saturatedFatLocalize, - saturatedFat.getImgRes(), - ) - } - - val sugarsNutriment = nutriments[Nutriment.SUGARS] - if (sugars != null && sugarsNutriment != null) { - val sugarsLocalize = sugars.getLocalize(requireActivity()) - levelItemList += NutrientLevelItem( - getString(R.string.txtSugars), - sugarsNutriment.getPer100gDisplayString(), - sugarsLocalize, - sugars.getImgRes(), - ) - } - - val saltNutriment = nutriments[Nutriment.SALT] - if (salt != null && saltNutriment != null) { - levelItemList += NutrientLevelItem( - getString(R.string.txtSalt), - saltNutriment.getPer100gDisplayString(), - salt.getLocalize(requireActivity()), - salt.getImgRes() - ) - } - drawNutritionGrade() + levelItemList += listOfNotNull( + nutriments.buildLevelItem(requireContext(), Nutriment.FAT, fat), + nutriments.buildLevelItem(requireContext(), Nutriment.SATURATED_FAT, saturatedFat), + nutriments.buildLevelItem(requireContext(), Nutriment.SUGARS, sugars), + nutriments.buildLevelItem(requireContext(), Nutriment.SALT, salt) + ) + updateNutritionGradeImg(true) } - binding.listNutrientLevels.adapter = NutrientLevelListAdapter(requireActivity(), levelItemList) - binding.listNutrientLevels.layoutManager = LinearLayoutManager(requireActivity()) + binding.listNutrientLevels.apply { + adapter = NutrientLevelListAdapter(requireActivity(), levelItemList) + layoutManager = LinearLayoutManager(requireActivity()) + } } - private fun drawNutritionGrade() { - binding.imageGradeLayout.visibility = VISIBLE + private fun updateNutritionGradeImg(show: Boolean) { + if (!show) { + binding.imageGradeLayout.isGone = true + return + } + + binding.imageGradeLayout.isGone = false binding.imageGrade.setImageResource(product.getNutriScoreResource()) binding.imageGrade.setOnClickListener { - val customTabsIntent = CustomTabsHelper.getCustomTabsIntent(requireContext(), customTabActivityHelper!!.session) - CustomTabActivityHelper.openCustomTab(requireActivity(), customTabsIntent, nutritionScoreUri!!, WebViewFallback()) + CustomTabActivityHelper.openCustomTab( + requireActivity(), + CustomTabsHelper.getCustomTabsIntent(requireContext(), customTabActivityHelper!!.session), + nutritionScoreUri!!, + WebViewFallback() + ) } } @@ -457,13 +428,16 @@ class NutritionProductFragment : BaseFragment(), CustomTabActivityHelper.Connect * Checks the product states_tags to determine which prompt to be shown */ private fun checkPrompts() { - if (product.statesTags.contains(ApiFields.StateTags.CATEGORIES_TO_BE_COMPLETED)) { + // Category + if (StateTags.CATEGORIES_TO_BE_COMPLETED in product.statesTags) { showCategoryPrompt = true } - if (product.noNutritionData == "on") { + + // Nutrition + if (product.isNoNutrition()) { showNutritionPrompt = false showNutritionData = false - } else if (product.statesTags.contains(ApiFields.StateTags.NUTRITION_FACTS_TO_BE_COMPLETED)) { + } else if (StateTags.NUTRITION_FACTS_TO_BE_COMPLETED in product.statesTags) { showNutritionPrompt = true } } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt index a1aaa789c2f2..0d6a82eb3991 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt @@ -391,78 +391,43 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { if (isFlavors(OFF)) { binding.scoresLayout.visibility = View.VISIBLE - val levelItems = mutableListOf() - val nutriments = product.nutriments - val nutrientLevels = product.nutrientLevels - var fat: NutrimentLevel? = null - var saturatedFat: NutrimentLevel? = null - var sugars: NutrimentLevel? = null - var salt: NutrimentLevel? = null - if (nutrientLevels != null) { - fat = nutrientLevels.fat - saturatedFat = nutrientLevels.saturatedFat - sugars = nutrientLevels.sugars - salt = nutrientLevels.salt - } val servingInL = product.isPerServingInLiter() binding.textNutrientTxt.setText(if (servingInL != true) R.string.txtNutrientLevel100g else R.string.txtNutrientLevel100ml) - if (fat != null || salt != null || saturatedFat != null || sugars != null) { + + val nutrientLevels = product.nutrientLevels + val fat = nutrientLevels?.fat + val saturatedFat = nutrientLevels?.saturatedFat + val sugars = nutrientLevels?.sugars + val salt = nutrientLevels?.salt + + val levelItems = mutableListOf() + if (fat == null && salt == null && saturatedFat == null && sugars == null) { + binding.cvNutritionLights.visibility = View.GONE + } else { // prefetch the URL nutritionScoreUri = Uri.parse(getString(R.string.nutriscore_uri)) customTabActivityHelper.mayLaunchUrl(nutritionScoreUri, null, null) + + val nutriments = product.nutriments + levelItems += listOfNotNull( + nutriments.buildLevelItem(requireContext(), Nutriment.FAT, fat), + nutriments.buildLevelItem(requireContext(), Nutriment.SATURATED_FAT, saturatedFat), + nutriments.buildLevelItem(requireContext(), Nutriment.SUGARS, sugars), + nutriments.buildLevelItem(requireContext(), Nutriment.SALT, salt), + ) + binding.cvNutritionLights.visibility = View.VISIBLE - val fatNutriment = nutriments[Nutriment.FAT] - if (fat != null && fatNutriment != null) { - levelItems += NutrientLevelItem( - getString(R.string.txtFat), - fatNutriment.getPer100gDisplayString(), - fat.getLocalize(requireContext()), - fat.getImgRes(), - ) - } - val saturatedFatNutriment = nutriments[Nutriment.SATURATED_FAT] - if (saturatedFat != null && saturatedFatNutriment != null) { - val saturatedFatLocalize = saturatedFat.getLocalize(requireContext()) - levelItems += NutrientLevelItem( - getString(R.string.txtSaturatedFat), - saturatedFatNutriment.getPer100gDisplayString(), - saturatedFatLocalize, - saturatedFat.getImgRes() - ) - } - val sugarsNutriment = nutriments[Nutriment.SUGARS] - if (sugars != null && sugarsNutriment != null) { - levelItems += NutrientLevelItem( - getString(R.string.txtSugars), - sugarsNutriment.getPer100gDisplayString(), - sugars.getLocalize(requireContext()), - sugars.getImgRes(), - ) - } - val saltNutriment = nutriments[Nutriment.SALT] - if (salt != null && saltNutriment != null) { - val saltLocalize = salt.getLocalize(requireContext()) - levelItems += NutrientLevelItem( - getString(R.string.txtSalt), - saltNutriment.getPer100gDisplayString(), - saltLocalize, - salt.getImgRes(), - ) - } - } else { - binding.cvNutritionLights.visibility = View.GONE } - binding.listNutrientLevels.layoutManager = LinearLayoutManager(requireContext()) - binding.listNutrientLevels.adapter = NutrientLevelListAdapter(requireContext(), levelItems) + binding.listNutrientLevels.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = NutrientLevelListAdapter(requireContext(), levelItems) + } refreshNutriScore() - refreshNovaIcon() - refreshCO2OrEcoscoreIcon() - refreshScoresLayout() } else { binding.scoresLayout.visibility = View.GONE diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/NutrimentListItem.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/NutrimentListItem.kt index 6f041cde0f19..1b2b7489f4e5 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/NutrimentListItem.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/NutrimentListItem.kt @@ -31,7 +31,7 @@ data class NutrimentListItem( nutriment: ProductNutriments.ProductNutriment ) : this( title, - nutriment.per100gInUnit.value, + nutriment.per100gInUnit?.value, nutriment.perServingInUnit?.value, nutriment.unit, nutriment.modifier diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Product.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Product.kt index 51af7a170a4e..538f706ceb16 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Product.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Product.kt @@ -189,6 +189,8 @@ class Product : SearchProduct() { @JsonProperty(ApiFields.Keys.NO_NUTRITION_DATA) val noNutritionData: String? = null + fun isNoNutrition(): Boolean = noNutritionData.contentEquals("on", true) + /** * The nutrientLevels */ diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/ProductNutriments.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/ProductNutriments.kt index 5ec1780ce2af..b58465bb12b9 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/ProductNutriments.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/ProductNutriments.kt @@ -1,13 +1,14 @@ package openfoodfacts.github.scrachx.openfood.models +import android.content.Context import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonInclude +import openfoodfacts.github.scrachx.openfood.R import openfoodfacts.github.scrachx.openfood.models.MeasurementUnit.UNIT_GRAM import openfoodfacts.github.scrachx.openfood.network.ApiFields.Suffix import openfoodfacts.github.scrachx.openfood.utils.* import java.io.Serializable -import java.util.* /** * JSON representation of the product `nutriments` entry. @@ -48,7 +49,7 @@ class ProductNutriments : Serializable { else ProductNutriment( nutriment, name, - getValuePer100g(nutriment)!!, + getValuePer100g(nutriment), getValuePerServing(nutriment), getUnit(nutriment), getModifier(nutriment) @@ -99,8 +100,10 @@ class ProductNutriments : Serializable { class ProductNutriment internal constructor( val nutriment: Nutriment, val name: String, - val per100gInG: Measurement, + + val per100gInG: Measurement?, val perServingInG: Measurement?, + unit: MeasurementUnit, val modifier: Modifier ) { @@ -108,22 +111,16 @@ class ProductNutriments : Serializable { fun isEnergy() = unit in ENERGY_UNITS - fun getPer100gDisplayString() = buildString { - modifier.ifNotDefault { - append(it.sym) - append(" ") - } - append(per100gInUnit.displayString()) - } + fun getPer100gDisplayString() = per100gInUnit?.toDisplayString(modifier) /** * Returns the amount of nutriment per 100g * of product in the units stored in [ProductNutriment.unit] */ - val per100gInUnit: Measurement + val per100gInUnit: Measurement? get() { return if (isEnergy()) per100gInG - else per100gInG.convertTo(unit) + else per100gInG?.convertTo(unit) } /** @@ -146,10 +143,22 @@ class ProductNutriments : Serializable { * @param portion a measurement of the portion * @return a nutrient measurement for a the given amount of this product */ - fun getForPortion(portion: Measurement) = Measurement( - value = per100gInUnit.value / 100 * portion.grams.value, - unit = per100gInUnit.unit - ) + fun getForPortion(portion: Measurement): Measurement? = per100gInUnit?.forPortion(portion) + + } +} + +fun ProductNutriments.buildLevelItem( + context: Context, + nutriment: Nutriment, + nutrimentLevel: NutrimentLevel?, +): NutrientLevelItem? { + val productNutriment = this[nutriment] ?: return null + + val per100gDisplayString = productNutriment.getPer100gDisplayString() ?: return null + val localizedNutrimentLevel = nutrimentLevel?.getLocalize(context) ?: return null + val category = context.getString(R.string.compare_fat) + return NutrientLevelItem(category, per100gDisplayString, localizedNutrimentLevel, nutrimentLevel.getImgRes()) } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Measurement.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Measurement.kt index 50409f4a3d56..0bd34243d5bb 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Measurement.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Measurement.kt @@ -2,6 +2,8 @@ package openfoodfacts.github.scrachx.openfood.utils import openfoodfacts.github.scrachx.openfood.models.MeasurementUnit import openfoodfacts.github.scrachx.openfood.models.MeasurementUnit.* +import openfoodfacts.github.scrachx.openfood.models.Modifier +import openfoodfacts.github.scrachx.openfood.models.ifNotDefault data class Measurement( @@ -11,6 +13,12 @@ data class Measurement( fun measure(value: Float, unit: MeasurementUnit) = Measurement(value, unit) +fun Measurement.forPortion(portion: Measurement) = Measurement( + value = value / 100 * portion.grams.value, + unit = this.unit +) + + /** * Converts a given measurement to grams. * @@ -71,7 +79,12 @@ fun Measurement.convertTo(unit: MeasurementUnit): Measurement { ) } -fun Measurement.displayString() = buildString { + +fun Measurement.toDisplayString(modifier: Modifier? = null): String = buildString { + modifier?.ifNotDefault { + append(it.sym) + append(" ") + } append(getRoundNumber(value)) append(" ") append(unit.sym) diff --git a/app/src/test/java/openfoodfacts/github/scrachx/openfood/utils/UnitUtilsTest.kt b/app/src/test/java/openfoodfacts/github/scrachx/openfood/utils/UnitUtilsTest.kt index d084714d11dd..2ab99dab9e3c 100644 --- a/app/src/test/java/openfoodfacts/github/scrachx/openfood/utils/UnitUtilsTest.kt +++ b/app/src/test/java/openfoodfacts/github/scrachx/openfood/utils/UnitUtilsTest.kt @@ -98,8 +98,8 @@ class UnitUtilsTest { @Test fun `test display string`() { - assertThat(measure(5.92f, UNIT_GRAM).displayString()).isIn(listOf("5.92 g", "5,92 g")) - assertThat(measure(5f, UNIT_GRAM).displayString()).isEqualTo("5 g") + assertThat(measure(5.92f, UNIT_GRAM).toDisplayString()).isIn(listOf("5.92 g", "5,92 g")) + assertThat(measure(5f, UNIT_GRAM).toDisplayString()).isEqualTo("5 g") } @Test diff --git a/gradle.properties b/gradle.properties index 557129b0304f..43aac028f649 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,5 @@ -## For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx1024m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -# -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true #Thu Apr 08 18:30:17 CEST 2021 android.enableJetifier=true android.useAndroidX=true -org.gradle.jvmargs=-Xms512M -Xmx1536M +org.gradle.jvmargs=-Xms1G -Xmx2G