diff --git a/app/src/main/java/com/shuwei/dish/match/db/DbViewModel.kt b/app/src/main/java/com/shuwei/dish/match/db/DbViewModel.kt index e822fe2..5c56771 100644 --- a/app/src/main/java/com/shuwei/dish/match/db/DbViewModel.kt +++ b/app/src/main/java/com/shuwei/dish/match/db/DbViewModel.kt @@ -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 diff --git a/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt index 6cb3dd5..6983e00 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt @@ -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 = 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(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() // 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) } } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt index 725aa08..4a3cb53 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt @@ -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() } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt index cbf6d93..097da0e 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt @@ -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( diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt index f6d947f..7c6299b 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt @@ -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,避免无效遍历