From b11db2c9d721c208df2e4c769023f88445f0d47f Mon Sep 17 00:00:00 2001 From: VaiTon Date: Sat, 3 Apr 2021 21:33:44 +0200 Subject: [PATCH] fix: bold labels in SummaryProductFragment.kt, ref: use androidx instead of custom methods fix: bold allergens in ingredients list ref: deduplicate --- .../scrachx/openfood/features/HomeFragment.kt | 4 +- .../openfood/features/LoginActivity.kt | 19 +-- .../additives/AdditiveFragmentHelper.kt | 58 +++---- .../features/compare/ProductCompareAdapter.kt | 13 +- .../product/ProductFragmentPagerAdapter.kt | 14 +- .../product/edit/ProductEditActivity.kt | 11 +- .../product/view/CategoryProductHelper.kt | 51 +++---- .../product/view/ProductViewActivity.kt | 82 +++++----- .../environment/EnvironmentProductFragment.kt | 14 +- .../IIngredientsProductPresenter.kt | 5 +- .../ingredients/IngredientsProductFragment.kt | 143 +++++++++--------- .../IngredientsProductPresenter.kt | 42 +++-- .../nutrition/NutritionProductFragment.kt | 8 +- .../view/summary/SummaryProductFragment.kt | 24 ++- .../scanhistory/ScanHistoryActivity.kt | 29 ++-- .../features/search/ProductSearchActivity.kt | 86 +++++------ .../adapters/NutrientLevelListAdapter.kt | 5 +- .../openfood/models/BoldNutrimentListItem.kt | 13 +- .../openfood/models/NutrimentListItem.kt | 10 +- .../scrachx/openfood/models/Nutriments.kt | 6 +- .../scrachx/openfood/network/ApiFields.kt | 1 + .../openfood/network/OpenFoodAPIClient.kt | 4 +- .../openfood/network/services/ProductsAPI.kt | 46 +++--- .../github/scrachx/openfood/utils/Utils.kt | 30 ++-- app/src/main/res/layout/activity_login.xml | 1 + .../res/layout/fragment_summary_product.xml | 2 +- 26 files changed, 360 insertions(+), 361 deletions(-) diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/HomeFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/HomeFragment.kt index 84e7ca53a597..7da42b9320e5 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/HomeFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/HomeFragment.kt @@ -116,7 +116,6 @@ class HomeFragment : NavigationBaseFragment() { } productsApi.signIn(login, password, "Sign-in") - .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnError { Log.e(LOG_TAG, "Cannot check user credentials.", it) } .subscribe { response -> @@ -126,8 +125,7 @@ class HomeFragment : NavigationBaseFragment() { Log.e(LOG_TAG, "I/O Exception while checking user saved credentials.", e) return@subscribe } - if (htmlBody.contains("Incorrect user name or password.") - || htmlBody.contains("See you soon!")) { + if (LoginActivity.isHtmlNotValid(htmlBody)) { Log.w(LOG_TAG, "Cannot validate login, deleting saved credentials and asking the user to log back in.") settings.edit { putString("user", "") diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/LoginActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/LoginActivity.kt index 085a9dae7800..2366a2a7aa4f 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/LoginActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/LoginActivity.kt @@ -28,7 +28,7 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.core.content.ContextCompat import androidx.core.content.edit import com.afollestad.materialdialogs.MaterialDialog -import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import io.reactivex.android.schedulers.AndroidSchedulers @@ -99,15 +99,14 @@ class LoginActivity : BaseActivity() { binding.passInput.error = getString(R.string.error_field_required) binding.passInput.requestFocus() return - } - if (password.length < 6) { + } else if (password.length < 6) { binding.passInput.error = getText(R.string.error_invalid_password) binding.passInput.requestFocus() return } // End checks - val snackbar = Snackbar.make(binding.loginLinearlayout, R.string.toast_retrieving, BaseTransientBottomBar.LENGTH_LONG) + val snackbar = Snackbar.make(binding.loginLinearlayout, R.string.toast_retrieving, LENGTH_LONG) .apply { show() } binding.btnLogin.isClickable = false @@ -129,10 +128,8 @@ class LoginActivity : BaseActivity() { return@subscribe } val pref = this@LoginActivity.getSharedPreferences("login", 0) - if (htmlNoParsed == null - || htmlNoParsed.contains("Incorrect user name or password.") - || htmlNoParsed.contains("See you soon!")) { - Snackbar.make(binding.loginLinearlayout, R.string.errorLogin, BaseTransientBottomBar.LENGTH_LONG).show() + if (isHtmlNotValid(htmlNoParsed)) { + Snackbar.make(binding.loginLinearlayout, R.string.errorLogin, LENGTH_LONG).show() binding.passInput.setText("") binding.txtInfoLogin.setTextColor(ContextCompat.getColor(this, R.color.red)) binding.txtInfoLogin.setText(R.string.txtInfoLoginNo) @@ -151,7 +148,7 @@ class LoginActivity : BaseActivity() { break } } - Snackbar.make(binding.loginLinearlayout, R.string.connection, BaseTransientBottomBar.LENGTH_LONG).show() + Snackbar.make(binding.loginLinearlayout, R.string.connection, LENGTH_LONG).show() pref.edit { putString("user", login) putString("pass", password) @@ -248,5 +245,9 @@ class LoginActivity : BaseActivity() { override fun parseResult(resultCode: Int, intent: Intent?) = resultCode == RESULT_OK } + + internal fun isHtmlNotValid(html: String?) = (html == null + || html.contains("Incorrect user name or password.") + || html.contains("See you soon!")) } } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/additives/AdditiveFragmentHelper.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/additives/AdditiveFragmentHelper.kt index 34e17665d54a..b5397fc0726e 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/additives/AdditiveFragmentHelper.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/additives/AdditiveFragmentHelper.kt @@ -1,15 +1,16 @@ package openfoodfacts.github.scrachx.openfood.features.additives import android.text.SpannableStringBuilder -import android.text.Spanned import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.text.style.DynamicDrawableSpan -import android.text.style.ForegroundColorSpan import android.text.style.ImageSpan import android.view.View import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.core.text.bold +import androidx.core.text.color +import androidx.core.text.inSpans import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.databind.JsonNode import io.reactivex.disposables.CompositeDisposable @@ -20,7 +21,6 @@ import openfoodfacts.github.scrachx.openfood.features.shared.BaseFragment import openfoodfacts.github.scrachx.openfood.models.entities.additive.AdditiveName import openfoodfacts.github.scrachx.openfood.network.WikiDataApiClient import openfoodfacts.github.scrachx.openfood.utils.SearchType -import openfoodfacts.github.scrachx.openfood.utils.bold import openfoodfacts.github.scrachx.openfood.utils.showBottomSheet /** @@ -31,28 +31,30 @@ object AdditiveFragmentHelper { * Show names of all additives on the TextView * * @param additives list of additive names - * @param additiveProduct TextView which displays additive names + * @param additivesView TextView which displays additive names * @param apiClientForWikiData object of WikidataApiClient */ @JvmStatic fun showAdditives( additives: List, - additiveProduct: TextView, + additivesView: TextView, apiClientForWikiData: WikiDataApiClient, fragment: BaseFragment, compositeDisposable: CompositeDisposable - ) = additiveProduct.run { - text = bold(fragment.getString(R.string.txtAdditives)) + ) = additivesView.run { movementMethod = LinkMovementMethod.getInstance() - append(" ") - append("\n") isClickable = true - movementMethod = LinkMovementMethod.getInstance() - additives.dropLast(1).forEach { - append(getAdditiveTag(it, apiClientForWikiData, fragment, compositeDisposable)) - append("\n") - } - append(getAdditiveTag(additives.last(), apiClientForWikiData, fragment, compositeDisposable)) + text = SpannableStringBuilder() + .bold { append(fragment.getString(R.string.txtAdditives)) } + .apply { + additives.forEach { + append("\n") + append(getAdditiveTag(it, + apiClientForWikiData, + fragment, + compositeDisposable)) + } + } } /** @@ -62,7 +64,10 @@ object AdditiveFragmentHelper { * @param apiClientForWikiData object of WikidataApiClient * @param fragment holds a reference to the calling fragment */ - private fun getAdditiveTag(additive: AdditiveName, apiClientForWikiData: WikiDataApiClient, fragment: BaseFragment, compositeDisposable: CompositeDisposable): CharSequence { + private fun getAdditiveTag(additive: AdditiveName, + apiClientForWikiData: WikiDataApiClient, + fragment: BaseFragment, + compositeDisposable: CompositeDisposable): CharSequence { val activity = fragment.requireActivity() val clickableSpan = object : ClickableSpan() { override fun onClick(view: View) { @@ -77,16 +82,18 @@ object AdditiveFragmentHelper { } return SpannableStringBuilder().also { - it.append(additive.name) - it.setSpan(clickableSpan, 0, it.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + it.inSpans(clickableSpan) { append(additive.name) } // if the additive has an overexposure risk ("high" or "moderate") then append the warning message to it if (additive.hasOverexposureData()) { - val isHighRisk = additive.overexposureRisk.equals("high", ignoreCase = true) + val isHighRisk = additive.overexposureRisk.equals("high", true) - val riskIcon = + val riskIcon = ( if (isHighRisk) ContextCompat.getDrawable(activity, R.drawable.ic_additive_high_risk) else ContextCompat.getDrawable(activity, R.drawable.ic_additive_moderate_risk) + )?.apply { + setBounds(0, 0, this.intrinsicWidth, this.intrinsicHeight) + }!! val riskWarningStr = if (isHighRisk) fragment.getString(R.string.overexposure_high) else fragment.getString(R.string.overexposure_moderate) @@ -94,13 +101,10 @@ object AdditiveFragmentHelper { if (isHighRisk) ContextCompat.getColor(activity, R.color.overexposure_high) else ContextCompat.getColor(activity, R.color.overexposure_moderate) - riskIcon!!.setBounds(0, 0, riskIcon.intrinsicWidth, riskIcon.intrinsicHeight) - val iconSpan = ImageSpan(riskIcon, DynamicDrawableSpan.ALIGN_BOTTOM) - it.append(" - ") // this will be replaced with the risk icon - it.setSpan(iconSpan, it.length - 2, it.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - it.append(riskWarningStr) - it.setSpan(ForegroundColorSpan(riskWarningColor), it.length - riskWarningStr.length, it.length, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + it.append(" ") + it.inSpans(ImageSpan(riskIcon, DynamicDrawableSpan.ALIGN_BOTTOM)) { it.append("-") } + it.append(" ") + it.color(riskWarningColor) { append(riskWarningStr) } } } } 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 bb70ea9a2ee0..b71f682531de 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 @@ -19,6 +19,7 @@ import android.Manifest.permission import android.app.Activity import android.content.pm.PackageManager import android.os.Build +import android.text.SpannableStringBuilder import android.util.Log import android.util.TypedValue import android.view.LayoutInflater @@ -29,6 +30,7 @@ import android.widget.TextView import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.text.bold import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.afollestad.materialdialogs.MaterialDialog @@ -148,7 +150,10 @@ class ProductCompareAdapter( // Quantity if (!product.quantity.isNullOrBlank()) { - holder.binding.productComparisonQuantity.text = "${bold(activity.getString(R.string.compare_quantity))} ${product.quantity}" + holder.binding.productComparisonQuantity.text = "${ + SpannableStringBuilder() + .bold { append(activity.getString(R.string.compare_quantity)) } + } ${product.quantity}" } else { holder.binding.productComparisonQuantity.visibility = View.INVISIBLE } @@ -156,7 +161,8 @@ class ProductCompareAdapter( // Brands val brands = product.brands if (!brands.isNullOrBlank()) { - holder.binding.productComparisonBrand.text = bold(activity.getString(R.string.compare_brands)) + holder.binding.productComparisonBrand.text = SpannableStringBuilder() + .bold { append(activity.getString(R.string.compare_brands)) } holder.binding.productComparisonBrand.append(" ") val brandsList = brands.split(",") brandsList.dropLast(1).forEach { brand -> @@ -237,7 +243,8 @@ class ProductCompareAdapter( .doOnError { Log.e(ProductCompareAdapter::class.simpleName, "loadAdditives", it) } .subscribe { additives -> if (additives.isNotEmpty()) { - val additivesBuilder = StringBuilder(bold(activity.getString(R.string.compare_additives))) + val additivesBuilder = StringBuilder(SpannableStringBuilder() + .bold { append(activity.getString(R.string.compare_additives)) }) .append(" ").append("\n") additives.dropLast(1).forEach { additive -> additivesBuilder.append(additive.name).append("\n") diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/ProductFragmentPagerAdapter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/ProductFragmentPagerAdapter.kt index 06f37779ff9a..f24f8c88862a 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/ProductFragmentPagerAdapter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/ProductFragmentPagerAdapter.kt @@ -1,24 +1,20 @@ package openfoodfacts.github.scrachx.openfood.features.product -import androidx.annotation.StringRes import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import openfoodfacts.github.scrachx.openfood.features.shared.BaseFragment import openfoodfacts.github.scrachx.openfood.models.ProductState -class ProductFragmentPagerAdapter(private val fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) { +class ProductFragmentPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) { private val fragments = mutableListOf() private val tabsTitles = mutableListOf() - fun add(fragment: BaseFragment, tabTitle: String) { - fragments.add(fragment) - tabsTitles.add(tabTitle) + operator fun plusAssign(entry: Pair) { + fragments += entry.first + tabsTitles += entry.second } - fun add(fragment: BaseFragment, @StringRes tabTitleRes: Int) { - fragments.add(fragment) - tabsTitles.add(fragmentActivity.getString(tabTitleRes)) - } + fun add(entry: Pair) = plusAssign(entry) override fun createFragment(i: Int) = fragments[i] diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt index 2904ee09edd3..05d32dda4936 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt @@ -233,25 +233,26 @@ class ProductEditActivity : AppCompatActivity() { private fun setupViewPager(viewPager: ViewPager2) { // Initialize fragments - val adapterResult = ProductFragmentPagerAdapter(this) fragmentsBundle.putSerializable("product", mProduct) editOverviewFragment.arguments = fragmentsBundle ingredientsFragment.arguments = fragmentsBundle - adapterResult.add(editOverviewFragment, R.string.overview) - adapterResult.add(ingredientsFragment, R.string.ingredients) + val adapterResult = ProductFragmentPagerAdapter(this).apply { + this += editOverviewFragment to getString(R.string.overview) + this += ingredientsFragment to getString(R.string.ingredients) + } // If on off or opff, add Nutrition Facts fragment when { isFlavors(OFF, OPFF) -> { nutritionFactsFragment.arguments = fragmentsBundle - adapterResult.add(nutritionFactsFragment, R.string.nutrition_facts) + adapterResult += nutritionFactsFragment to getString(R.string.nutrition_facts) } isFlavors(OBF, OPF) -> { binding.textNutritionFactsIndicator.setText(R.string.photos) addProductPhotosFragment.arguments = fragmentsBundle - adapterResult.add(addProductPhotosFragment, R.string.photos) + adapterResult += addProductPhotosFragment to getString(R.string.photos) } } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CategoryProductHelper.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CategoryProductHelper.kt index 1e2705fe163f..da60e51f0301 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CategoryProductHelper.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/CategoryProductHelper.kt @@ -8,6 +8,8 @@ import android.text.style.* import android.view.View import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.core.text.bold +import androidx.core.text.inSpans import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.addTo import openfoodfacts.github.scrachx.openfood.R @@ -16,7 +18,6 @@ import openfoodfacts.github.scrachx.openfood.features.shared.BaseFragment import openfoodfacts.github.scrachx.openfood.models.entities.category.CategoryName import openfoodfacts.github.scrachx.openfood.network.WikiDataApiClient import openfoodfacts.github.scrachx.openfood.utils.SearchType -import openfoodfacts.github.scrachx.openfood.utils.bold import openfoodfacts.github.scrachx.openfood.utils.showBottomSheet class CategoryProductHelper( @@ -30,33 +31,35 @@ class CategoryProductHelper( private set fun showCategories() = categoryText.let { - it.text = bold(fragment.getString(R.string.txtCategories)) it.movementMethod = LinkMovementMethod.getInstance() - it.append(" ") it.isClickable = true it.movementMethod = LinkMovementMethod.getInstance() + + val text = SpannableStringBuilder() + .bold { append(fragment.getString(R.string.txtCategories)) } + .append(" ") + if (categories.isEmpty()) { it.visibility = View.GONE } else { it.visibility = View.VISIBLE // Add all the categories to text view and link them to wikidata is possible - for (category in categories) { - val categoryName = getCategoriesTag(category) + categories.forEach { category -> // Add category name to text view - it.append(categoryName) + text.append(getCategoriesTag(category)) // Add a comma if not the last item - if (category != categories.last()) it.append(", ") + if (category != categories.last()) text.append(", ") - if (category.categoryTag != null && category.categoryTag == "en:alcoholic-beverages") { + if (category.categoryTag == "en:alcoholic-beverages") { containsAlcohol = true } } } + it.text = text } private fun getCategoriesTag(category: CategoryName): CharSequence { - val spannableStringBuilder = SpannableStringBuilder() val clickableSpan = object : ClickableSpan() { override fun onClick(view: View) { if (category.isWikiDataIdPresent == true) { @@ -75,11 +78,12 @@ class CategoryProductHelper( } } } - spannableStringBuilder.append(category.name) - spannableStringBuilder.setSpan(clickableSpan, 0, spannableStringBuilder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + val spannableStringBuilder = SpannableStringBuilder() + .inSpans(clickableSpan) { append(category.name) } if (!category.isNotNull) { - val iss = StyleSpan(Typeface.ITALIC) //Span to make text italic - spannableStringBuilder.setSpan(iss, 0, spannableStringBuilder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + // Span to make text italic + spannableStringBuilder.setSpan(StyleSpan(Typeface.ITALIC), 0, spannableStringBuilder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } return spannableStringBuilder } @@ -93,21 +97,12 @@ class CategoryProductHelper( } val riskAlcoholConsumption = fragment.getString(R.string.risk_alcohol_consumption) alcoholAlertText.visibility = View.VISIBLE - alcoholAlertText.text = SpannableStringBuilder().also { - it.append("- ") - it.setSpan( - ImageSpan(alcoholAlertIcon, DynamicDrawableSpan.ALIGN_BOTTOM), - 0, - 1, - Spanned.SPAN_INCLUSIVE_INCLUSIVE - ) - it.append(riskAlcoholConsumption) - it.setSpan( - ForegroundColorSpan(ContextCompat.getColor(fragment.requireContext(), R.color.red)), - it.length - riskAlcoholConsumption.length, - it.length, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) + alcoholAlertText.text = SpannableStringBuilder().apply { + inSpans(ImageSpan(alcoholAlertIcon, DynamicDrawableSpan.ALIGN_BOTTOM)) { append("-") } + append(" ") + inSpans(ForegroundColorSpan(ContextCompat.getColor(fragment.requireContext(), R.color.red))) + { append(riskAlcoholConsumption) } + } } } \ No newline at end of file diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ProductViewActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ProductViewActivity.kt index 6545f81b2c08..03a3c11b313b 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ProductViewActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ProductViewActivity.kt @@ -86,7 +86,7 @@ class ProductViewActivity : BaseActivity(), OnRefreshListener { setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) - if (!checkIntentAction()) { + if (!checkActionFromIntent()) { productState = requireProductState() initViews() } @@ -100,7 +100,7 @@ class ProductViewActivity : BaseActivity(), OnRefreshListener { override fun onResume() { super.onResume() // To check if the activity is resumed and new product is opened through a deep link. If not, then only we can call requireProductState() - if (!checkIntentAction()) + if (!checkActionFromIntent()) productState = requireProductState().also { adapterResult!!.refresh(it) } } @@ -119,24 +119,12 @@ class ProductViewActivity : BaseActivity(), OnRefreshListener { * * @param barcode from the URL. */ - private fun loadProductDataFromUrl(barcode: String) { - client.getProductStateFull(barcode, Utils.HEADER_USER_AGENT_SCAN) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError { - Log.w(this::class.simpleName, "Failed to load product data.", it) - finish() - } - .subscribe { pState -> - productState = pState - intent.putExtra(KEY_STATE, pState) - // Adding check on productState.getProduct() to avoid null pointer exception (happens in setViewPager()) when product not found - if (productState != null && productState!!.product != null) { - initViews() - } else { - finish() - } - }.addTo(disp) - } + private fun fetchProduct(barcode: String) = client.getProductStateFull(barcode, Utils.HEADER_USER_AGENT_SCAN) + .observeOn(AndroidSchedulers.mainThread()) + .doOnError { + Log.w(this::class.simpleName, "Failed to load product $barcode.", it) + finish() + } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -206,18 +194,26 @@ class ProductViewActivity : BaseActivity(), OnRefreshListener { PERFORM_OCR, SEND_UPDATED } - // to check if an product is opened through a deep link (from browser) - private fun checkIntentAction(): Boolean { - return when (intent.action) { - Intent.ACTION_VIEW -> { - val data = intent.data - val paths = data.toString().split("/").toTypedArray() // paths[4] - productState = ProductState() - loadProductDataFromUrl(paths[4]) - true - } - else -> false + /** To check if an product is opened through a deep link */ + private fun checkActionFromIntent() = when (intent.action) { + Intent.ACTION_VIEW -> { + val data = intent.data + val barcode = data.toString().split("/")[4] + + // Fetch product from server, then initialize views + fetchProduct(barcode).subscribe { pState -> + productState = pState + intent.putExtra(KEY_STATE, pState) + // Adding check on productState.getProduct() to avoid null pointer exception (happens in setViewPager()) when product not found + if (productState != null && productState!!.product != null) { + initViews() + } else { + finish() + } + }.addTo(disp) + true } + else -> false } companion object { @@ -241,28 +237,36 @@ class ProductViewActivity : BaseActivity(), OnRefreshListener { val titles = activity.resources.getStringArray(R.array.nav_drawer_items_product) val newTitles = activity.resources.getStringArray(R.array.nav_drawer_new_items_product) - adapter.add(SummaryProductFragment.newInstance(productState), titles[0]) + adapter += SummaryProductFragment.newInstance(productState) to titles[0] val preferences = PreferenceManager.getDefaultSharedPreferences(activity) // Add Ingredients fragment for off, obf and opff - if (isFlavors(OFF, OBF, OPFF)) adapter.add(IngredientsProductFragment.newInstance(productState), titles[1]) + if (isFlavors(OFF, OBF, OPFF)) { + adapter += IngredientsProductFragment.newInstance(productState) to titles[1] + } - if (isFlavors(OFF, OPFF)) adapter.add(NutritionProductFragment.newInstance(productState), titles[2]) + if (isFlavors(OFF, OPFF)) { + adapter += NutritionProductFragment.newInstance(productState) to titles[2] + } - if (isFlavors(OFF)) adapter.add(EnvironmentProductFragment.newInstance(productState), titles[4]) + if (isFlavors(OFF)) { + adapter += EnvironmentProductFragment.newInstance(productState) to titles[4] + } if (isFlavors(OFF, OPFF, OBF) && isPhotoMode(activity) || isFlavors(OPF)) { - adapter.add(ProductPhotosFragment.newInstance(productState), newTitles[0]) + adapter += ProductPhotosFragment.newInstance(productState) to newTitles[0] } if (isFlavors(OFF, OBF)) { - adapter.add(ServerAttributesFragment.newInstance(productState), activity.getString(R.string.synthesis_tab)) + adapter += ServerAttributesFragment.newInstance(productState) to activity.getString(R.string.synthesis_tab) } - if (isFlavors(OBF)) adapter.add(IngredientsAnalysisProductFragment.newInstance(productState), newTitles[1]) + if (isFlavors(OBF)) { + adapter += IngredientsAnalysisProductFragment.newInstance(productState) to newTitles[1] + } if (preferences.getBoolean(activity.getString(R.string.pref_contribution_tab_key), false)) { - adapter.add(ContributorsFragment.newInstance(productState), activity.getString(R.string.contribution_tab)) + adapter += ContributorsFragment.newInstance(productState) to activity.getString(R.string.contribution_tab) } viewPager.adapter = adapter 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 fc58ca9ceea3..00b569f181fb 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 @@ -3,11 +3,13 @@ package openfoodfacts.github.scrachx.openfood.features.product.view.environment import android.app.Activity import android.content.Intent import android.os.Bundle +import android.text.SpannableStringBuilder import android.text.method.LinkMovementMethod import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.text.HtmlCompat +import androidx.core.text.bold import com.squareup.picasso.Picasso import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxkotlin.addTo @@ -94,7 +96,8 @@ class EnvironmentProductFragment : BaseFragment() { val carbonFootprintNutriment = nutriments[Nutriments.CARBON_FOOTPRINT] if (carbonFootprintNutriment != null) { - binding.textCarbonFootprint.text = bold(getString(R.string.textCarbonFootprint)) + binding.textCarbonFootprint.text = SpannableStringBuilder() + .bold { append(getString(R.string.textCarbonFootprint)) } binding.textCarbonFootprint.append(carbonFootprintNutriment.for100gInUnits) binding.textCarbonFootprint.append(carbonFootprintNutriment.unit) } else { @@ -111,7 +114,8 @@ class EnvironmentProductFragment : BaseFragment() { val packaging = product.packaging if (!packaging.isNullOrEmpty()) { - binding.packagingText.text = bold(getString(R.string.packaging_environmentTab)) + binding.packagingText.text = SpannableStringBuilder() + .bold { append(getString(R.string.packaging_environmentTab)) } binding.packagingText.append(" ") binding.packagingText.append(packaging.split(',').toString().removeSurrounding("[", "]")) } else { @@ -121,7 +125,8 @@ class EnvironmentProductFragment : BaseFragment() { val recyclingInstructionsToDiscard = product.recyclingInstructionsToDiscard if (!recyclingInstructionsToDiscard.isNullOrEmpty()) { // TODO: 02/03/2021 i18n - binding.recyclingInstructionToDiscard.text = bold("Recycling instructions - To discard: ") + binding.recyclingInstructionToDiscard.text = SpannableStringBuilder() + .bold { append("Recycling instructions - To discard: ") } binding.recyclingInstructionToDiscard.append(recyclingInstructionsToDiscard) } else { binding.recyclingInstructionsDiscardCv.visibility = View.GONE @@ -130,7 +135,8 @@ class EnvironmentProductFragment : BaseFragment() { val recyclingInstructionsToRecycle = product.recyclingInstructionsToRecycle if (!recyclingInstructionsToRecycle.isNullOrEmpty()) { // TODO: 02/03/2021 i18n - binding.recyclingInstructionToRecycle.text = bold("Recycling instructions - To recycle: ") + binding.recyclingInstructionToRecycle.text = SpannableStringBuilder() + .bold { append("Recycling instructions - To recycle: ") } binding.recyclingInstructionToRecycle.append(recyclingInstructionsToRecycle) } else { binding.recyclingInstructionsRecycleCv.visibility = View.GONE diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IIngredientsProductPresenter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IIngredientsProductPresenter.kt index 8ea88a91ad86..ea8a3da08582 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IIngredientsProductPresenter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IIngredientsProductPresenter.kt @@ -31,8 +31,9 @@ interface IIngredientsProductPresenter { interface View { fun showAdditives(additives: List) - fun showAdditivesState(state: ProductInfoState) + fun setAdditivesState(state: ProductInfoState) + fun showAllergens(allergens: List) - fun showAllergensState(state: ProductInfoState) + fun setAllergensState(state: ProductInfoState) } } \ No newline at end of file diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductFragment.kt index 430a715a9f32..b68ce413ff9f 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductFragment.kt @@ -30,6 +30,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.browser.customtabs.CustomTabsIntent +import androidx.core.text.bold import androidx.viewpager2.widget.ViewPager2 import com.afollestad.materialdialogs.MaterialDialog import com.squareup.picasso.Picasso @@ -67,8 +68,9 @@ import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import openfoodfacts.github.scrachx.openfood.network.WikiDataApiClient import openfoodfacts.github.scrachx.openfood.repositories.ProductRepository import openfoodfacts.github.scrachx.openfood.utils.* +import openfoodfacts.github.scrachx.openfood.utils.ProductInfoState.EMPTY +import openfoodfacts.github.scrachx.openfood.utils.ProductInfoState.LOADING import java.io.File -import java.util.regex.Pattern import javax.inject.Inject @AndroidEntryPoint @@ -94,7 +96,12 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. lateinit var picasso: Picasso private val performOCRLauncher = registerForActivityResult(PerformOCRContract()) - { result -> if (result) onRefresh() } + { result -> + if (result) { + ingredientExtracted = true + onRefresh() + } + } private val updateImagesLauncher = registerForActivityResult(SendUpdatedImgContract()) { result -> if (result) onRefresh() } private val loginLauncher = registerForActivityResult(LoginContract()) @@ -160,25 +167,30 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. val otherNutritionTags = product.otherNutritionTags if (vitaminTagsList.isNotEmpty()) { binding.cvVitaminsTagsText.visibility = View.VISIBLE - binding.vitaminsTagsText.text = bold(getString(R.string.vitamin_tags_text)) - binding.vitaminsTagsText.append(buildStringBuilder(vitaminTagsList, SPACE)) + binding.vitaminsTagsText.text = SpannableStringBuilder() + .bold { append(getString(R.string.vitamin_tags_text)) } + .append(tagListToString(vitaminTagsList)) } if (aminoAcidTagsList.isNotEmpty()) { binding.cvAminoAcidTagsText.visibility = View.VISIBLE - binding.aminoAcidTagsText.text = bold(getString(R.string.amino_acid_tags_text)) - binding.aminoAcidTagsText.append(buildStringBuilder(aminoAcidTagsList, SPACE)) + binding.aminoAcidTagsText.text = SpannableStringBuilder() + .bold { append(getString(R.string.amino_acid_tags_text)) } + .append(tagListToString(aminoAcidTagsList)) } if (mineralTags.isNotEmpty()) { binding.cvMineralTagsText.visibility = View.VISIBLE - binding.mineralTagsText.text = bold(getString(R.string.mineral_tags_text)) - binding.mineralTagsText.append(buildStringBuilder(mineralTags, SPACE)) + binding.mineralTagsText.text = SpannableStringBuilder() + .bold { append(getString(R.string.mineral_tags_text)) } + .append(tagListToString(mineralTags)) } if (otherNutritionTags.isNotEmpty()) { binding.otherNutritionTags.visibility = View.VISIBLE - binding.otherNutritionTags.text = bold(getString(R.string.other_tags_text)) - binding.otherNutritionTags.append(buildStringBuilder(otherNutritionTags, SPACE)) + binding.otherNutritionTags.text = SpannableStringBuilder() + .bold { append(getString(R.string.other_tags_text)) } + .append(tagListToString(otherNutritionTags)) } - binding.textAdditiveProduct.text = bold(getString(R.string.txtAdditives)) + binding.textAdditiveProduct.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtAdditives)) } presenter.loadAdditives() if (!product.getImageIngredientsUrl(langCode).isNullOrBlank()) { @@ -189,9 +201,7 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. // Load Image if isLowBatteryMode is false if (!requireContext().isBatteryLevelLow()) { - picasso - .load(product.getImageIngredientsUrl(langCode)) - .into(binding.imageViewIngredients) + picasso.load(product.getImageIngredientsUrl(langCode)).into(binding.imageViewIngredients) } else { binding.imageViewIngredients.visibility = View.GONE } @@ -202,13 +212,13 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. if (mSendProduct != null && !mSendProduct!!.imgupload_ingredients.isNullOrBlank()) { binding.addPhotoLabel.visibility = View.GONE ingredients = mSendProduct!!.imgupload_ingredients - Picasso.get().load(LOCALE_FILE_SCHEME + ingredients).config(Bitmap.Config.RGB_565).into(binding.imageViewIngredients) + picasso.load(LOCALE_FILE_SCHEME + ingredients).config(Bitmap.Config.RGB_565).into(binding.imageViewIngredients) } val allergens = getAllergens() if (!product.getIngredientsText(langCode).isNullOrEmpty()) { binding.cvTextIngredientProduct.visibility = View.VISIBLE var txtIngredients = SpannableStringBuilder(product.getIngredientsText(langCode)!!.replace("_", "")) - txtIngredients = setSpanBoldBetweenTokens(txtIngredients, allergens) + txtIngredients = boldAllergens(txtIngredients, allergens) if (product.getIngredientsText(langCode).isNullOrEmpty()) { binding.extractIngredientsPrompt.visibility = View.VISIBLE } @@ -227,17 +237,18 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. val language = LocaleHelper.getLanguage(context) binding.cvTextTraceProduct.visibility = View.VISIBLE binding.textTraceProduct.movementMethod = LinkMovementMethod.getInstance() - binding.textTraceProduct.text = bold(getString(R.string.txtTraces)) + binding.textTraceProduct.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtTraces)) } binding.textTraceProduct.append(" ") val traces = product.traces.split(",") - traces.withIndex().forEach { (i, trace) -> - if (i > 0) binding.textTraceProduct.append(", ") - binding.textTraceProduct.append(Utils.getClickableText( - getTracesName(language, trace), + + binding.textTraceProduct.append(traces.joinToString(", ") { + getSearchLinkText( + getTracesName(language, it), SearchType.TRACE, requireActivity() - )) - } + ) + }) } else { binding.cvTextTraceProduct.visibility = View.GONE } @@ -263,13 +274,9 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. return if (allergenName != null) allergenName.name else tag } - private fun buildStringBuilder(stringList: List, prefix: String) = StringBuilder().apply { - append(prefix) - stringList.forEach { otherSubstance -> - append(trimLanguagePartFromString(otherSubstance)) - append(", ") - } - } + private fun tagListToString(tagList: List) = + tagList.joinToString(", ", " ") { it.substring(3) } + private fun getAllergensTag(allergen: AllergenName): CharSequence { val ssb = SpannableStringBuilder() @@ -299,32 +306,27 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. return ssb } - /** - * @return the string after trimming the language code from the tags - * like it returns folic-acid for en:folic-acid - */ - private fun trimLanguagePartFromString(string: String) = string.substring(3) - - private fun setSpanBoldBetweenTokens(text: CharSequence, allergens: List): SpannableStringBuilder { - return SpannableStringBuilder(text).also { - val m = INGREDIENT_PATTERN.matcher(it) - while (m.find()) { - val tm = m.group() - val allergenValue = tm.replace("[(),.-]+".toRegex(), "") - for (allergen in allergens) { - if (allergen.equals(allergenValue, ignoreCase = true)) { - var start = m.start() - var end = m.end() - if (tm.contains("(")) { - start += 1 - } else if (tm.contains(")")) { - end -= 1 - } - it.setSpan(StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + private fun boldAllergens(ingredientsText: CharSequence, allergenTags: List): SpannableStringBuilder { + return SpannableStringBuilder(ingredientsText).also { ssb -> + INGREDIENT_REGEX.findAll(ingredientsText).forEach { match -> + + val allergenTxt = match.value + .replace("[()]+".toRegex(), "") + .replace("[,.-]".toRegex(), " ") + allergenTags.find { tag -> tag.contains(allergenTxt, true) }?.let { + var start = match.range.first + var end = match.range.last + 1 + if ("(" in match.value) { + start += 1 } + if (")" in match.value) { + end -= 1 + } + ssb.setSpan(StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } } - it.insert(0, bold(getString(R.string.txtIngredients) + ' ')) + ssb.insert(0, SpannableStringBuilder() + .bold { append(getString(R.string.txtIngredients) + ' ') }) } } @@ -332,25 +334,22 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. showAdditives(additives, binding.textAdditiveProduct, wikidataClient, this, disp) } - override fun showAdditivesState(state: ProductInfoState) { + override fun setAdditivesState(state: ProductInfoState) { when (state) { - ProductInfoState.LOADING -> { + LOADING -> { binding.cvTextAdditiveProduct.visibility = View.VISIBLE binding.textAdditiveProduct.append(getString(R.string.txtLoading)) } - ProductInfoState.EMPTY -> binding.cvTextAdditiveProduct.visibility = View.GONE + EMPTY -> binding.cvTextAdditiveProduct.visibility = View.GONE } } override fun showAllergens(allergens: List) { binding.textSubstanceProduct.movementMethod = LinkMovementMethod.getInstance() - binding.textSubstanceProduct.text = bold(getString(R.string.txtSubstances)) - binding.textSubstanceProduct.append(" ") - allergens.withIndex().forEach { (i, allergen) -> - binding.textSubstanceProduct.append(getAllergensTag(allergen)) - // Add comma if not the last item - if (i != allergens.lastIndex) binding.textSubstanceProduct.append(", ") - } + binding.textSubstanceProduct.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtSubstances)) } + .append(" ") + .append(allergens.joinToString(", ") { getAllergensTag(it) }) } @@ -373,13 +372,13 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. } } - override fun showAllergensState(state: ProductInfoState) { + override fun setAllergensState(state: ProductInfoState) { when (state) { - ProductInfoState.LOADING -> { + LOADING -> { binding.textSubstanceProduct.visibility = View.VISIBLE binding.textSubstanceProduct.append(getString(R.string.txtLoading)) } - ProductInfoState.EMPTY -> binding.textSubstanceProduct.visibility = View.GONE + EMPTY -> binding.textSubstanceProduct.visibility = View.GONE } } @@ -398,7 +397,7 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. fun extractIngredients() { if (!isAdded) return - ingredientExtracted = true + val settings = requireContext().getLoginPreferences() if (settings.getString("user", "")!!.isEmpty()) { showSignInDialog() @@ -438,13 +437,9 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. } } - private fun newIngredientImage() { - doChooseOrTakePhotos() - } + private fun newIngredientImage() = doChooseOrTakePhotos() - override fun doOnPhotosPermissionGranted() { - newIngredientImage() - } + override fun doOnPhotosPermissionGranted() = newIngredientImage() private fun onPhotoReturned(newPhotoFile: File) { val image = ProductImage(productState.code!!, ProductImageField.INGREDIENTS, newPhotoFile) @@ -472,7 +467,7 @@ class IngredientsProductFragment : BaseFragment(), IIngredientsProductPresenter. } companion object { - val INGREDIENT_PATTERN: Pattern = Pattern.compile("[\\p{L}\\p{Nd}(),.-]+") + val INGREDIENT_REGEX = Regex("[\\p{L}\\p{Nd}(),.-]+") fun newInstance(productState: ProductState) = IngredientsProductFragment().apply { arguments = Bundle().apply { putSerializable(KEY_STATE, productState) diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductPresenter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductPresenter.kt index 4b06a7c06ce8..91c33449b2eb 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductPresenter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/ingredients/IngredientsProductPresenter.kt @@ -24,11 +24,11 @@ import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.toObservable import io.reactivex.schedulers.Schedulers import openfoodfacts.github.scrachx.openfood.models.Product -import openfoodfacts.github.scrachx.openfood.models.entities.additive.AdditiveName import openfoodfacts.github.scrachx.openfood.models.entities.allergen.AllergenName import openfoodfacts.github.scrachx.openfood.repositories.ProductRepository import openfoodfacts.github.scrachx.openfood.utils.LocaleHelper -import openfoodfacts.github.scrachx.openfood.utils.ProductInfoState +import openfoodfacts.github.scrachx.openfood.utils.ProductInfoState.EMPTY +import openfoodfacts.github.scrachx.openfood.utils.ProductInfoState.LOADING /** * Created by Lobster on 17.03.18. @@ -44,32 +44,30 @@ class IngredientsProductPresenter( override fun loadAdditives() { val additivesTags = product.additivesTags if (additivesTags.isEmpty()) { - view.showAdditivesState(ProductInfoState.EMPTY) + view.setAdditivesState(EMPTY) return } val languageCode = LocaleHelper.getLanguage(context) additivesTags.toObservable() - .flatMapSingle { tag: String? -> - productRepository.getAdditiveByTagAndLanguageCode(tag, languageCode).flatMap { categoryName: AdditiveName -> + .flatMapSingle { tag -> + productRepository.getAdditiveByTagAndLanguageCode(tag, languageCode).flatMap { categoryName -> if (categoryName.isNull) { productRepository.getAdditiveByTagAndDefaultLanguageCode(tag) - } else { - Single.just(categoryName) - } + } else Single.just(categoryName) } } .filter { it.isNotNull } .toList() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .doOnSubscribe { view.showAdditivesState(ProductInfoState.LOADING) } + .doOnSubscribe { view.setAdditivesState(LOADING) } .doOnError { Log.e(IngredientsProductPresenter::class.simpleName, "loadAdditives", it) - view.showAdditivesState(ProductInfoState.EMPTY) + view.setAdditivesState(EMPTY) } - .subscribe { additives: List -> + .subscribe { additives -> if (additives.isEmpty()) { - view.showAdditivesState(ProductInfoState.EMPTY) + view.setAdditivesState(EMPTY) } else { view.showAdditives(additives) } @@ -80,31 +78,29 @@ class IngredientsProductPresenter( override fun loadAllergens() { val allergenTags = product.allergensTags if (allergenTags.isEmpty()) { - view.showAllergensState(ProductInfoState.EMPTY) + view.setAllergensState(EMPTY) return } val languageCode = LocaleHelper.getLanguage(context) allergenTags.toObservable() - .flatMapSingle { tag: String? -> + .flatMapSingle { tag -> productRepository.getAllergenByTagAndLanguageCode(tag, languageCode).flatMap { allergenName: AllergenName -> if (allergenName.isNull) { - return@flatMap productRepository.getAllergenByTagAndDefaultLanguageCode(tag) - } else { - return@flatMap Single.just(allergenName) - } + productRepository.getAllergenByTagAndDefaultLanguageCode(tag) + } else Single.just(allergenName) } } .toList() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .doOnSubscribe { view.showAllergensState(ProductInfoState.LOADING) } - .doOnError { e: Throwable? -> + .doOnSubscribe { view.setAllergensState(LOADING) } + .doOnError { e -> Log.e(IngredientsProductPresenter::class.simpleName, "loadAllergens", e) - view.showAllergensState(ProductInfoState.EMPTY) + view.setAllergensState(EMPTY) } - .subscribe { allergens: List -> + .subscribe { allergens -> if (allergens.isEmpty()) { - view.showAllergensState(ProductInfoState.EMPTY) + view.setAllergensState(EMPTY) } else { view.showAllergens(allergens) } 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 33abf2a62809..ddc0c0d3a657 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 @@ -38,6 +38,7 @@ import android.widget.Spinner import androidx.browser.customtabs.CustomTabsIntent import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.text.bold import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog @@ -210,9 +211,10 @@ class NutritionProductFragment : BaseFragment(), CustomTabActivityHelper.Connect } else if (servingSize.contains("oz", true) && sharedPreferences.getString("volumeUnitPreference", "l") == "l") { servingSize = UnitUtils.getServingInL(servingSize) } - binding.textServingSize.text = bold(getString(R.string.txtServingSize)) - binding.textServingSize.append(" ") - binding.textServingSize.append(servingSize) + binding.textServingSize.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtServingSize)) } + .append(" ") + .append(servingSize) } if (arguments != null) { 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 cc336d86fda2..2542baba4ed2 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 @@ -34,6 +34,7 @@ import android.widget.TextView import android.widget.Toast import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.res.ResourcesCompat +import androidx.core.text.bold import androidx.core.view.updatePadding import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -50,7 +51,6 @@ import openfoodfacts.github.scrachx.openfood.AppFlavors.isFlavors import openfoodfacts.github.scrachx.openfood.R import openfoodfacts.github.scrachx.openfood.analytics.AnalyticsEvent import openfoodfacts.github.scrachx.openfood.analytics.MatomoAnalytics -import openfoodfacts.github.scrachx.openfood.app.OFFApplication import openfoodfacts.github.scrachx.openfood.customtabs.CustomTabActivityHelper import openfoodfacts.github.scrachx.openfood.customtabs.CustomTabsHelper import openfoodfacts.github.scrachx.openfood.customtabs.WebViewFallback @@ -264,8 +264,15 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { this.productState = productState product = productState.product!! presenter = SummaryProductPresenter(product, this, productRepository).apply { addTo(disp) } - binding.categoriesText.text = bold(getString(R.string.txtCategories)) - binding.labelsText.text = bold(getString(R.string.txtLabels)) + + binding.categoriesText.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtCategories)) } + + binding.labelsText.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtLabels)) } + + binding.textAdditiveProduct.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtAdditives)) } // Refresh visibility of UI components binding.textBrandProduct.visibility = View.VISIBLE @@ -285,7 +292,6 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { presenter.loadCategories() presenter.loadLabels() presenter.loadProductQuestion() - binding.textAdditiveProduct.text = bold(getString(R.string.txtAdditives)) presenter.loadAdditives() presenter.loadAnalysisTags() @@ -319,7 +325,7 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { binding.textBrandProduct.text = "" pBrands.split(",").withIndex().forEach { (i, brand) -> if (i > 0) binding.textBrandProduct.append(", ") - binding.textBrandProduct.append(Utils.getClickableText( + binding.textBrandProduct.append(getSearchLinkText( brand.trim { it <= ' ' }, SearchType.BRAND, requireActivity() @@ -331,7 +337,8 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { if (product.embTags.isNotEmpty() && product.embTags.toString().trim { it <= ' ' } != "[]") { binding.embText.movementMethod = LinkMovementMethod.getInstance() - binding.embText.text = bold(getString(R.string.txtEMB)) + binding.embText.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtEMB)) } binding.embText.append(" ") val embTags = product.embTags.toString() @@ -341,7 +348,7 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { embTags.withIndex().forEach { (i, embTag) -> if (i > 0) binding.embText.append(", ") - binding.embText.append(Utils.getClickableText( + binding.embText.append(getSearchLinkText( getEmbCode(embTag).trim { it <= ' ' }, SearchType.EMB, requireActivity() @@ -689,7 +696,8 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { } override fun showLabels(labelNames: List) { - binding.labelsText.text = bold(getString(R.string.txtLabels)) + binding.labelsText.text = SpannableStringBuilder() + .bold { append(getString(R.string.txtLabels)) } binding.labelsText.isClickable = true binding.labelsText.movementMethod = LinkMovementMethod.getInstance() binding.labelsText.append(" ") diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/scanhistory/ScanHistoryActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/scanhistory/ScanHistoryActivity.kt index 181293e8b4cb..00db56af0619 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/scanhistory/ScanHistoryActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/scanhistory/ScanHistoryActivity.kt @@ -276,22 +276,18 @@ class ScanHistoryActivity : BaseActivity() { } private fun showListSortingDialog() { - val sortTypes = if (BuildConfig.FLAVOR == "off") { - arrayOf( - getString(R.string.by_title), - getString(R.string.by_brand), - getString(R.string.by_nutrition_grade), - getString(R.string.by_barcode), - getString(R.string.by_time) - ) - } else { - arrayOf( - getString(R.string.by_title), - getString(R.string.by_brand), - getString(R.string.by_time), - getString(R.string.by_barcode) - ) - } + val sortTypes = if (isFlavors(OFF)) arrayOf( + getString(R.string.by_title), + getString(R.string.by_brand), + getString(R.string.by_nutrition_grade), + getString(R.string.by_barcode), + getString(R.string.by_time) + ) else arrayOf( + getString(R.string.by_title), + getString(R.string.by_brand), + getString(R.string.by_time), + getString(R.string.by_barcode) + ) MaterialDialog.Builder(this) .title(R.string.sort_by) .items(*sortTypes) @@ -310,7 +306,6 @@ class ScanHistoryActivity : BaseActivity() { companion object { fun start(context: Context) = context.startActivity(Intent(context, ScanHistoryActivity::class.java)) - val LOG_TAG = ScanHistoryActivity::class.simpleName } 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 1a195f8c5b4e..2b5e71b31fa1 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 @@ -4,7 +4,6 @@ import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import android.os.Bundle import android.util.Log import android.view.Menu @@ -17,6 +16,7 @@ import androidx.annotation.StringRes import androidx.browser.customtabs.CustomTabsIntent import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -44,6 +44,7 @@ import openfoodfacts.github.scrachx.openfood.models.SearchInfo import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient import openfoodfacts.github.scrachx.openfood.repositories.ProductRepository import openfoodfacts.github.scrachx.openfood.utils.* +import openfoodfacts.github.scrachx.openfood.utils.SearchType.* import java.text.NumberFormat import java.util.* import javax.inject.Inject @@ -107,11 +108,11 @@ class ProductSearchActivity : BaseActivity() { if (paths[3] == "cgi" && paths[4].contains("search.pl")) { mSearchInfo.searchTitle = data.getQueryParameter("search_terms") ?: "" mSearchInfo.searchQuery = data.getQueryParameter("search_terms") ?: "" - mSearchInfo.searchType = SearchType.SEARCH + mSearchInfo.searchType = SEARCH } else { mSearchInfo.searchTitle = paths[4] mSearchInfo.searchQuery = paths[4] - mSearchInfo.searchType = SearchType.fromUrl(paths[3]) ?: SearchType.SEARCH + mSearchInfo.searchType = SearchType.fromUrl(paths[3]) ?: SEARCH } } else { @@ -141,7 +142,7 @@ class ProductSearchActivity : BaseActivity() { searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { mSearchInfo.searchQuery = query - mSearchInfo.searchType = SearchType.SEARCH + mSearchInfo.searchType = SEARCH newSearchQuery() return true } @@ -157,7 +158,7 @@ class ProductSearchActivity : BaseActivity() { return true } }) - if (SearchType.CONTRIBUTOR == mSearchInfo.searchType) { + if (CONTRIBUTOR == mSearchInfo.searchType) { menu.findItem(R.id.action_set_type).isVisible = true } return true @@ -182,16 +183,11 @@ class ProductSearchActivity : BaseActivity() { title(R.string.show_by) items(*contributionTypes) itemsCallback { _, _, position, _ -> - when (position) { - 1, 2, 3, 4, 5 -> { - contributionType = position - newSearchQuery() - } - else -> { - contributionType = 0 - newSearchQuery() - } + contributionType = when (position) { + 1, 2, 3, 4, 5 -> position + else -> 0 } + newSearchQuery() } }.show() true @@ -203,7 +199,7 @@ class ProductSearchActivity : BaseActivity() { private fun setupHungerGames() { val sharedPref = PreferenceManager.getDefaultSharedPreferences(this) val actualCountryTag = sharedPref.getString(getString(R.string.pref_country_key), "") - if ("" == actualCountryTag) { + if (actualCountryTag.isNullOrBlank()) { productRepository.getCountryByCC2OrWorld(LocaleHelper.getLocaleFromContext().country) .observeOn(AndroidSchedulers.mainThread()) .map { it.tag } @@ -216,7 +212,7 @@ class ProductSearchActivity : BaseActivity() { } private fun setupUrlHungerGames(countryTag: String?) { - val url = Uri.parse("https://hunger.openfoodfacts.org/questions?type=${mSearchInfo.searchType.url}&value_tag=${mSearchInfo.searchQuery}&country=$countryTag") + val url = "https://hunger.openfoodfacts.org/questions?type=${mSearchInfo.searchType.url}&value_tag=${mSearchInfo.searchQuery}&country=$countryTag".toUri() val builder = CustomTabsIntent.Builder() val customTabsIntent = builder.build() binding.btnHungerGames.visibility = View.VISIBLE @@ -229,33 +225,33 @@ class ProductSearchActivity : BaseActivity() { private fun newSearchQuery() { supportActionBar?.title = mSearchInfo.searchTitle when (mSearchInfo.searchType) { - SearchType.BRAND -> { + BRAND -> { supportActionBar!!.setSubtitle(R.string.brand_string) setupHungerGames() } - SearchType.LABEL -> { + LABEL -> { supportActionBar!!.subtitle = getString(R.string.label_string) setupHungerGames() } - SearchType.CATEGORY -> { + CATEGORY -> { supportActionBar!!.subtitle = getString(R.string.category_string) setupHungerGames() } - SearchType.COUNTRY -> supportActionBar!!.setSubtitle(R.string.country_string) - SearchType.ORIGIN -> supportActionBar!!.setSubtitle(R.string.origin_of_ingredients) - SearchType.MANUFACTURING_PLACE -> supportActionBar!!.setSubtitle(R.string.manufacturing_place) - SearchType.ADDITIVE -> supportActionBar!!.setSubtitle(R.string.additive_string) - SearchType.SEARCH -> supportActionBar!!.setSubtitle(R.string.search_string) - SearchType.STORE -> supportActionBar!!.setSubtitle(R.string.store_subtitle) - SearchType.PACKAGING -> supportActionBar!!.setSubtitle(R.string.packaging_subtitle) - SearchType.CONTRIBUTOR -> supportActionBar!!.subtitle = getString(R.string.contributor_string) - SearchType.ALLERGEN -> supportActionBar!!.subtitle = getString(R.string.allergen_string) - SearchType.INCOMPLETE_PRODUCT -> supportActionBar!!.title = getString(R.string.products_to_be_completed) - SearchType.STATE -> { + COUNTRY -> supportActionBar!!.setSubtitle(R.string.country_string) + ORIGIN -> supportActionBar!!.setSubtitle(R.string.origin_of_ingredients) + MANUFACTURING_PLACE -> supportActionBar!!.setSubtitle(R.string.manufacturing_place) + ADDITIVE -> supportActionBar!!.setSubtitle(R.string.additive_string) + SEARCH -> supportActionBar!!.setSubtitle(R.string.search_string) + STORE -> supportActionBar!!.setSubtitle(R.string.store_subtitle) + PACKAGING -> supportActionBar!!.setSubtitle(R.string.packaging_subtitle) + CONTRIBUTOR -> supportActionBar!!.subtitle = getString(R.string.contributor_string) + ALLERGEN -> supportActionBar!!.subtitle = getString(R.string.allergen_string) + INCOMPLETE_PRODUCT -> supportActionBar!!.title = getString(R.string.products_to_be_completed) + STATE -> { // TODO: 26/07/2020 use resources supportActionBar!!.subtitle = "State" } - SearchType.TRACE -> supportActionBar!!.setSubtitle(R.string.traces) + TRACE -> supportActionBar!!.setSubtitle(R.string.traces) else -> error("No match case found for ${mSearchInfo.searchType}") } binding.progressBar.visibility = View.VISIBLE @@ -296,28 +292,28 @@ class ProductSearchActivity : BaseActivity() { fun loadDataFromAPI() { val searchQuery = mSearchInfo.searchQuery when (mSearchInfo.searchType) { - SearchType.BRAND -> client.getProductsByBrand(searchQuery, pageAddress) + BRAND -> client.getProductsByBrand(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_brand_products) - SearchType.COUNTRY -> client.getProductsByCountry(searchQuery, pageAddress) + COUNTRY -> client.getProductsByCountry(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_country_products) - SearchType.ORIGIN -> client.getProductsByOrigin(searchQuery, pageAddress) + ORIGIN -> client.getProductsByOrigin(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_country_products) - SearchType.MANUFACTURING_PLACE -> client.getProductsByManufacturingPlace(searchQuery, pageAddress) + MANUFACTURING_PLACE -> client.getProductsByManufacturingPlace(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_country_products) - SearchType.ADDITIVE -> client.getProductsByAdditive(searchQuery, pageAddress) + ADDITIVE -> client.getProductsByAdditive(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_additive_products) - SearchType.STORE -> client.getProductsByStore(searchQuery, pageAddress) + STORE -> client.getProductsByStore(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_store_products) - SearchType.PACKAGING -> client.getProductsByPackaging(searchQuery, pageAddress) + PACKAGING -> client.getProductsByPackaging(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_packaging_products) - SearchType.SEARCH -> { + SEARCH -> { if (isBarcodeValid(searchQuery)) { client.openProduct(searchQuery, this) } else { @@ -325,22 +321,22 @@ class ProductSearchActivity : BaseActivity() { .startSearch(R.string.txt_no_matching_products, R.string.txt_broaden_search) } } - SearchType.LABEL -> client.getProductsByLabel(searchQuery, pageAddress) + LABEL -> client.getProductsByLabel(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_label_products) - SearchType.CATEGORY -> client.getProductsByCategory(searchQuery, pageAddress) + CATEGORY -> client.getProductsByCategory(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching__category_products) - SearchType.ALLERGEN -> client.getProductsByAllergen(searchQuery, pageAddress) + ALLERGEN -> client.getProductsByAllergen(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_allergen_products) - SearchType.CONTRIBUTOR -> loadDataForContributor(searchQuery) + CONTRIBUTOR -> loadDataForContributor(searchQuery) - SearchType.STATE -> client.getProductsByStates(searchQuery, pageAddress) + STATE -> client.getProductsByStates(searchQuery, pageAddress) .startSearch(R.string.txt_no_matching_allergen_products) // Get Products to be completed data and input it to loadData function - SearchType.INCOMPLETE_PRODUCT -> client.getIncompleteProducts(pageAddress) + INCOMPLETE_PRODUCT -> client.getIncompleteProducts(pageAddress) .startSearch(R.string.txt_no_matching_incomplete_products) else -> Log.e("Products Browsing", "No match case found for " + mSearchInfo.searchType) diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/shared/adapters/NutrientLevelListAdapter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/shared/adapters/NutrientLevelListAdapter.kt index 1724917f0edd..91049800b2a6 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/shared/adapters/NutrientLevelListAdapter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/shared/adapters/NutrientLevelListAdapter.kt @@ -1,6 +1,7 @@ package openfoodfacts.github.scrachx.openfood.features.shared.adapters import android.content.Context +import android.text.SpannableStringBuilder import android.view.LayoutInflater import android.view.View import android.view.View.NO_ID @@ -8,11 +9,11 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources +import androidx.core.text.bold import androidx.recyclerview.widget.RecyclerView import openfoodfacts.github.scrachx.openfood.R import openfoodfacts.github.scrachx.openfood.features.shared.adapters.NutrientLevelListAdapter.NutrientViewHolder import openfoodfacts.github.scrachx.openfood.models.NutrientLevelItem -import openfoodfacts.github.scrachx.openfood.utils.bold class NutrientLevelListAdapter( private val context: Context, @@ -37,7 +38,7 @@ class NutrientLevelListAdapter( it.text = "" it.append(value) it.append(" ") - it.append(bold(category)) + it.append(SpannableStringBuilder().bold { append(category) }) it.append("\n") it.append(label) } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/BoldNutrimentListItem.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/BoldNutrimentListItem.kt index bb109ba689c1..19f9ae6a7ab0 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/BoldNutrimentListItem.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/BoldNutrimentListItem.kt @@ -1,6 +1,7 @@ package openfoodfacts.github.scrachx.openfood.models -import openfoodfacts.github.scrachx.openfood.utils.bold +import android.text.SpannableStringBuilder +import androidx.core.text.bold /** * Header with bold values @@ -16,9 +17,9 @@ class BoldNutrimentListItem( unit: CharSequence = "", modifier: CharSequence = "" ) : NutrimentListItem( - bold(title), - bold(value), - bold(servingValue), - bold(unit), - bold(modifier) + SpannableStringBuilder().bold { append(title) }, + SpannableStringBuilder().bold { append(value) }, + SpannableStringBuilder().bold { append(servingValue) }, + SpannableStringBuilder().bold { append(unit) }, + SpannableStringBuilder().bold { append(modifier) } ) \ No newline at end of file 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 6da3ff731f06..c4d69e552d59 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 @@ -12,11 +12,11 @@ import openfoodfacts.github.scrachx.openfood.utils.Utils.getRoundNumber * @param modifier one of the following: "<", ">", or "~" */ open class NutrimentListItem( - val title: String?, - value: String?, - servingValue: String?, - val unit: String?, - val modifier: String?, + internal val title: CharSequence?, + value: CharSequence?, + servingValue: CharSequence?, + val unit: CharSequence?, + val modifier: CharSequence?, val displayVolumeHeader: Boolean = false ) { val servingValue = if (servingValue.isNullOrBlank()) "" else getRoundNumber(servingValue) diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Nutriments.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Nutriments.kt index cb0e0d9cf006..394b763c890c 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Nutriments.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/models/Nutriments.kt @@ -310,7 +310,7 @@ class Nutriments : Serializable { return try { val valueFor100g = strValue.toFloat() val portionInGram = convertToGrams(portion, portionUnit) - getRoundNumber(valueFor100g / 100 * portionInGram) + getRoundNumber(valueFor100g / 100 * portionInGram).toString() } catch (e: NumberFormatException) { Log.w(Nutriments::class.simpleName, "Can't parse value '$strValue'", e) StringUtils.EMPTY @@ -318,11 +318,11 @@ class Nutriments : Serializable { } companion object { - private fun getValueInUnits(valueInGramOrMl: String, unit: String) = when { + private fun getValueInUnits(valueInGramOrMl: String, unit: String): String = when { valueInGramOrMl.isBlank() -> StringUtils.EMPTY valueInGramOrMl.isEmpty() || unit == Units.UNIT_GRAM -> valueInGramOrMl - else -> getRoundNumber(convertFromGram(valueInGramOrMl.toFloat(), unit)) + else -> getRoundNumber(convertFromGram(valueInGramOrMl.toFloat(), unit)).toString() } /** 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 9686dbeee6e5..1192f0d29fc0 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 @@ -197,6 +197,7 @@ object ApiFields { AMINO_ACIDS_TAGS, OTHER_NUTRITIONAL_SUBSTANCES_TAGS, URL, + NUTRIMENTS, BARCODE, TRACES_TAGS, INGREDIENTS_MAY_PALM_OIL_TAGS, 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 fc630c27c2d5..08d24da39e69 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 @@ -66,8 +66,8 @@ class OpenFoodAPIClient @Inject constructor( val langCode = getLanguage(context) val fieldsSet = allFields.toMutableSet() fieldsToLocalize.forEach { (field, shouldAddEn) -> - fieldsSet.add("${field}_$langCode") - if (shouldAddEn) fieldsSet.add("${field}_en") + fieldsSet += "${field}_$langCode" + if (shouldAddEn) fieldsSet += "${field}_en" } return fieldsSet.joinToString(",") } 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 0a82136f7bdc..22319ed08ffd 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 @@ -43,7 +43,7 @@ interface ProductsAPI { /** * The api prefix URL */ - private const val API_P = "/api/$API_VER" + private const val API_P = "api/$API_VER" } @GET("$API_P/product/{barcode}.json") @@ -181,9 +181,9 @@ interface ProductsAPI { @Path("page") page: Int ): Single - @GET("contributor/{Contributor}/{page}.json?nocache=1") + @GET("contributor/{contributor}/{page}.json?nocache=1") fun getProductsByContributor( - @Path("Contributor") contributor: String, + @Path("contributor") contributor: String, @Path("page") page: Int ): Single @@ -220,51 +220,51 @@ interface ProductsAPI { @GET("trace/{trace}.json") fun getProductsByTrace(@Path("trace") trace: String): Single - @GET("packager-code/{PackagerCode}.json") - fun getProductsByPackagerCode(@Path("PackagerCode") packagerCode: String): Single + @GET("packager-code/{packager_code}.json") + fun getProductsByPackagerCode(@Path("packager_code") packagerCode: String): Single - @GET("city/{City}.json") - fun getProducsByCity(@Path("City") city: String): Single + @GET("city/{city}.json") + fun getProducsByCity(@Path("city") city: String): Single @GET("nutrition-grade/{nutriscore}.json") fun getProductsByNutriScore(@Path("nutriscore") nutritionGrade: String): Single - @GET("nutrient-level/{NutrientLevel}.json") - fun byNutrientLevel(@Path("NutrientLevel") nutrientLevel: String): Single + @GET("nutrient-level/{nutrient_level}.json") + fun byNutrientLevel(@Path("nutrient_level") nutrientLevel: String): Single - @GET("contributor/{Contributor}.json?nocache=1") - fun byContributor(@Path("Contributor") contributor: String): Single + @GET("contributor/{contributor}.json?nocache=1") + fun byContributor(@Path("contributor") contributor: String): Single - @GET("contributor/{Contributor}/state/to-be-completed/{page}.json?nocache=1") + @GET("contributor/{contributor}/state/to-be-completed/{page}.json?nocache=1") fun getToBeCompletedProductsByContributor( - @Path("Contributor") contributor: String, + @Path("contributor") contributor: String, @Path("page") page: Int ): Single - @GET("/photographer/{Contributor}/{page}.json?nocache=1") + @GET("/photographer/{contributor}/{page}.json?nocache=1") fun getPicturesContributedProducts( - @Path("Contributor") contributor: String, + @Path("contributor") contributor: String, @Path("page") page: Int ): Single @GET("photographer/{Photographer}.json?nocache=1") fun getProductsByPhotographer(@Path("Photographer") photographer: String): Single - @GET("photographer/{Contributor}/state/to-be-completed/{page}.json?nocache=1") + @GET("photographer/{contributor}/state/to-be-completed/{page}.json?nocache=1") fun getPicturesContributedIncompleteProducts( - @Path("Contributor") contributor: String?, + @Path("contributor") contributor: String?, @Path("page") page: Int ): Single - @GET("informer/{Informer}.json?nocache=1") - fun getProductsByInformer(@Path("Informer") informer: String?): Single + @GET("informer/{informer}.json?nocache=1") + 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 + @GET("informer/{contributor}/{page}.json?nocache=1") + fun getInfoAddedProducts(@Path("contributor") contributor: String?, @Path("page") page: Int): Single - @GET("informer/{Contributor}/state/to-be-completed/{page}.json?nocache=1") + @GET("informer/{contributor}/state/to-be-completed/{page}.json?nocache=1") fun getInfoAddedIncompleteProductsSingle( - @Path("Contributor") contributor: String, + @Path("contributor") contributor: String, @Path("page") page: Int ): Single diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Utils.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Utils.kt index 3fa2865fe06c..920996bd8637 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Utils.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Utils.kt @@ -24,14 +24,12 @@ import android.content.pm.PackageManager.PERMISSION_GRANTED import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas -import android.graphics.Typeface import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.net.Uri import android.os.Build import android.os.Environment import android.text.Spannable -import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.ClickableSpan @@ -45,6 +43,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.net.toUri +import androidx.core.text.inSpans import androidx.core.view.children import androidx.work.* import com.afollestad.materialdialogs.MaterialDialog @@ -138,10 +137,10 @@ object Utils { * @param value float value * @return round value **with 2 decimals** or 0 if the value is empty or equals to 0 */ - fun getRoundNumber(value: String, locale: Locale = Locale.getDefault()) = when { + fun getRoundNumber(value: CharSequence, locale: Locale = Locale.getDefault()) = when { value.isEmpty() -> "?" value == "0" -> value - else -> value.toDoubleOrNull() + else -> value.toString().toDoubleOrNull() ?.let { DecimalFormat("##.##", DecimalFormatSymbols(locale)).format(it) } ?: "?" } @@ -208,16 +207,6 @@ object Utils { fun getOutputPicUri(context: Context): Uri = File(makeOrGetPictureDirectory(context), "${System.currentTimeMillis()}.jpg").toUri() - fun getClickableText( - text: String, - type: SearchType, - activityToStart: Activity - ): CharSequence = SpannableString(text).apply { - setSpan(object : ClickableSpan() { - override fun onClick(view: View) = start(activityToStart, type, text) - }, 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - /** * Function to open ContinuousScanActivity to facilitate scanning * @@ -349,12 +338,6 @@ private fun closeStyles(text: Spannable, tags: Array) { } } -/** - * Returns a CharSequence that applies boldface to the concatenation - * of the specified CharSequence objects. - */ -fun bold(vararg content: CharSequence) = apply(content, StyleSpan(Typeface.BOLD)) - fun getModifierNonDefault(modifier: String) = if (modifier != DEFAULT_MODIFIER) modifier else "" private val LOG_TAG = Utils::class.simpleName!! @@ -407,3 +390,10 @@ fun isBarcodeValid(barcode: String?): Boolean { * @return true if installed, false otherwise. */ fun isHardwareCameraInstalled(context: Context) = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) +fun getSearchLinkText( + text: String, + type: SearchType, + activityToStart: Activity +): CharSequence = SpannableStringBuilder().inSpans(object : ClickableSpan() { + override fun onClick(view: View) = start(activityToStart, type, text) +}) { append(text) } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index f9153fb0b818..74b843697906 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -63,6 +63,7 @@ android:layout_height="wrap_content" android:hint="@string/hintPass" android:inputType="textPassword" + android:maxLines="1" /> diff --git a/app/src/main/res/layout/fragment_summary_product.xml b/app/src/main/res/layout/fragment_summary_product.xml index 784be52db6dc..c904d9a89481 100644 --- a/app/src/main/res/layout/fragment_summary_product.xml +++ b/app/src/main/res/layout/fragment_summary_product.xml @@ -464,7 +464,7 @@ android:layout_marginBottom="16dp" android:visibility="gone" app:cardElevation="@dimen/card_elevation" - tools:visibility="gone"> + tools:visibility="visible">