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 df5eb587accf..9045cb820433 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 @@ -135,29 +135,34 @@ class ProductEditActivity : BaseActivity() { } override fun onBackPressed() { + // If the user changed something, alert before exiting + if (getUpdatedFieldsMap().isNotEmpty()) showExitConfirmDialog() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + if (getUpdatedFieldsMap().isNotEmpty()) { + showExitConfirmDialog() + true + } else false + } + R.id.save_product -> { + checkFieldsThenSave() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun showExitConfirmDialog() { MaterialAlertDialogBuilder(this) .setMessage(R.string.save_product) .setPositiveButton(R.string.txtSave) { _, _ -> checkFieldsThenSave() } - .setNegativeButton(R.string.txtPictureNeededDialogNo) { _, _ -> super.onBackPressed() } + .setNegativeButton(R.string.txt_discard) { _, _ -> super.onBackPressed() } .show() } - override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { - android.R.id.home -> { - MaterialAlertDialogBuilder(this) - .setMessage(R.string.save_product) - .setPositiveButton(R.string.txtSave) { _, _ -> checkFieldsThenSave() } - .setNegativeButton(R.string.txt_discard) { _, _ -> finish() } - .show() - true - } - R.id.save_product -> { - checkFieldsThenSave() - true - } - else -> super.onOptionsItemSelected(item) - } - private fun selectPage(position: Int) = when (position) { 1 -> updateTimelineIndicator(2, 1, 0) 2 -> updateTimelineIndicator(2, 2, 1) @@ -234,7 +239,7 @@ class ProductEditActivity : BaseActivity() { private fun setupViewPager(viewPager: ViewPager2) { // Initialize fragments - fragmentsBundle.putSerializable("product", mProduct) + fragmentsBundle.putSerializable(KEY_PRODUCT, mProduct) editOverviewFragment.arguments = fragmentsBundle ingredientsFragment.arguments = fragmentsBundle @@ -261,9 +266,6 @@ class ProductEditActivity : BaseActivity() { viewPager.adapter = adapterResult } - private fun createTextPlain(code: String) = - RequestBody.create(OpenFoodAPIClient.MIME_TEXT, code) - private fun getLoginPasswordInfo(): Map { val map = hashMapOf() val settings = getLoginPreferences() @@ -281,15 +283,20 @@ class ProductEditActivity : BaseActivity() { } private suspend fun saveProduct() { - productDetails += editOverviewFragment.getUpdatedFieldsMap() - productDetails += ingredientsFragment.getUpdatedFieldsMap() - if (isFlavors(OFF, OPFF)) { - productDetails += nutritionFactsFragment.getUpdatedFieldsMap() - } - productDetails += getLoginInfoMap() + productDetails += getUpdatedFieldsMap() + getLoginInfoMap() saveProductOffline() } + private fun getUpdatedFieldsMap(): Map { + val updatedValues = editOverviewFragment.getUpdatedFieldsMap().toMutableMap() + updatedValues += ingredientsFragment.getUpdatedFieldsMap() + + if (isFlavors(OFF, OPFF)) + updatedValues += nutritionFactsFragment.getUpdatedFieldsMap() + + return updatedValues + } + fun proceed() = if (binding.viewpager.currentItem < 2) { binding.viewpager.setCurrentItem(binding.viewpager.currentItem + 1, true) } else checkFieldsThenSave() @@ -529,7 +536,9 @@ class ProductEditActivity : BaseActivity() { suspend fun performOCR(code: String, imageField: String) { - withContext(Main) { ingredientsFragment.showOCRProgress() } + withContext(Main) { + ingredientsFragment.showOCRProgress() + } val node = withContext(IO) { try { @@ -641,6 +650,7 @@ class ProductEditActivity : BaseActivity() { const val KEY_EDIT_OFFLINE_PRODUCT = "edit_offline_product" const val KEY_EDIT_PRODUCT = "edit_product" + const val KEY_PRODUCT = "product" const val KEY_IS_EDITING = "is_edition" const val KEY_STATE = "state" @@ -693,5 +703,8 @@ class ProductEditActivity : BaseActivity() { context.startActivity(this) } } + + private fun createTextPlain(code: String) = + RequestBody.create(OpenFoodAPIClient.MIME_TEXT, code) } } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ingredients/EditIngredientsFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ingredients/EditIngredientsFragment.kt index 880af41e6966..dc318290344a 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ingredients/EditIngredientsFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ingredients/EditIngredientsFragment.kt @@ -24,6 +24,7 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.Toast import androidx.core.net.toFile +import androidx.core.view.isGone import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope @@ -286,6 +287,7 @@ class EditIngredientsFragment : ProductEditFragment() { private fun preFillValuesForOffline(prod: OfflineSavedProduct) { productDetails = prod.productDetails.toMutableMap() + // Load ingredients image getImageIngredients()?.let { binding.imageProgress.visibility = View.VISIBLE picasso @@ -317,11 +319,16 @@ class EditIngredientsFragment : ProductEditFragment() { * Automatically load suggestions for allergen names */ private fun loadAutoSuggestions(allergens: List) { - val adapter = ArrayAdapter(requireActivity(), android.R.layout.simple_dropdown_item_1line, allergens) binding.traces.addChipTerminator(',', ChipTerminatorHandler.BEHAVIOR_CHIPIFY_CURRENT_TOKEN) binding.traces.setNachoValidator(ChipifyingNachoValidator()) binding.traces.enableEditChipOnTouch(false, true) - binding.traces.setAdapter(adapter) + binding.traces.setAdapter( + ArrayAdapter( + requireActivity(), + android.R.layout.simple_dropdown_item_1line, + allergens + ) + ) } @@ -381,11 +388,7 @@ class EditIngredientsFragment : ProductEditFragment() { } private fun toggleOCRButtonVisibility() { - if (binding.ingredientsList.isEmpty()) { - binding.btnExtractIngredients.visibility = View.VISIBLE - } else { - binding.btnExtractIngredients.visibility = View.GONE - } + binding.ingredientsList.isGone = binding.ingredientsList.isNotEmpty() } /** @@ -399,8 +402,7 @@ class EditIngredientsFragment : ProductEditFragment() { ?.takeUnless { it.isEmpty() } ?: ApiFields.Defaults.DEFAULT_LANGUAGE targetMap[lcIngredientsKey(lc)] = binding.ingredientsList.text.toString() - val string = binding.traces.chipValues.joinToString(",") - targetMap[ApiFields.Keys.ADD_TRACES.substring(4)] = string + targetMap[ApiFields.Keys.ADD_TRACES.substring(4)] = binding.traces.chipValues.joinToString(",") } } 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 146fd1ef43d2..27df0c4ff67c 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 @@ -10,6 +10,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import openfoodfacts.github.scrachx.openfood.R import openfoodfacts.github.scrachx.openfood.databinding.CalculateDetailsBinding import openfoodfacts.github.scrachx.openfood.features.adapters.CalculatedNutrimentsGridAdapter +import openfoodfacts.github.scrachx.openfood.features.product.edit.ProductEditActivity.Companion.KEY_PRODUCT import openfoodfacts.github.scrachx.openfood.features.shared.BaseActivity import openfoodfacts.github.scrachx.openfood.models.* import openfoodfacts.github.scrachx.openfood.models.MeasurementUnit @@ -191,7 +192,6 @@ class CalculateDetailsActivity : BaseActivity() { } companion object { - private const val KEY_PRODUCT = "product" private const val KEY_SPINNER_VALUE = "spinnerValue" private const val KEY_WEIGHT = "weight" diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt index 6f9291e8bd34..e196543b8801 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt @@ -47,6 +47,8 @@ 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.databinding.ActivityProductListsBinding +import openfoodfacts.github.scrachx.openfood.features.product.edit.ProductEditActivity +import openfoodfacts.github.scrachx.openfood.features.product.edit.ProductEditActivity.Companion.KEY_PRODUCT import openfoodfacts.github.scrachx.openfood.features.productlist.ProductListActivity import openfoodfacts.github.scrachx.openfood.features.productlist.ProductListActivity.Companion.KEY_LIST_ID import openfoodfacts.github.scrachx.openfood.features.productlist.ProductListActivity.Companion.KEY_LIST_NAME @@ -309,7 +311,6 @@ class ProductListsActivity : BaseActivity(), SwipeController.Actions { } companion object { - private const val KEY_PRODUCT = "product" @JvmStatic fun start(context: Context, productToAdd: Product) = context.startActivity( diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Context.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Context.kt index f0b60c1e130f..bf46eee30263 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Context.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/Context.kt @@ -6,8 +6,9 @@ import android.content.IntentFilter import android.content.pm.PackageManager import android.os.BatteryManager import android.util.Log -import androidx.preference.PreferenceManager +import androidx.preference.PreferenceManager.getDefaultSharedPreferences import java.io.File +import java.io.IOException import kotlin.math.ceil private const val LOG_TAG = "ContextExt" @@ -18,23 +19,23 @@ private const val LOG_TAG = "ContextExt" * @return true if battery is low or false if battery in not low */ fun Context.isBatteryLevelLow(percent: Int = 15): Boolean { - val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) - val batteryStatus = registerReceiver(null, ifilter) ?: throw IllegalStateException("cannot get battery level") + val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + val batteryStatus = registerReceiver(null, filter) ?: throw IllegalStateException("cannot get battery level") val level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) - val batteryPct = level / scale.toFloat() * 100 + val batteryPct = level.toFloat() / scale * 100 Log.i("BATTERYSTATUS", batteryPct.toString()) - return ceil(batteryPct.toDouble()) <= percent + return ceil(batteryPct) <= percent } -fun Context.isDisableImageLoad(defValue: Boolean = false) = PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean("disableImageLoad", defValue) - fun Context.isLowBatteryMode() = isDisableImageLoad() && isBatteryLevelLow() -fun Context.isFastAdditionMode(defValue: Boolean = false) = PreferenceManager.getDefaultSharedPreferences(this) +fun Context.isDisableImageLoad(defValue: Boolean = false) = getDefaultSharedPreferences(this) + .getBoolean("disableImageLoad", defValue) + +fun Context.isFastAdditionMode(defValue: Boolean = false) = getDefaultSharedPreferences(this) .getBoolean("fastAdditionMode", defValue) fun Context.dpsToPixel(dps: Int) = dps.toPx(this) @@ -53,6 +54,7 @@ fun Context.getVersionName(): String = try { } fun Context.isHardwareCameraInstalled() = isHardwareCameraInstalled(this) + fun Context.clearCameraCache() { (getCameraCacheLocation().listFiles() ?: return).forEach { if (it.delete()) Log.i(LOG_TAG, "Deleted cached photo '${it.absolutePath}'.") @@ -61,14 +63,19 @@ fun Context.clearCameraCache() { } fun Context.getCameraCacheLocation(): File { - var cacheDir = cacheDir - if (Utils.isExternalStorageWritable()) { - cacheDir = externalCacheDir - } + // Prefer external storage, photos are not sensible. + // From android docs: + // > If you need to store sensitive data only temporarily, + // > you should use the app's designated cache directory + // > within internal storage to save the data + val cacheDir = if (Utils.isExternalStorageWritable()) externalCacheDir else cacheDir + val picDir = File(cacheDir, "EasyImage") - if (!picDir.exists()) { + if (picDir.exists()) { + check(picDir.isDirectory) { "Path '$picDir' is not a directory." } + } else { if (picDir.mkdirs()) Log.i(LOG_TAG, "Directory '${picDir.absolutePath}' created.") - else Log.i(LOG_TAG, "Couldn't create directory '${picDir.absolutePath}'.") + else throw IOException("Couldn't create directory '${picDir.absolutePath}'.") } return picDir } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_product_ingredients.xml b/app/src/main/res/layout/fragment_add_product_ingredients.xml index 8cbce1c39435..fcad3e4b5954 100644 --- a/app/src/main/res/layout/fragment_add_product_ingredients.xml +++ b/app/src/main/res/layout/fragment_add_product_ingredients.xml @@ -48,7 +48,6 @@ android:layout_width="50dp" android:layout_height="50dp" android:layout_marginStart="24dp" - android:layout_marginLeft="24dp" android:layout_marginTop="8dp" android:background="?android:selectableItemBackground" app:layout_constraintStart_toStartOf="parent" @@ -163,12 +162,12 @@ android:layout_height="wrap_content" android:text="@string/extract_ingredients" android:visibility="gone" - app:drawableLeftCompat="@drawable/ic_compare_arrows_black_18dp" app:layout_constraintBottom_toBottomOf="@id/ingredients_list" app:layout_constraintEnd_toEndOf="@id/ingredients_list" app:layout_constraintStart_toStartOf="@id/ingredients_list" app:layout_constraintTop_toBottomOf="@id/traces" - app:layout_constraintTop_toTopOf="@id/ingredients_list" /> + app:layout_constraintTop_toTopOf="@id/ingredients_list" + tools:visibility="visible" /> + app:layout_constraintTop_toTopOf="@id/ingredients_list" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/ocr_progress" + tools:visibility="visible" />