refactor(activity): 重构食材识别流程,改由接口查询识别结果,queryGoodsList 改为具名参数

This commit is contained in:
2026-05-06 16:41:35 +08:00
parent e5fc0c265d
commit 2de5ec39b5
7 changed files with 165 additions and 109 deletions
@@ -15,8 +15,8 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.FoodAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogFoodSearchBinding
import com.shuwei.dish.match.dialog.SeasoningSelectDialog.Companion.PAGE_SIZE
import com.shuwei.dish.match.entity.GoodsItem
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.KeyboardUtil
@@ -36,20 +36,25 @@ class FoodSearchDialog(
private val onItemSelected: (item: GoodsItem) -> Unit
) : BottomSheetDialog(activity, R.style.BottomSheet) {
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
private val list = mutableListOf<GoodsItem>().apply {
add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
companion object {
const val PAGE_SIZE = 50
}
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
private val list = mutableListOf<GoodsItem>()
// .apply {
// add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
// add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
// add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
// add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
// add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
// add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
// add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
// add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
// add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
// add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
// add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
// }
private val adapter = FoodAdapter(list).apply {
setOnItemClickListener { _, _, position ->
list[position].isClicked = true
@@ -63,7 +68,6 @@ class FoodSearchDialog(
private var goodsName: String? = null
private var pageNo = 1
private val pageSize = 50
init {
setContentView(binding.root)
@@ -160,16 +164,12 @@ class FoodSearchDialog(
* 请求食材列表
*/
private fun getGoodsList() {
val param = mutableMapOf<String, Any>(
"goodsType" to "0",
"placeId" to BaseApp.canteenId,
"pageNum" to pageNo,
"pageSize" to pageSize
activity.netViewModel.queryGoodsList(
goodsType = "0",
pageNum = pageNo,
pageSize = PAGE_SIZE,
goodsName = goodsName.takeUnless { it.isNullOrBlank() }
)
if (!goodsName.isNullOrBlank()) {
param["goodsName"] = goodsName!!
}
activity.netViewModel.queryGoodsList(param)
}
/**
@@ -185,7 +185,7 @@ class FoodSearchDialog(
if (pageNo == 1) list.clear()
list.addAll(records)
adapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
val isLoadMoreEnable = records.size >= PAGE_SIZE
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) pageNo++
}
@@ -20,7 +20,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.SeasoningSearchAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogSeasoningSelectBinding
import com.shuwei.dish.match.entity.GoodsItem
import com.shuwei.dish.match.entity.SeasoningEntity
@@ -43,6 +42,7 @@ class SeasoningSelectDialog(
companion object {
const val TAG = "SeasoningSelectDialog"
const val PAGE_SIZE = 50
}
private val binding = DialogSeasoningSelectBinding.inflate(LayoutInflater.from(activity))
@@ -60,7 +60,6 @@ class SeasoningSelectDialog(
private var goodsName: String? = null
private var pageNo = 1
private val pageSize = 50
private var collectJob: Job? = null
init {
@@ -181,16 +180,12 @@ class SeasoningSelectDialog(
* 请求调料列表
*/
private fun getGoodsList() {
val param = mutableMapOf<String, Any>(
"goodsType" to "1",
"placeId" to BaseApp.canteenId,
"pageNum" to pageNo,
"pageSize" to pageSize
activity.netViewModel.queryGoodsList(
goodsType = "1",
pageNum = pageNo,
pageSize = PAGE_SIZE,
goodsName = goodsName.takeUnless { it.isNullOrBlank() }
)
if (!goodsName.isNullOrBlank()) {
param["goodsName"] = goodsName!!
}
activity.netViewModel.queryGoodsList(param)
}
/**
@@ -206,7 +201,7 @@ class SeasoningSelectDialog(
if (pageNo == 1) list.clear()
list.addAll(records.map { it.toSeasoningEntity() })
adapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
val isLoadMoreEnable = records.size >= PAGE_SIZE
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) pageNo++
}
@@ -2,6 +2,7 @@ package com.shuwei.dish.match.net
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.GoodsItem
@@ -93,8 +94,29 @@ class NetViewModel(
/**
* 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用)
* @param goodsType 物品类型:0=食材,1=调料
* @param placeId 场所 ID,默认取当前食堂 ID
* @param pageNum 页码,默认第 1 页
* @param pageSize 每页条数,默认 50
* @param goodsName 按名称模糊搜索,为 null 时不传该字段
* @param goodsNames 按名称列表批量查询,为 null 时不传该字段
*/
fun queryGoodsList(param: MutableMap<String, Any>) {
fun queryGoodsList(
goodsType: String,
placeId: String = BaseApp.canteenId,
pageNum: Int = 1,
pageSize: Int = 50,
goodsName: String? = null,
goodsNames: List<String>? = null
) {
val param = mutableMapOf<String, Any>(
"goodsType" to goodsType,
"placeId" to placeId,
"pageNum" to pageNum,
"pageSize" to pageSize
)
goodsName?.let { param["goodsName"] = it }
goodsNames?.let { param["goodsNames"] = it }
viewModelScope.launch {
_goodsListState.value = UiState.Loading
_goodsListState.value = repository.queryGoodsList(param)
@@ -19,7 +19,12 @@ import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.visible
import androidx.core.net.toUri
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.toast
import kotlinx.coroutines.launch
/**
* 食材识别结果页面
@@ -34,24 +39,23 @@ class FoodRecognizeActivity : BaseActivity() {
const val EXTRA_SELECTED_ITEM = "extra_selected_item"
const val IS_MANUAL_CANCEL = "isManualCancel"
/** 内存缓存食材列表,避免通过 Intent 序列化传递大数据 */
private var pendingGoodsList: ArrayList<GoodsItem>? = null
private var recognizeNameList: ArrayList<FoodModule.IdNameScore>? = null
/**
* 启动本页面,通过 onResult 回调返回用户选中的食材
* @param activity 宿主 Activity
* @param imageUri 食材照片路径(可为空)
* @param goodsList 识别到的食材列表
* @param nameScoreList 识别到的食材列表
* @param finishCallback 页面关闭后回调,携带用户选中的食材(未选中时为 null)
*/
fun start(
activity: BaseActivity,
imageUri: String? = null,
currentWeight: Double = 0.0,
goodsList: ArrayList<GoodsItem> = arrayListOf(),
nameScoreList: ArrayList<FoodModule.IdNameScore> = arrayListOf(),
finishCallback: (Boolean, GoodsItem?) -> Unit = { _, _ -> }
) {
pendingGoodsList = goodsList
recognizeNameList = nameScoreList
val launchIntent = Intent(activity, FoodRecognizeActivity::class.java).apply {
putExtra(EXTRA_IMAGE_URI, imageUri)
putExtra(EXTRA_CURRENT_WEIGHT, currentWeight)
@@ -62,7 +66,8 @@ class FoodRecognizeActivity : BaseActivity() {
IntentCompat.getSerializableExtra(
it,
EXTRA_SELECTED_ITEM,
GoodsItem::class.java)
GoodsItem::class.java
)
}
finishCallback(isManualCancel, entity)
}
@@ -102,6 +107,12 @@ class FoodRecognizeActivity : BaseActivity() {
/** 食材搜索弹窗,持有引用防止重复打开 */
private var foodSearchDialog: FoodSearchDialog? = null
/** 通过name查询IdNameScore */
private val nameScoreMap = mutableMapOf<String, FoodModule.IdNameScore>()
/** 识别物品名称列表 */
private val goodsNameList = mutableListOf<String>()
private val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
updateScrollIndicator()
@@ -136,8 +147,6 @@ class FoodRecognizeActivity : BaseActivity() {
}
}
)
initData()
initRecyclerView()
updateRvParentLayoutHeight()
initClickListeners()
@@ -145,6 +154,8 @@ class FoodRecognizeActivity : BaseActivity() {
onBackPressedDispatcher.addCallback(this) {
manualBack()
}
initObserver()
initData()
}
/**
@@ -166,23 +177,75 @@ class FoodRecognizeActivity : BaseActivity() {
currentWeight = intent.getDoubleExtra(EXTRA_CURRENT_WEIGHT, 0.0)
binding.tvWeight.text = "${currentWeight}g"
// 取出内存缓存的食材列表,取用后立即清空
val goodsList = pendingGoodsList?.also { pendingGoodsList = null }
if (!goodsList.isNullOrEmpty()) {
list.addAll(goodsList)
// 默认选中第一项
selectedPosition = 0
list[0].isClicked = true
list[0].useWeight = currentWeight
binding.tvSelectedFood.text = list[0].goodsName ?: "-"
recognizeNameList?.apply {
forEach {
val goodsName = it.name.split("WP").first()
goodsNameList.add(goodsName)
nameScoreMap.put(goodsName, it)
}
recognizeNameList = null
}
netViewModel.queryGoodsList(
goodsType = "0",
goodsNames = goodsNameList
)
}
private fun initObserver() {
lifecycleScope.launch {
netViewModel.goodsListState.collect { state ->
when (state) {
is UiState.Loading -> {
showLoading()
}
is UiState.Success -> {
delayDismissLoading()
loadRecognizeList(state.data)
}
is UiState.Error -> {
delayDismissLoading()
toast(state.msg)
}
is UiState.Idle -> {}
}
}
}
}
private fun loadRecognizeList(goodsList: List<GoodsItem>?) {
if (goodsList.isNullOrEmpty()) {
binding.flListContainer.updateLayoutParams {
height = 260.dp
}
binding.rvRecognizeList.gone()
binding.tvNoData.visible()
return
}
binding.rvRecognizeList.visible()
binding.tvNoData.gone()
list.clear()
goodsList.forEach {
val food = nameScoreMap[it.goodsName]
val foodScore = ((1 - (food?.score ?: 0.0)) * 10000).toInt()
list.add(it.also { it.foodScore = foodScore })
}
adapter.notifyDataSetChanged()
updateRvParentLayoutHeight()
// 默认选中第一项
selectedPosition = 0
list[0].isClicked = true
list[0].useWeight = currentWeight
binding.tvSelectedFood.text = list[0].goodsName ?: "-"
}
/**
* 初始化 RecyclerView
*/
private fun initRecyclerView() {
binding.recyclerView.let {
binding.rvRecognizeList.let {
it.layoutManager = GridLayoutManager(this@FoodRecognizeActivity, 2)
it.adapter = adapter
it.isVerticalScrollBarEnabled = false
@@ -206,11 +269,8 @@ class FoodRecognizeActivity : BaseActivity() {
* 根据列表是否有数据控制 RecyclerView 显隐
*/
private fun updateListVisibility() {
if (list.isEmpty()) {
binding.flListContainer.gone()
} else {
binding.flListContainer.visible()
binding.recyclerView.post { updateScrollIndicator() }
if (list.isNotEmpty()) {
binding.rvRecognizeList.post { updateScrollIndicator() }
}
}
@@ -218,7 +278,7 @@ class FoodRecognizeActivity : BaseActivity() {
* 根据 RecyclerView 滚动状态刷新右侧自定义指示条
*/
private fun updateScrollIndicator() {
val recyclerView = binding.recyclerView
val recyclerView = binding.rvRecognizeList
val track = binding.vScrollIndicatorTrack
val thumb = binding.vScrollIndicatorThumb
@@ -295,7 +355,7 @@ class FoodRecognizeActivity : BaseActivity() {
updateRvParentLayoutHeight()
binding.tvSelectedFood.text = item.goodsName ?: "-"
updateListVisibility()
binding.recyclerView.post { updateScrollIndicator() }
binding.rvRecognizeList.post { updateScrollIndicator() }
}
/**
@@ -336,8 +396,8 @@ class FoodRecognizeActivity : BaseActivity() {
* 取消待执行的延迟重量更新任务
*/
private fun cancelPendingWeightUpdate() {
// pendingWeightRunnable?.let { handler.removeCallbacks(it) }
// pendingWeightRunnable = null
// pendingWeightRunnable?.let { handler.removeCallbacks(it) }
// pendingWeightRunnable = null
}
/**
@@ -240,7 +240,7 @@ class PrepareFoodActivity : BaseActivity() {
it.isSetFinished = true
})
binding.rvDishPartList.smoothScrollToPosition(list.size - 1)
binding.rvMaterialList.smoothScrollToPosition(list.size - 1)
}
}
@@ -357,7 +357,7 @@ class PrepareFoodActivity : BaseActivity() {
* RecyclerView初始化
*/
private fun initRecyclerView() {
binding.rvDishPartList.run {
binding.rvMaterialList.run {
layoutManager =
LinearLayoutManager(this@PrepareFoodActivity, LinearLayoutManager.VERTICAL, false)
// 仅 isNewDishType=true 的 item 显示侧滑删除菜单(必须在 setAdapter 之前调用)
@@ -489,7 +489,11 @@ class PrepareFoodActivity : BaseActivity() {
return@withContext
}
withContext(Dispatchers.Main) {
queryFood(foodScoreList)
Log.d(TAG, "takePhoto queryFood, list=$foodScoreList")
postDelayed(1000) {
dismissLoading()
loadRecognizeResultPage(foodScoreList)
}
}
}
}
@@ -505,47 +509,10 @@ class PrepareFoodActivity : BaseActivity() {
dismissLoading()
}
/**
* 调用接口查询识别到的菜品
* @param list 食材列表
*/
private fun queryFood(list: List<FoodModule.IdNameScore>) {
Log.d(TAG, "takePhoto queryFood, list=$list")
//调用接口成功,返回食材列表设置isTakingPhoto.set(false),暂时写死数据
val foodList = mutableListOf<GoodsItem>()
list.forEachIndexed { index, food ->
Log.d(TAG, "takePhoto queryFood, index=${index},food=$food")
var foodName = ""
var foodId = ""
food.name.split("WP").let {
foodName = it[0]
foodId = if (it.size > 1) {
"WP${it[1]}"
} else {
food.name
}
}
foodList.add(
GoodsItem(
goodsId = foodId,
goodsName = foodName
).also {
it.foodScore = ((1 - food.score) * 10000).toInt()
it.isOriginalData = false
}
)
}
Log.d(TAG, "takePhoto queryFood, 数据添加完成")
postDelayed(1000) {
dismissLoading()
loadRecognizeResultPage(foodList)
}
}
/**
* 加载识别结果页面
*/
private fun loadRecognizeResultPage(foodList: List<GoodsItem>) {
private fun loadRecognizeResultPage(nameScoreList: List<FoodModule.IdNameScore>) {
if (currentWeight < WEIGHT_RECOGNIZE_VALUE) {
Log.d(TAG, "takePhoto queryFood, 已取消")
toast("已取消")
@@ -557,7 +524,7 @@ class PrepareFoodActivity : BaseActivity() {
activity = this,
imageUri = lastPhotoUri?.toString(),
currentWeight = currentWeight,
goodsList = ArrayList(foodList),
nameScoreList = ArrayList(nameScoreList),
finishCallback = { isManualCancel, entity ->
Log.d(TAG, "queryFood: isManualCancel=$isManualCancel, entity=${entity.toJsonString()}")
manualCancelFlag = isManualCancel
@@ -35,7 +35,7 @@
android:layout_marginTop="12dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:id="@+id/rvRecognizeList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
@@ -68,6 +68,18 @@
android:layout_marginEnd="1dp"
android:background="@drawable/scrollbar_thumb_food_recognize"
android:visibility="gone" />
<TextView
android:id="@+id/tvNoData"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="无此食材,请联系管理员添加"
android:gravity="center"
android:textStyle="bold"
android:textSize="40sp"
android:textColor="@color/red_ff4444"
android:background="@color/white"
android:visibility="gone"/>
</FrameLayout>
<LinearLayout
@@ -93,7 +93,7 @@
</FrameLayout>
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/rvDishPartList"
android:id="@+id/rvMaterialList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="20dp"