feat(activity): 优化食材识别重量逻辑,修复主辅材重复保存问题
- PrepareFoodActivity:改为以首次秤读数为基准计算净重,注释清零按钮和测试按钮,修复 foodSelectCallback 中 isClicked 赋值对象错误导致选中状态异常的 bug,修复 getGoodsList 重复调用时主辅材累加的问题,向 FoodRecognizeActivity 传递 initialWeight - FoodRecognizeActivity:启用秤重量监听,改为净重显示;用滑动窗口(最近10帧)替换延迟更新逻辑,关闭页面时从窗口中提取连续稳定值作为 useWeight,数据不稳定时弹窗提示而非直接关闭 - DbViewModel:saveCookFoodAndGoods 首次插入后回填 entity.id,防止重复进入时主辅材重复插入 - InitActivity:注释启动时自动清零秤的逻辑 - SubmitFoodActivity:将魔法数字 0.5 提取为常量 WEIGHT_THRESHOLD
This commit is contained in:
@@ -73,6 +73,8 @@ class DbViewModel : ViewModel() {
|
||||
// 首次保存:主表 + 主辅材 + 调料 全量 insert
|
||||
rep.insertCookFood(entity)
|
||||
val newData = rep.getCookFoodById(entity.foodId, cookMode, entity.dinnerType ?: "0")
|
||||
// 回填 id,防止下次调用时 getCookFoodById 查不到而重复插入主辅材
|
||||
newData?.let { entity.id = it.id }
|
||||
list?.let {
|
||||
it.forEach { goods ->
|
||||
goods.id = 0
|
||||
|
||||
@@ -10,10 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.shuwei.dish.match.adapter.FoodAdapter
|
||||
import com.shuwei.dish.match.base.BaseActivity
|
||||
import com.shuwei.dish.match.databinding.ActivityFoodRecognizeBinding
|
||||
import com.shuwei.dish.match.dialog.CommonDialog
|
||||
import com.shuwei.dish.match.dialog.FoodSearchDialog
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import com.shuwei.dish.match.entity.GoodsNameQueryDTO
|
||||
import com.shuwei.dish.match.utils.ActivityManager
|
||||
import com.shuwei.dish.match.utils.AddressUtil
|
||||
import com.shuwei.dish.match.utils.WeightUtil
|
||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||
import com.shuwei.dish.match.utils.ext.gone
|
||||
@@ -38,6 +40,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
private const val TAG = "FoodRecognizeActivity"
|
||||
private const val EXTRA_IMAGE_URI = "extra_image_uri"
|
||||
private const val EXTRA_CURRENT_WEIGHT = "extra_current_weight"
|
||||
private const val EXTRA_INITIAL_WEIGHT = "extra_initial_weight"
|
||||
const val EXTRA_SELECTED_ITEM = "extra_selected_item"
|
||||
const val IS_MANUAL_CANCEL = "isManualCancel"
|
||||
|
||||
@@ -54,6 +57,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
activity: BaseActivity,
|
||||
imageUri: String? = null,
|
||||
currentWeight: Double = 0.0,
|
||||
initialWeight: Double = 0.0,
|
||||
nameScoreList: ArrayList<FoodModule.IdNameScore> = arrayListOf(),
|
||||
finishCallback: (Boolean, GoodsItem?) -> Unit = { _, _ -> }
|
||||
) {
|
||||
@@ -61,6 +65,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
val launchIntent = Intent(activity, FoodRecognizeActivity::class.java).apply {
|
||||
putExtra(EXTRA_IMAGE_URI, imageUri)
|
||||
putExtra(EXTRA_CURRENT_WEIGHT, currentWeight)
|
||||
putExtra(EXTRA_INITIAL_WEIGHT, initialWeight)
|
||||
}
|
||||
activity.startActivity(launchIntent) { resultIntent ->
|
||||
val isManualCancel = resultIntent?.getBooleanExtra(IS_MANUAL_CANCEL, false) ?: false
|
||||
@@ -94,14 +99,17 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
/** 当前选中的列表位置,-1 表示无选中 */
|
||||
private var selectedPosition = -1
|
||||
|
||||
/** 秤1当前实时重量 */
|
||||
/** 秤1当前实时净重(已减去初始读数) */
|
||||
private var currentWeight = 0.0
|
||||
|
||||
/** 重量减少时延迟更新的任务,页面关闭前取消可防止用错误重量 */
|
||||
private var pendingWeightRunnable: Runnable? = null
|
||||
/** 初始秤读数,净重 = 秤读数 - initialWeight */
|
||||
private var initialWeight = 0.0
|
||||
|
||||
/** 当前待延迟更新的目标重量,避免相同重量重复重置计时器 */
|
||||
private var pendingWeight = 0.0
|
||||
/**
|
||||
* 滑动窗口:保存最近 10 条稳定帧净重,用于关闭页面时判断数据是否稳定
|
||||
* 超过 10 条时移除队头,保持最新数据
|
||||
*/
|
||||
private val weightWindow = ArrayDeque<Double>(10)
|
||||
|
||||
/** 右侧自定义滚动指示条最小高度 */
|
||||
private val scrollIndicatorMinHeightPx by lazy { (24 * resources.displayMetrics.density).toInt() }
|
||||
@@ -199,6 +207,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
binding.ivFoodPhoto.setImageURI(imageUri.toUri())
|
||||
}
|
||||
currentWeight = intent.getDoubleExtra(EXTRA_CURRENT_WEIGHT, 0.0)
|
||||
initialWeight = intent.getDoubleExtra(EXTRA_INITIAL_WEIGHT, 0.0)
|
||||
binding.tvWeight.text = "${currentWeight}g"
|
||||
// val tempList = arrayListOf<GoodsItem>()
|
||||
// Process recognized name list if available
|
||||
@@ -436,58 +445,82 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
* 低于阈值说明食材已被取走,保留上次有效值等待页面关闭
|
||||
*/
|
||||
private fun registerWeightListener() {
|
||||
// WeightUtil.addWeightListener(TAG) { address, state, weight ->
|
||||
// if (address == AddressUtil.ONE && state == WeightUtil.STATE_STABLE) {
|
||||
// runOnUiThread {
|
||||
// currentWeight = weight
|
||||
// binding.tvWeight.run {
|
||||
// if (tag != weight) {
|
||||
// text = "$weight g"
|
||||
// tag = weight
|
||||
// }
|
||||
// }
|
||||
// if (weight >= PrepareCookActivity.WEIGHT_RECOGNIZE_VALUE) {
|
||||
// adapter.items.firstOrNull { it.isClicked }?.let { item ->
|
||||
// if (pendingWeight != weight) {
|
||||
// // 无论增减,均延迟更新,避免手拿物品时的瞬时增重干扰
|
||||
// cancelPendingWeightUpdate()
|
||||
// pendingWeight = weight
|
||||
// pendingWeightRunnable = Runnable {
|
||||
// item.useWeight = weight
|
||||
// pendingWeightRunnable = null
|
||||
// }.also { handler.postDelayed(it, 3000) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
WeightUtil.addWeightListener(TAG) { address, state, weight ->
|
||||
if (address == AddressUtil.ONE && state == WeightUtil.STATE_STABLE) {
|
||||
runOnUiThread {
|
||||
// 净重 = 秤读数 - 初始读数
|
||||
val netWeight = weight - initialWeight
|
||||
currentWeight = netWeight
|
||||
binding.tvWeight.run {
|
||||
if (tag != netWeight) {
|
||||
text = "${netWeight}g"
|
||||
tag = netWeight
|
||||
}
|
||||
}
|
||||
// 入队,超过 10 条移除队头
|
||||
if (weightWindow.size >= 10) weightWindow.removeFirst()
|
||||
weightWindow.addLast(netWeight)
|
||||
// 实时更新选中食材重量
|
||||
list.getOrNull(selectedPosition)?.useWeight = netWeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消待执行的延迟重量更新任务
|
||||
* 从滑动窗口尾部向前查找连续稳定值:
|
||||
* 跳过尾部不稳定的过渡帧,找到第一段连续相同且大于阈值的序列
|
||||
* 连续条数 >= 3 时返回该值,否则返回 null(数据不稳定)
|
||||
*/
|
||||
private fun cancelPendingWeightUpdate() {
|
||||
// pendingWeightRunnable?.let { handler.removeCallbacks(it) }
|
||||
// pendingWeightRunnable = null
|
||||
private fun getStableWeightFromWindow(): Double? {
|
||||
val threshold = PrepareFoodActivity.WEIGHT_RECOGNIZE_VALUE
|
||||
var i = weightWindow.indices.last
|
||||
while (i >= 0) {
|
||||
val candidate = weightWindow[i]
|
||||
// 跳过低于阈值的过渡帧
|
||||
if (candidate <= threshold) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
// 以 candidate 为基准,向前统计连续相同条数
|
||||
var count = 0
|
||||
var j = i
|
||||
while (j >= 0 && weightWindow[j] == candidate) {
|
||||
count++
|
||||
j--
|
||||
}
|
||||
if (count >= 3) return candidate
|
||||
// 不足 3 条,继续向前找
|
||||
i = j
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 携带当前选中食材关闭页面
|
||||
* @param isManualCancel true=用户手动返回;false=被动关闭(重量低于阈值)
|
||||
*/
|
||||
private fun finishWithResult(isManualCancel: Boolean) {
|
||||
cancelPendingWeightUpdate()
|
||||
val item = list.getOrNull(selectedPosition)
|
||||
if (!isManualCancel && item != null) {
|
||||
// 被动关闭:从窗口取稳定值
|
||||
val stableWeight = getStableWeightFromWindow()
|
||||
if (stableWeight == null) {
|
||||
// 数据不稳定,不关闭页面,弹窗提示
|
||||
CommonDialog(this)
|
||||
.setTitle("提示")
|
||||
.setContent("数据不稳定,请重新放置食材后再取走")
|
||||
.setPositiveButton("确认")
|
||||
.show()
|
||||
return
|
||||
}
|
||||
item.useWeight = stableWeight
|
||||
}
|
||||
|
||||
val intent = Intent().apply {
|
||||
putExtra(IS_MANUAL_CANCEL, isManualCancel)
|
||||
if (isManualCancel.not()) {
|
||||
list.getOrNull(selectedPosition)?.let {
|
||||
// 兜底:若 useWeight 仍为空或0,使用当前实时重量
|
||||
if ((it.useWeight ?: 0.0) <= 0.0) {
|
||||
it.useWeight = currentWeight
|
||||
}
|
||||
putExtra(EXTRA_SELECTED_ITEM, it)
|
||||
}
|
||||
if (!isManualCancel) {
|
||||
item?.let { putExtra(EXTRA_SELECTED_ITEM, it) }
|
||||
}
|
||||
}
|
||||
setResult(RESULT_OK, intent)
|
||||
@@ -496,7 +529,6 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cancelPendingWeightUpdate()
|
||||
WeightUtil.removeWeightListener(TAG)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ class InitActivity : BaseActivity() {
|
||||
WeightUtil.getWeight()
|
||||
WeightUtil.startContinuousRead()
|
||||
//对净材秤1和熟食秤2称进行清零
|
||||
WeightUtil.tareTwo(AddressUtil.ONE)
|
||||
WeightUtil.tareTwo(AddressUtil.TWO)
|
||||
//WeightUtil.tareTwo(AddressUtil.ONE)
|
||||
//WeightUtil.tareTwo(AddressUtil.TWO)
|
||||
initViews()
|
||||
}
|
||||
|
||||
|
||||
@@ -124,34 +124,41 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun addViewClickListener() {
|
||||
binding.tvShowWeight.setOnClickListener {
|
||||
showLoading("正在清零……")
|
||||
WeightUtil.tareTwo(AddressUtil.ONE)
|
||||
delayDismissLoading()
|
||||
}
|
||||
// TODO: 测试功能-------------------------------
|
||||
binding.btnTestConfirm.clickWithDebounce {
|
||||
if (currentWeight <= 0.0) return@clickWithDebounce
|
||||
val (index, item) = list.withIndex().find { (_, it) -> it.isOriginalData && it.isClicked && (it.useWeight?:0.0) <= 0.0 } ?: return@clickWithDebounce
|
||||
item.useWeight = currentWeight
|
||||
item.isSetFinished = true
|
||||
list.forEachIndexed { i, it -> it.isClicked = i == index }
|
||||
materialAdapter.notifyDataSetChanged()
|
||||
}
|
||||
// TODO: 测试功能-------------------------------
|
||||
// binding.tvShowWeight.setOnClickListener {
|
||||
// showLoading("正在清零……")
|
||||
// WeightUtil.tareTwo(AddressUtil.ONE)
|
||||
// delayDismissLoading()
|
||||
// }
|
||||
// // TODO: 测试功能-------------------------------
|
||||
// binding.btnTestConfirm.clickWithDebounce {
|
||||
// if (currentWeight <= 0.0) return@clickWithDebounce
|
||||
// val (index, item) = list.withIndex().find { (_, it) -> it.isOriginalData && it.isClicked && (it.useWeight?:0.0) <= 0.0 } ?: return@clickWithDebounce
|
||||
// item.useWeight = currentWeight
|
||||
// item.isSetFinished = true
|
||||
// list.forEachIndexed { i, it -> it.isClicked = i == index }
|
||||
// materialAdapter.notifyDataSetChanged()
|
||||
// }
|
||||
// // TODO: 测试功能-------------------------------
|
||||
//binding.root.setOnClickListener { hideKeyboard() }
|
||||
WeightUtil.addWeightListener(
|
||||
weightKey = TAG,
|
||||
getWeight = { address, state, weight ->
|
||||
if (address == AddressUtil.ONE && state == WeightUtil.STATE_STABLE) {
|
||||
runOnUiThread {
|
||||
// 首次收到稳定读数时,将其作为初始值(去皮基准)
|
||||
if (!initialWeightReady) {
|
||||
initialWeight = weight
|
||||
initialWeightReady = true
|
||||
}
|
||||
// 净重 = 秤读数 - 初始读数
|
||||
val netWeight = weight - initialWeight
|
||||
binding.tvShowWeight.run {
|
||||
if (tag != weight) {
|
||||
text = "称重:${weight}g"
|
||||
tag = weight
|
||||
if (tag != netWeight) {
|
||||
text = "称重:${netWeight}g"
|
||||
tag = netWeight
|
||||
}
|
||||
}
|
||||
recognizeFood(weight)
|
||||
recognizeFood(netWeight)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -216,6 +223,12 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
private var lastWeight = 0.0
|
||||
private var currentWeight = 0.0
|
||||
|
||||
/** 初始秤读数,净重 = 秤读数 - initialWeight */
|
||||
private var initialWeight = 0.0
|
||||
|
||||
/** 初始读数是否已从秤获取 */
|
||||
private var initialWeightReady = false
|
||||
|
||||
/**
|
||||
* 重量稳定时触发菜品识别
|
||||
* @param weight 当前稳定重量
|
||||
@@ -257,11 +270,11 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
val filterValue = list.withIndex().find { (_, it) -> it.goodsId == entity.goodsId }
|
||||
if (filterValue != null) {
|
||||
val index = filterValue.index
|
||||
val item = filterValue.value
|
||||
item.useWeight = (item.useWeight ?: 0.0) + (entity.useWeight ?: 0.0)
|
||||
item.isSetFinished = true
|
||||
val goods = filterValue.value
|
||||
goods.useWeight = (goods.useWeight ?: 0.0) + (entity.useWeight ?: 0.0)
|
||||
goods.isSetFinished = true
|
||||
list.forEachIndexed { i, it ->
|
||||
item.isClicked = i == index
|
||||
it.isClicked = i == index
|
||||
}
|
||||
} else {
|
||||
list.forEach { it.isClicked = false }
|
||||
@@ -325,6 +338,8 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
private fun getGoodsList() {
|
||||
if (goodsList == null) {
|
||||
goodsList = mutableListOf()
|
||||
} else {
|
||||
goodsList!!.clear()
|
||||
}
|
||||
// 将 GoodsItem 转换为 CookFoodGoodsEntity 并补充菜品相关字段
|
||||
list.filter { it.isSetFinished }.forEach {
|
||||
@@ -629,6 +644,7 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
activity = this,
|
||||
imageUri = lastPhotoUri?.toString(),
|
||||
currentWeight = currentWeight,
|
||||
initialWeight = initialWeight,
|
||||
nameScoreList = ArrayList(nameScoreList),
|
||||
finishCallback = { isManualCancel, entity ->
|
||||
Log.d(
|
||||
|
||||
@@ -49,6 +49,8 @@ class SubmitFoodActivity : BaseActivity() {
|
||||
|
||||
/** 本地临时 foodId 前缀;以此开头说明菜品尚未在服务器登记,提交时需清空让服务器分配真实 ID */
|
||||
const val LOCAL_ID_PREFIX = "localId_"
|
||||
|
||||
const val WEIGHT_THRESHOLD = 0.01
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivitySubmitFoodBinding
|
||||
@@ -247,7 +249,7 @@ class SubmitFoodActivity : BaseActivity() {
|
||||
.coerceAtLeast(0.0)
|
||||
.roundedOneDecimalPlace()
|
||||
|
||||
if (useWeight < 0.5) {
|
||||
if (useWeight < WEIGHT_THRESHOLD) {
|
||||
// 放回场景:必须稳定后才移除,避免拿取过程中误删
|
||||
if (data.state != WeightUtil.STATE_STABLE) return@forEach
|
||||
if (rawWeightMap.remove(key) != null) {
|
||||
@@ -279,8 +281,8 @@ class SubmitFoodActivity : BaseActivity() {
|
||||
val (baseGoodsId, baseWeight) = baseSeasoningMap[goodsName] ?: ("" to 0.0)
|
||||
val totalWeight = (newWeight + baseWeight).roundedOneDecimalPlace()
|
||||
Log.d(TAG, "refreshAdapterByName, goodsName=$goodsName, newWeight=$newWeight, baseWeight=$baseWeight, totalWeight=$totalWeight, isCooking=${food?.isCooking}")
|
||||
if (totalWeight < 0.5) {
|
||||
Log.d(TAG, "refreshAdapterByName,重量小于0.5g, isCooking=${food?.isCooking}, goodsName=$goodsName, totalWeight=$totalWeight")
|
||||
if (totalWeight < WEIGHT_THRESHOLD) {
|
||||
Log.d(TAG, "refreshAdapterByName,重量小于${WEIGHT_THRESHOLD}g, isCooking=${food?.isCooking}, goodsName=$goodsName, totalWeight=$totalWeight")
|
||||
// 仅保护 baseSeasoningMap 中的历史调料;本次会话新加的调料可以移除
|
||||
if (!baseSeasoningMap.containsKey(goodsName)) {
|
||||
// 只有 item 确实存在时才调用 remove,避免无效遍历
|
||||
|
||||
Reference in New Issue
Block a user