feat(activity): 新增菜品识别功能,重构FoodRecognizeDialog并临时跳过调料配置检查

- 新增 FoodRecognizeDialog(继承 BottomSheetDialog),只初始化一次,暴露 loadData() 方法
- 新增 dialog_food_recognize.xml 布局(标题 + 2列 GridLayoutManager RecyclerView)
- PrepareCookActivity 集成相机初始化、拍照识别流程(重量稳定 ≥10g 时触发)
- PrepareCookActivity 新增 flCameraContainer(1dp 隐藏容器)用于相机预览绑定
- SelectDishActivity 临时注释 judgeDeviceConfig 逻辑,直接调用 block()
- 修正 GpioUtils/L/LightManager 包名为 com.shuwei.dish.match.utils
This commit is contained in:
2026-04-13 17:31:31 +08:00
parent 41036cdbf9
commit 96863e2078
9 changed files with 496 additions and 16 deletions
@@ -1,10 +1,11 @@
package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.Typeface
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
@@ -12,23 +13,34 @@ import com.shuwei.dish.match.adapter.DishPartAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding
import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding
import com.shuwei.dish.match.dialog.FoodSearchDialog
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.dialog.FoodRecognizeDialog
import com.shuwei.dish.match.dialog.FoodSearchDialog
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.net.NetViewModel
import android.view.ViewGroup
import androidx.camera.view.PreviewView
import androidx.lifecycle.lifecycleScope
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.CameraUtils
import com.shuwei.dish.match.utils.ImageUtil
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.Serializable
import kotlin.getValue
import java.util.concurrent.atomic.AtomicBoolean
@SuppressLint("NotifyDataSetChanged")
class PrepareCookActivity : BaseActivity() {
@@ -40,6 +52,8 @@ class PrepareCookActivity : BaseActivity() {
private lateinit var binding: ActivityPrepareCookBinding
private val cameraUtils: CameraUtils by lazy { CameraUtils(this) }
private var food: FoodRecord? = null
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
@@ -69,6 +83,7 @@ class PrepareCookActivity : BaseActivity() {
binding.tvDishName.text = food?.foodName ?: ""
addViewClickListener()
initCamera(binding.flCameraContainer)
initRecyclerView()
getDishDetail()
@@ -81,6 +96,9 @@ class PrepareCookActivity : BaseActivity() {
//Log.d(TAG, "addViewClickListener: address=$address,state=$state,weight=$weight")
if (address == AddressUtil.ONE) {
binding.tvDishPartWeight.text = "${weight}"
if (state == WeightUtil.STATE_STABLE) {
recognizeFood(weight)
}
}
})
binding.ivWeightClear.setOnClickListener {
@@ -134,6 +152,25 @@ class PrepareCookActivity : BaseActivity() {
}
}
private val isTakingPhoto = AtomicBoolean(false)
/**
* 重量稳定时触发菜品识别
* @param weight 当前稳定重量
*/
private fun recognizeFood(weight: Double) {
if (weight < 10) {
if (recognizeDialog.isShowing) recognizeDialog.dismiss()
return
}
if (isTakingPhoto.get() || recognizeDialog.isShowing) return
isTakingPhoto.set(true)
cameraUtils.takePhoto(
succCallback = cameraSuccessCallback,
failCallback = cameraFailureCallback
)
}
private fun addFood() {
FoodSearchDialog().show(this) { item ->
val filterResult = list.firstOrNull { it.goodsId == item.goodsId }
@@ -251,6 +288,16 @@ class PrepareCookActivity : BaseActivity() {
private var firstReqSize = 0
private val list = mutableListOf<CookFoodGoodsEntity>()
private val recognizeDialog by lazy {
FoodRecognizeDialog(this) { item ->
// TODO: 处理识别结果选中
}
.apply {
setOnDismissListener {
isTakingPhoto.set(false)
}
}
}
private val dishPartAdapter by lazy {
DishPartAdapter(list).apply {
setOnItemClickListener { _, _, positon ->
@@ -326,6 +373,26 @@ class PrepareCookActivity : BaseActivity() {
}
}
override fun onResume() {
super.onResume()
cameraUtils.bind()
}
override fun onPause() {
super.onPause()
cameraUtils.unbind()
}
/**
* 初始化相机并绑定预览容器
* @param container 相机预览容器
*/
fun initCamera(container: ViewGroup) {
cameraUtils.initCamera()
val previewBinding = LayoutCameraPreviewBinding.inflate(layoutInflater, container)
cameraUtils.setPreviewController(previewBinding.previewView)
}
@Suppress("DEPRECATION")
@SuppressLint("GestureBackNavigation")
@Deprecated("Deprecated in Java")
@@ -367,4 +434,49 @@ class PrepareCookActivity : BaseActivity() {
}.show()
}
private val cameraSuccessCallback: (Uri) -> Unit = { uri ->
Log.d(TAG, "takePhoto success")
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val bitmap = ImageUtil.uriToBitmap(this@PrepareCookActivity, uri)
if (bitmap == null) {
Log.d(TAG, "takePhoto bitmap is null")
isTakingPhoto.set(false)
return@withContext
}
val foodScoreList = FoodModule.getFoodScoreList(bitmap)
if (foodScoreList.isEmpty()) {
Log.d(TAG, "takePhoto foodScoreList is empty")
isTakingPhoto.set(false)
return@withContext
}
withContext(Dispatchers.Main) {
queryFood(foodScoreList)
}
}
}
}
private val cameraFailureCallback: (String) -> Unit = { errMsg ->
Log.d(TAG, "takePhoto failure")
isTakingPhoto.set(false)
}
private fun queryFood(list: List<FoodModule.IdNameScore>) {
Log.d(TAG, "takePhoto queryFood, list=${list.toString()}")
//调用接口成功,返回食材列表设置isTakingPhoto.set(false),暂时写死数据
val foodList = mutableListOf<CookFoodGoodsEntity>()
list.forEachIndexed { index, food ->
val foodName = food.name.split("WP").first()
val foodScore = ((1-food.score) * 100).roundedDecimalPlace(2)
foodList.add(CookFoodGoodsEntity(
goodsId = index.toString(),
goodsName = "${foodName}-${foodScore}%",
foodId = index.toString()
))
}
recognizeDialog.loadData(foodList)
isTakingPhoto.set(false)
}
}
@@ -82,18 +82,19 @@ class SelectDishActivity : BaseActivity() {
private var configFinished = false
fun judgeDeviceConfig(block: () -> Unit) {
if (configFinished) {
block()
return
}
appViewModel.loadSeasoning {
if (it.isEmpty()) {
showDeviceConfigDialog()
return@loadSeasoning
}
configFinished = true
block()
}
// if (configFinished) {
// block()
// return
// }
// appViewModel.loadSeasoning {
// if (it.isEmpty()) {
// showDeviceConfigDialog()
// return@loadSeasoning
// }
// configFinished = true
// block()
// }
block()
}
private fun showDeviceConfigDialog() {