feat(ui): 添加用料明细预览功能和清空数据确认对话框

- 在 SubmitFoodActivity 中添加右侧图标显示并实现点击打开用料明细预览弹窗
- 新增 GoodsPreviewDialog 用于展示底部弹出的用料明细列表
- 创建 GoodsPreviewAdapter 和相关布局文件实现用料列表展示
- 添加 ic_list_view 图标用于右上角按钮
- 在 VectorCollectionFragment 中为清空数据按钮添加确认对话框
- 优化 WeightUtil 工具类添加可用性检查避免异常调用
This commit is contained in:
2026-05-19 11:15:33 +08:00
parent aa539d87d0
commit 292fdade81
8 changed files with 354 additions and 3 deletions
@@ -0,0 +1,46 @@
package com.shuwei.dish.match.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.shuwei.dish.match.databinding.ItemGoodsPreviewBinding
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
/**
* 用料明细预览列表适配器
* 显示字段:foodId、goodsId、goodsCode、goodsName、materialType、useWeight
*/
class GoodsPreviewAdapter(
private val list: List<CookFoodGoodsEntity>
) : RecyclerView.Adapter<GoodsPreviewAdapter.VH>() {
inner class VH(val binding: ItemGoodsPreviewBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val binding = ItemGoodsPreviewBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return VH(binding)
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: VH, position: Int) {
val item = list[position]
holder.binding.run {
tvGoodsId.text = item.goodsId
tvGoodsCode.text = item.goodsCode.orEmpty()
tvGoodsName.text = item.goodsName.orEmpty()
// materialType1主料 2辅料 3调料
tvMaterialType.text = when (item.materialType) {
1 -> "主料"
2 -> "辅料"
3 -> "调料"
else -> "${item.materialType}"
}
tvUseWeight.text = "${item.useWeight ?: 0.0}g"
}
}
override fun getItemCount() = list.size
}
@@ -0,0 +1,80 @@
package com.shuwei.dish.match.dialog
import android.graphics.Rect
import android.view.KeyboardShortcutGroup
import android.view.LayoutInflater
import android.view.Menu
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.GoodsPreviewAdapter
import com.shuwei.dish.match.databinding.DialogGoodsPreviewBinding
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.visible
/**
* 用料明细预览弹窗,以 BottomSheetDialog 形式展示当前 goodsList
* @param context 宿主 ContextActivity
* @param goodsList 需要展示的物品列表
*/
class GoodsPreviewDialog(
context: android.content.Context,
private val goodsList: List<CookFoodGoodsEntity>
) : BottomSheetDialog(context, R.style.BottomSheet) {
private val binding = DialogGoodsPreviewBinding.inflate(LayoutInflater.from(context))
init {
setContentView(binding.root)
setCancelable(true)
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
binding.rvGoodsList.run {
layoutManager = LinearLayoutManager(context)
adapter = GoodsPreviewAdapter(goodsList)
}
// foodId 对同一菜品所有条目相同,统一显示在列表上方
val foodId = goodsList.firstOrNull()?.foodId.orEmpty()
binding.tvFoodId.text = "菜品ID${foodId.ifEmpty { "-" }}"
if (goodsList.isEmpty()) {
binding.rvGoodsList.gone()
binding.tvEmpty.visible()
}
}
/** 弹出时注入 Window.Callback,解决 RecyclerView 与 BottomSheet 拖拽的滑动冲突 */
override fun show() {
super.show()
val originalCallback = window?.callback ?: return
window?.callback = object : android.view.Window.Callback by originalCallback {
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
val rect = Rect()
binding.rvGoodsList.getGlobalVisibleRect(rect)
behavior.isDraggable = !rect.contains(event.rawX.toInt(), event.rawY.toInt())
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
behavior.isDraggable = true
}
}
return originalCallback.dispatchTouchEvent(event)
}
override fun onPointerCaptureChanged(hasCapture: Boolean) {
originalCallback.onPointerCaptureChanged(hasCapture)
}
override fun onProvideKeyboardShortcuts(
data: List<KeyboardShortcutGroup?>?,
menu: Menu?,
deviceId: Int
) {
originalCallback.onProvideKeyboardShortcuts(data, menu, deviceId)
}
}
}
}
@@ -18,6 +18,7 @@ import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivitySubmitFoodBinding import com.shuwei.dish.match.databinding.ActivitySubmitFoodBinding
import com.shuwei.dish.match.dialog.CommonDialog import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.dialog.GoodsPreviewDialog
import com.shuwei.dish.match.db.entity.CookFoodEntity import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.model.FoodRecord import com.shuwei.dish.match.model.FoodRecord
@@ -30,7 +31,6 @@ import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.appendText import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.clickWithDebounce 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.roundedDecimalPlace
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.startActivity
@@ -128,7 +128,14 @@ class SubmitFoodActivity : BaseActivity() {
}, titleAction = { }, titleAction = {
it.text = food?.foodName it.text = food?.foodName
}, rightIconAction = { }, rightIconAction = {
it.gone() it.visible()
it.setImageResource(R.drawable.ic_list_view)
it.setOnClickListener {
lifecycleScope.launch {
val list = buildPreviewGoodsList()
GoodsPreviewDialog(this@SubmitFoodActivity, list).show()
}
}
}, backAction = { }, backAction = {
it.setOnClickListener { it.setOnClickListener {
remindSaveDataDialog() remindSaveDataDialog()
@@ -492,6 +499,19 @@ class SubmitFoodActivity : BaseActivity() {
} }
/**
* 构建用于预览弹窗的 goodsList:
* 取 goodsList 中的主辅材,再用 adapter 中最新调料覆盖原有调料数据(materialType==3
* 调料的完整字段(含 goodsCode)通过 buildSeasoningEntity 从 DB 补全
*/
private suspend fun buildPreviewGoodsList(): List<CookFoodGoodsEntity> {
val base = goodsList?.filter { it.materialType != 3 }?.toMutableList() ?: mutableListOf()
seasoningAdapter.items
.filter { it.useWeight > 0.0 }
.forEach { base.add(buildSeasoningEntity(it)) }
return base
}
override fun onDestroy() { override fun onDestroy() {
WeightUtil.removeWeightListener(TAG) WeightUtil.removeWeightListener(TAG)
super.onDestroy() super.onDestroy()
@@ -32,6 +32,7 @@ import com.shuwei.dish.match.databinding.FragmentVectorCollectionBinding
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.dialog.Loading import com.shuwei.dish.match.dialog.Loading
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.model.FoodCollectionBean import com.shuwei.dish.match.model.FoodCollectionBean
import com.shuwei.dish.match.model.GoodsItem import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.net.UiState import com.shuwei.dish.match.net.UiState
@@ -299,7 +300,14 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
} }
takePhoto() takePhoto()
} }
binding.btnClearData.setOnClickListener { clearData() } binding.btnClearData.setOnClickListener {
CommonDialog(requireContext())
.setTitle("清空确认")
.setContent("确认清空所有已采集的图片数据?")
.setNegativeButton("取消")
.setPositiveButton("确认") { clearData() }
.show()
}
binding.rvSearchFood.let { binding.rvSearchFood.let {
it.layoutManager = GridLayoutManager(context, 2) it.layoutManager = GridLayoutManager(context, 2)
it.adapter = searchAdapter it.adapter = searchAdapter
@@ -72,6 +72,7 @@ object WeightUtil {
} }
fun getWeight() { fun getWeight() {
if (!isAvailable) return
try { try {
Weigher2.getWeight() Weigher2.getWeight()
} catch (e: Exception) { } catch (e: Exception) {
@@ -95,6 +96,7 @@ object WeightUtil {
} }
fun tareTwo(address: Int) { fun tareTwo(address: Int) {
if (!isAvailable) return
try { try {
Log.d(TAG, "tareTwo,执行清零操作: address=$address") Log.d(TAG, "tareTwo,执行清零操作: address=$address")
// SDK 内部自动管理总线,直接发送清零命令 // SDK 内部自动管理总线,直接发送清零命令
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M3,5 h18 v2 h-18 Z
M3,11 h18 v2 h-18 Z
M3,17 h18 v2 h-18 Z" />
</vector>
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_top"
android:orientation="vertical">
<!-- 顶部拖拽指示条 -->
<View
android:layout_width="90dp"
android:layout_height="10dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:background="@drawable/shape_gray_dc_5" />
<!-- 标题 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="用料明细"
android:textColor="@color/black333"
android:textSize="36sp"
android:textStyle="bold" />
<!-- 菜品ID -->
<TextView
android:id="@+id/tvFoodId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:layout_marginBottom="20dp"
android:textColor="@color/black666"
android:textSize="26sp" />
<!-- 表头 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white_f6"
android:orientation="horizontal"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="20dp"
android:paddingBottom="20dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="物品ID"
android:textColor="@color/black999"
android:textSize="24sp" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:text="物品名称"
android:textColor="@color/black999"
android:textSize="24sp" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="物料编码"
android:textColor="@color/black999"
android:textSize="24sp" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center"
android:text="物料类型"
android:textColor="@color/black999"
android:textSize="24sp" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="end"
android:text="用量(g)"
android:textColor="@color/black999"
android:textSize="24sp" />
</LinearLayout>
<!-- 列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvGoodsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
android:maxHeight="700dp"
android:nestedScrollingEnabled="true"
android:overScrollMode="never" />
<!-- 空布局 -->
<TextView
android:id="@+id/tvEmpty"
android:layout_width="match_parent"
android:layout_height="200dp"
android:gravity="center"
android:text="暂无用料数据"
android:textColor="@color/gray_c8"
android:textSize="28sp"
android:visibility="gone" />
</LinearLayout>
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_bottom_line"
android:orientation="horizontal"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="24dp"
android:paddingBottom="24dp">
<!-- goodsId -->
<TextView
android:id="@+id/tvGoodsId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/black666"
android:textSize="26sp" />
<!-- goodsName -->
<TextView
android:id="@+id/tvGoodsName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/black333"
android:textSize="26sp"
android:textStyle="bold" />
<!-- goodsCode -->
<TextView
android:id="@+id/tvGoodsCode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/black333"
android:textSize="26sp"
android:textStyle="bold" />
<!-- materialType -->
<TextView
android:id="@+id/tvMaterialType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center"
android:maxLines="1"
android:textColor="@color/dish_green"
android:textSize="26sp" />
<!-- useWeight -->
<TextView
android:id="@+id/tvUseWeight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="end"
android:maxLines="1"
android:textColor="@color/black333"
android:textSize="26sp" />
</LinearLayout>