feat(ui): 新增餐品列表和菜品包装页面
- 创建了 MealListActivity 餐品列表页面,用于展示可选的餐品 - 实现了餐品列表的 RecyclerView 展示和点击跳转功能 - 添加了 PackActivity 菜品包装页面,支持食材构成管理 - 集成了相机预览功能用于食材识别 - 实现了重量识别和食材添加功能 - 添加了侧滑删除菜单和食材重量管理 - 创建了 MealListAdapter 适配器用于餐品列表展示
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package com.shuwei.dish.match.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.databinding.ListItemFood2Binding
|
||||
import com.shuwei.dish.match.model.NutFoodOptionVO
|
||||
|
||||
/**
|
||||
* 餐品列表适配器
|
||||
*/
|
||||
class MealListAdapter(list: MutableList<NutFoodOptionVO>) :
|
||||
BaseQuickAdapter<NutFoodOptionVO, MealListAdapter.VH>(list) {
|
||||
|
||||
inner class VH(var binding: ListItemFood2Binding) : QuickViewHolder(binding.root)
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: VH,
|
||||
position: Int,
|
||||
item: NutFoodOptionVO?
|
||||
) {
|
||||
holder.binding.tvDishInfo.text = item?.foodName
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
context: Context,
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): VH {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val binding = ListItemFood2Binding.inflate(inflater, parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.shuwei.dish.match.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.shuwei.dish.match.adapter.MealListAdapter
|
||||
import com.shuwei.dish.match.base.BaseActivity
|
||||
import com.shuwei.dish.match.databinding.ActivityMealListBinding
|
||||
import com.shuwei.dish.match.model.NutFoodOptionVO
|
||||
import com.shuwei.dish.match.net.NetViewModelV2
|
||||
import com.shuwei.dish.match.net.UiState
|
||||
import com.shuwei.dish.match.utils.ext.gone
|
||||
import com.shuwei.dish.match.utils.ext.toast
|
||||
import com.shuwei.dish.match.utils.ext.visible
|
||||
|
||||
/**
|
||||
* 餐品列表页面
|
||||
*/
|
||||
class MealListActivity : BaseActivity() {
|
||||
|
||||
companion object {
|
||||
const val MEAL_ITEM = "mealItem"
|
||||
}
|
||||
|
||||
private val binding by lazy { ActivityMealListBinding.inflate(layoutInflater) }
|
||||
private val viewModelV2 by lazy { NetViewModelV2() }
|
||||
|
||||
private val mealList = mutableListOf<NutFoodOptionVO>()
|
||||
private val mealAdapter by lazy { MealListAdapter(mealList) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
setHeaderBackground()
|
||||
setTitleBar(titleBarAction = {
|
||||
it.visible()
|
||||
}, titleAction = {
|
||||
it.text = "餐品列表"
|
||||
}, rightIconAction = {
|
||||
it.gone()
|
||||
}, backAction = {
|
||||
it.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
})
|
||||
|
||||
initRecyclerView()
|
||||
loadMealList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 RecyclerView
|
||||
*/
|
||||
private fun initRecyclerView() {
|
||||
binding.rvMealList.layoutManager = LinearLayoutManager(this)
|
||||
binding.rvMealList.adapter = mealAdapter
|
||||
mealAdapter.setOnItemClickListener { _, _, position ->
|
||||
val item = mealList.getOrNull(position) ?: return@setOnItemClickListener
|
||||
val intent = Intent(this, PackActivity::class.java).apply {
|
||||
putExtra(PackActivity.MEAL_ITEM, item)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载餐品列表
|
||||
*/
|
||||
private fun loadMealList() {
|
||||
viewModelV2.getFoodOptionsWithCallback(
|
||||
keyword = null,
|
||||
pageNum = 1,
|
||||
pageSize = 100,
|
||||
onLoading = {
|
||||
showLoading("加载中……")
|
||||
},
|
||||
onResult = { state ->
|
||||
dismissLoading()
|
||||
when (state) {
|
||||
is UiState.Success -> {
|
||||
val records = state.data?.records
|
||||
if (records.isNullOrEmpty()) {
|
||||
toast("暂无数据")
|
||||
return@getFoodOptionsWithCallback
|
||||
}
|
||||
mealList.clear()
|
||||
mealList.addAll(records)
|
||||
mealAdapter.notifyDataSetChanged()
|
||||
}
|
||||
is UiState.Error -> {
|
||||
toast(state.msg)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
package com.shuwei.dish.match.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.addCallback
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.adapter.FoodMaterialAdapter
|
||||
import com.shuwei.dish.match.base.BaseActivity
|
||||
import com.shuwei.dish.match.databinding.ActivityPackBinding
|
||||
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
||||
import com.shuwei.dish.match.dialog.CommonDialog
|
||||
import com.shuwei.dish.match.model.CookFoodDTO
|
||||
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.model.FoodRecord
|
||||
import com.shuwei.dish.match.model.GoodsItem
|
||||
import com.shuwei.dish.match.model.NutFoodOptionVO
|
||||
import com.shuwei.dish.match.net.UiState
|
||||
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.SpTool
|
||||
import com.shuwei.dish.match.utils.WeightUtil
|
||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||
import com.shuwei.dish.match.utils.ext.dp
|
||||
import com.shuwei.dish.match.utils.ext.gone
|
||||
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 com.yanzhenjie.recyclerview.SwipeMenuItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.Serializable
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.math.abs
|
||||
import com.shuwei.dish.match.model.NutFoodComposition
|
||||
import com.shuwei.dish.match.net.NetViewModelV2
|
||||
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
class PackActivity : BaseActivity() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "PackActivity"
|
||||
const val MEAL_ITEM = "mealItem"
|
||||
const val PAGE_FROM = "pageFrom"
|
||||
const val HOME = "home"
|
||||
const val WEIGHT_CHANGE_VALUE = 5
|
||||
const val WEIGHT_RECOGNIZE_VALUE = 10
|
||||
|
||||
/** 主材数量上限 */
|
||||
const val MATERIAL_COUNT = 3
|
||||
}
|
||||
|
||||
private val binding by lazy { ActivityPackBinding.inflate(layoutInflater) }
|
||||
|
||||
private val cameraUtils: CameraUtils by lazy { CameraUtils(this) }
|
||||
|
||||
private var mealItem: NutFoodOptionVO? = null
|
||||
|
||||
/** 来源页面标识,HOME 时返回跳转 SamplingListActivity */
|
||||
private var pageFrom: String? = null
|
||||
|
||||
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
|
||||
private val viewModelV2 by lazy { NetViewModelV2() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
setHeaderBackground()
|
||||
pageFrom = intent.getStringExtra(PAGE_FROM)
|
||||
mealItem = IntentCompat.getParcelableExtra(intent, MEAL_ITEM, NutFoodOptionVO::class.java)
|
||||
|
||||
mealItem?.let {
|
||||
binding.etInputDish.run {
|
||||
setText(it.foodName)
|
||||
tag = it.foodName
|
||||
}
|
||||
}
|
||||
|
||||
val isSamplingMode = SpTool.cookMode == 1
|
||||
val titleText = if (isSamplingMode) "菜品信息采集" else "菜品净菜包装"
|
||||
setTitleBar(titleBarAction = {
|
||||
it.visible()
|
||||
}, titleAction = {
|
||||
it.text = titleText
|
||||
}, rightIconAction = {
|
||||
it.gone()
|
||||
}, backAction = {
|
||||
it.setOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
})
|
||||
|
||||
// 不可编辑,仅展示菜名
|
||||
binding.etInputDish.isFocusable = false
|
||||
binding.etInputDish.isFocusableInTouchMode = false
|
||||
binding.ivDishSearch.gone()
|
||||
|
||||
addViewClickListener()
|
||||
addBackKeyListener()
|
||||
initCamera(binding.flCameraContainer)
|
||||
initRecyclerView()
|
||||
initObserver()
|
||||
|
||||
// 根据传入的餐品数据加载食材列表
|
||||
mealItem?.let {
|
||||
val compositions = it.compositions
|
||||
if (!compositions.isNullOrEmpty()) {
|
||||
loadDishCompositions(compositions)
|
||||
} else {
|
||||
toast("该餐品无食材构成信息")
|
||||
}
|
||||
}
|
||||
}
|
||||
private var currentWeight = 0.0
|
||||
private fun addViewClickListener() {
|
||||
// ========== 重量识别相关功能已注释 ==========
|
||||
WeightUtil.addWeightListener(
|
||||
weightKey = TAG,
|
||||
getWeight = { address, state, weight ->
|
||||
if (address == AddressUtil.ONE && state == WeightUtil.STATE_STABLE) {
|
||||
runOnUiThread {
|
||||
currentWeight = weight - initialWeight
|
||||
binding.tvShowWeight.run {
|
||||
if (tag != currentWeight) {
|
||||
// text = "称重:${netWeight}g"
|
||||
val weightText = "${currentWeight.roundedOneDecimalPlace()}g"
|
||||
text = weightText
|
||||
tag = currentWeight
|
||||
}
|
||||
}
|
||||
//recognizeFood(netWeight)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (SpTool.cookMode == 1) {
|
||||
// 采集模式:绑定搜索栏交互
|
||||
binding.ivDishSearch.visible()
|
||||
binding.etInputDish.isFocusable = true
|
||||
binding.etInputDish.isFocusableInTouchMode = true
|
||||
binding.ivDishSearch.setOnClickListener { jumpSearch() }
|
||||
binding.etInputDish.addOnActionSearchListener { jumpSearch() }
|
||||
}
|
||||
|
||||
// binding.btnCook.clickWithDebounce {
|
||||
// if (SpTool.cookMode == 1) {
|
||||
// // 采集模式:从输入框创建 FoodRecord
|
||||
// val showFoodName = binding.etInputDish.text.toString().trim()
|
||||
// if (showFoodName.isBlank()) {
|
||||
// toast("菜品名称为空")
|
||||
// return@clickWithDebounce
|
||||
// }
|
||||
// if (list.isEmpty()) {
|
||||
// toast("菜品构成信息未设置")
|
||||
// return@clickWithDebounce
|
||||
// }
|
||||
// val foodRecord = FoodRecord().apply {
|
||||
// foodId = mealItem?.foodId?.toString()
|
||||
// foodName = showFoodName
|
||||
// cookMode = 1
|
||||
// }
|
||||
// Log.d(TAG, "goToSubmit: goodsList:${list.toJsonString()}")
|
||||
// val submitList = list.map { it.toCookFoodGoodsEntity() }.toMutableList()
|
||||
// startActivity<SubmitFoodActivity> {
|
||||
// putExtra(SubmitFoodActivity.GOODS_LIST, submitList as Serializable)
|
||||
// putExtra(SubmitFoodActivity.FOOD_ITEM, foodRecord as Serializable)
|
||||
// }
|
||||
// } else {
|
||||
// // 包装模式:检查食材是否全部设置完成
|
||||
// val count = list.count { it.isSetFinished.not() }
|
||||
// if (count > 0) {
|
||||
// showRemindDialog()
|
||||
// return@clickWithDebounce
|
||||
// }
|
||||
// openSubmitPage()
|
||||
// }
|
||||
// }
|
||||
|
||||
binding.ivClearWeight.clickWithDebounce {
|
||||
initialWeight = 0.0
|
||||
WeightUtil.tareTwo(1)
|
||||
}
|
||||
|
||||
binding.ivAddWeight.clickWithDebounce {
|
||||
if(currentWeight <= 0.0) {
|
||||
toast("当前秤重量无效")
|
||||
return@clickWithDebounce
|
||||
}
|
||||
list.getOrNull(clickIndex)?.let {
|
||||
it.useWeight = currentWeight
|
||||
it.isSetFinished = true
|
||||
materialAdapter.notifyItemChanged(clickIndex)
|
||||
}
|
||||
}
|
||||
binding.btnGetCode.clickWithDebounce {
|
||||
val item = list.getOrNull(clickIndex)?:return@clickWithDebounce
|
||||
//viewModelV2.getCode
|
||||
}
|
||||
binding.btnSubmit.clickWithDebounce {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// private val isTakingPhoto = AtomicBoolean(false)
|
||||
|
||||
// ========== 重量识别相关变量已注释 ==========
|
||||
// private var showRecognizePage = false
|
||||
// private var pageVisible = true
|
||||
// private var manualCancelFlag = false
|
||||
// private var lastPhotoUri: Uri? = null
|
||||
// private var lastWeight = 0.0
|
||||
// private var currentWeight = 0.0
|
||||
private var initialWeight = SpTool.configWeight.toDouble()
|
||||
|
||||
// private fun recognizeFood(weight: Double) {
|
||||
// // 重量识别逻辑已注释
|
||||
// }
|
||||
|
||||
/**
|
||||
* 重新计算 list 中非接口数据(isOriginalData=false)的 materialType
|
||||
*
|
||||
* 规则:
|
||||
* - 接口数据主材数量 n = list 中 isOriginalData=true 且 materialType=1 的数量
|
||||
* - 有接口数据且 MATERIAL_COUNT - n <= 0:所有非接口数据均为辅材(2)
|
||||
* - 无接口数据,或 MATERIAL_COUNT - n > 0:m = MATERIAL_COUNT - n,
|
||||
* 非接口数据按 useWeight 从大到小排序,前 m 个为主材(1),其余为辅材(2)
|
||||
*/
|
||||
private fun updateMaterialTypes() {
|
||||
val hasOriginalData = list.any { it.isOriginalData }
|
||||
// 接口数据中主材数量
|
||||
val n = list.count { it.isOriginalData && it.materialType == 1 }
|
||||
// 待分配的非接口数据
|
||||
val nonOriginalList = list.filter { !it.isOriginalData }
|
||||
|
||||
if (hasOriginalData && MATERIAL_COUNT - n <= 0) {
|
||||
// 主材已满,所有非接口数据均为辅材
|
||||
nonOriginalList.forEach { it.materialType = 2 }
|
||||
} else {
|
||||
// 剩余可分配主材名额
|
||||
val m = MATERIAL_COUNT - n
|
||||
// 按 useWeight 从大到小排序
|
||||
val sorted = nonOriginalList.sortedByDescending { it.useWeight ?: 0.0 }
|
||||
sorted.forEachIndexed { index, item ->
|
||||
item.materialType = if (index < m) 1 else 2
|
||||
}
|
||||
}
|
||||
materialAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun openSubmitPage() {
|
||||
getGoodsList()
|
||||
if (goodsList.isNullOrEmpty()) {
|
||||
toast("无菜品构成信息")
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "goToSubmit: goodsList:${goodsList?.toJsonString()}")
|
||||
startActivity<SubmitFoodActivity> {
|
||||
putExtra(SubmitFoodActivity.GOODS_LIST, goodsList as Serializable)
|
||||
putExtra(SubmitFoodActivity.FOOD_ITEM, FoodRecord().apply {
|
||||
foodId = mealItem?.foodId?.toString()
|
||||
foodName = mealItem?.foodName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGoodsList() {
|
||||
if (goodsList == null) {
|
||||
goodsList = mutableListOf()
|
||||
} else {
|
||||
goodsList!!.clear()
|
||||
}
|
||||
// 将 Goods Item 转换为 CookFoodGoodsEntity 并补充菜品相关字段
|
||||
list.filter { it.isSetFinished }.forEach {
|
||||
goodsList?.add(it.toCookFoodGoodsEntity().apply {
|
||||
foodId = mealItem?.foodId?.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRemindDialog() {
|
||||
CommonDialog(this)
|
||||
.setTitle(getString(R.string.food_remind_01))
|
||||
.setContent(getString(R.string.food_remind_02))
|
||||
.setNegativeButton("返回调整")
|
||||
.setPositiveButton("确认无误") { openSubmitPage() }
|
||||
.setOnDismissCallback { hideStatusBar() }
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
private fun initObserver() {
|
||||
lifecycleScope.launch {
|
||||
netViewModel.foodDetailState.collect { state ->
|
||||
when (state) {
|
||||
is UiState.Success -> {
|
||||
val detail = state.data
|
||||
if (detail == null) {
|
||||
toast("查询菜品信息为空")
|
||||
return@collect
|
||||
}
|
||||
loadDishDetail(detail)
|
||||
}
|
||||
is UiState.Error -> toast(state.msg)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== getDishDetail 方法已删除,不再通过接口查询 ==========
|
||||
|
||||
private fun loadDishDetail(detail: CookFoodDTO) {
|
||||
val voList = detail.foodConstituteList
|
||||
if (voList.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
// 筛选出主材和辅材,转换为 Goods Item(useWeight 置零)
|
||||
val tempData = voList.filter { it.materialType == 1 || it.materialType == 2 }
|
||||
.map { it.toGoodsItem().also { item -> item.useWeight = 0.0 } }
|
||||
// 直接替换 adapter.items
|
||||
list.clear()
|
||||
list.addAll(tempData)
|
||||
materialAdapter.items = list
|
||||
materialAdapter.notifyDataSetChanged()
|
||||
firstReqSize = list.size
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载餐品食材构成(从 NutFoodOptionVO.compositions 解析)
|
||||
*/
|
||||
private fun loadDishCompositions(compositions: List<NutFoodComposition>) {
|
||||
// 将 NutFoodComposition 转换为 Goods Item
|
||||
val tempData = compositions.map { comp ->
|
||||
GoodsItem().apply {
|
||||
goodsId = comp.materId?.toString() ?: ""
|
||||
goodsName = comp.ingredientName ?: ""
|
||||
materialType = if (comp.isMain == 1) 1 else 2
|
||||
useWeight = 0.0
|
||||
isOriginalData = true
|
||||
isSetFinished = false
|
||||
}
|
||||
}.toMutableList()
|
||||
|
||||
list.clear()
|
||||
list.addAll(tempData)
|
||||
materialAdapter.items = list
|
||||
materialAdapter.notifyDataSetChanged()
|
||||
firstReqSize = list.size
|
||||
}
|
||||
|
||||
private var firstReqSize = 0
|
||||
private val list = mutableListOf<GoodsItem>()
|
||||
private var clickIndex = -1
|
||||
private val materialAdapter by lazy {
|
||||
FoodMaterialAdapter(list).apply {
|
||||
onItemClick = { positon ->
|
||||
this@PackActivity.clickIndex = positon
|
||||
list.forEachIndexed { index, entity ->
|
||||
entity.isClicked = index == positon
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
addOnItemChildClickListener(R.id.ivClearIcon) { _, _, position ->
|
||||
val item = list.getOrNull(position) ?: return@addOnItemChildClickListener
|
||||
Log.d(TAG, "onFoodItemClick: ${item.toJsonString()}")
|
||||
if (item.isOriginalData) {
|
||||
// 无重量信息时转为 item 整体点击(触发选中效果)
|
||||
if (item.useWeight == null || item.useWeight == 0.0) {
|
||||
onItemClick?.invoke(position)
|
||||
return@addOnItemChildClickListener
|
||||
}
|
||||
deleteRemindDialog("清除确认", "确定清除食材「${item.goodsName}」添加的重量吗?") {
|
||||
item.run {
|
||||
isNewDishType = false
|
||||
useWeight = 0.0
|
||||
isSetFinished = false
|
||||
}
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
return@addOnItemChildClickListener
|
||||
}
|
||||
|
||||
deleteRemindDialog("删除确认", "确定删除食材「${item.goodsName}」吗?") {
|
||||
removeFood(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RecyclerView初始化
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun initRecyclerView() {
|
||||
binding.rvMaterialList.let {
|
||||
it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||
// 仅 isNewDishType=true 的 item 显示侧滑删除菜单(必须在 setAdapter 之前调用)
|
||||
it.setSwipeMenuCreator { _, rightMenu, position ->
|
||||
if (list.getOrNull(position)?.isNewDishType == true) {
|
||||
rightMenu.addMenuItem(buildDeleteMenuItem())
|
||||
}
|
||||
}
|
||||
// 点击侧滑菜单项:先关闭菜单,再弹窗确认删除
|
||||
it.setOnItemMenuClickListener { menuBridge, position ->
|
||||
menuBridge.closeMenu()
|
||||
val item = list.getOrNull(position) ?: return@setOnItemMenuClickListener
|
||||
deleteRemindDialog("删除确认", "确定删除食材「${item.goodsName}」吗?") {
|
||||
removeFood(position)
|
||||
}
|
||||
}
|
||||
// item 点击事件
|
||||
it.setOnItemClickListener { _, position ->
|
||||
materialAdapter.onItemClick?.invoke(position)
|
||||
}
|
||||
handleRvTouchHideKeyboard()
|
||||
it.adapter = materialAdapter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除提醒
|
||||
*/
|
||||
private fun deleteRemindDialog(title: String, content: String, action: () -> Unit) {
|
||||
CommonDialog(this@PackActivity)
|
||||
.setTitle(title)
|
||||
.setContent(content)
|
||||
.setNegativeButton("取消")
|
||||
.setPositiveButton("确认") {
|
||||
action()
|
||||
}.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除菜品
|
||||
*/
|
||||
private fun removeFood(position: Int) {
|
||||
materialAdapter.removeAt(position)
|
||||
toast("已删除")
|
||||
updateMaterialTypes()
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建侧滑删除菜单项
|
||||
*/
|
||||
private fun buildDeleteMenuItem(): SwipeMenuItem {
|
||||
return SwipeMenuItem(this).apply {
|
||||
setImage(R.drawable.ic_trash_white)
|
||||
setBackground(R.drawable.bg_swipe_delete)
|
||||
width = 160.dp
|
||||
height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// pageVisible = true
|
||||
// showRecognizePage = false
|
||||
cameraUtils.bind()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// pageVisible = false
|
||||
cameraUtils.unbind()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化相机并绑定预览容器
|
||||
* @param container 相机预览容器
|
||||
*/
|
||||
fun initCamera(container: ViewGroup) {
|
||||
cameraUtils.initCamera()
|
||||
val previewBinding = LayoutCameraPreviewBinding.inflate(layoutInflater, container)
|
||||
cameraUtils.setPreviewController(previewBinding.previewView)
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册返回键监听,替代已废弃的 onBackPressed()
|
||||
*/
|
||||
private fun addBackKeyListener() {
|
||||
onBackPressedDispatcher.addCallback(this) {
|
||||
if (SpTool.cookMode == 1) {
|
||||
// 采集模式:有新增食材时提示保存
|
||||
if (list.isEmpty().not()) {
|
||||
saveDataRemindDialog()
|
||||
return@addCallback
|
||||
}
|
||||
if (pageFrom == HOME) {
|
||||
startActivity<SamplingModeActivity>()
|
||||
}
|
||||
} else {
|
||||
// 包装模式:有未保存数据时提示
|
||||
val count = list.count { !it.isOriginalData }
|
||||
if (count > 0) {
|
||||
saveDataRemindDialog()
|
||||
return@addCallback
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 未保存数据提醒
|
||||
*/
|
||||
private fun saveDataRemindDialog() {
|
||||
CommonDialog(this)
|
||||
.setTitle("返回提示")
|
||||
.setContent("您好,当前页面存在未保存的数据,\n确认返回吗?")
|
||||
.setNegativeButton("取消")
|
||||
.setPositiveButton("确认") {
|
||||
if (SpTool.cookMode == 1 && pageFrom == HOME) {
|
||||
startActivity<SamplingModeActivity>()
|
||||
}
|
||||
finish()
|
||||
}
|
||||
.setOnDismissCallback { hideStatusBar() }
|
||||
.show()
|
||||
}
|
||||
|
||||
// ========== 拍照回调相关功能已注释 ==========
|
||||
// private val cameraSuccessCallback: (Uri) -> Unit = { uri ->
|
||||
// // 拍照成功回调已注释
|
||||
// }
|
||||
// private var notRecognizeDialog: CommonDialog? = null
|
||||
// private fun showNotRecognizeDialog() {
|
||||
// // 未识别弹窗已注释
|
||||
// }
|
||||
// private val cameraFailureCallback: (String) -> Unit = { errMsg ->
|
||||
// // 拍照失败回调已注释
|
||||
// }
|
||||
// private fun loadRecognizeResultPage(nameScoreList: List<FoodModule.IdNameScore>) {
|
||||
// // 识别结果页加载已注释
|
||||
// }
|
||||
|
||||
/**
|
||||
* 跳转食物搜索页(采集模式专用)
|
||||
*/
|
||||
private fun jumpSearch() {
|
||||
val searchContent = binding.etInputDish.text.toString().trim()
|
||||
if (searchContent.isBlank()) {
|
||||
toast(binding.etInputDish.hint.toString())
|
||||
return
|
||||
}
|
||||
val launchIntent = Intent(this, FoodSearchActivity::class.java).apply {
|
||||
putExtra(FoodSearchActivity.FOOD_NAME, searchContent)
|
||||
}
|
||||
startActivity(launchIntent) { resultIntent ->
|
||||
if (resultIntent == null) return@startActivity
|
||||
val food = IntentCompat.getSerializableExtra(resultIntent, FoodSearchActivity.FOOD_ITEM, FoodRecord::class.java)
|
||||
Log.d(TAG, "jumpSearch: record=$food")
|
||||
if (food == null) return@startActivity
|
||||
binding.etInputDish.run {
|
||||
setText(food.foodName)
|
||||
tag = food.foodName
|
||||
setSelection(text.length)
|
||||
}
|
||||
}
|
||||
hideKeyboard()
|
||||
}
|
||||
|
||||
private fun handleRvTouchHideKeyboard() {
|
||||
val gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
hideKeyboard()
|
||||
return false
|
||||
}
|
||||
})
|
||||
binding.rvMaterialList.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
|
||||
private var startX = 0f
|
||||
private var startY = 0f
|
||||
|
||||
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
|
||||
when (e.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
startX = e.x
|
||||
startY = e.y
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val dx = abs(e.x - startX)
|
||||
val dy = abs(e.y - startY)
|
||||
if (dx > dy && dx > 10) {
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
}
|
||||
gestureDetector.onTouchEvent(e)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:background="@drawable/bg_other_page">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="30dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/shape_white_30_corners"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:padding="10dp"
|
||||
android:text="餐品列表"
|
||||
android:textColor="@color/black666"
|
||||
android:textSize="28sp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvMealList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="vertical"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/list_item_food2" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,231 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:background="@drawable/bg_other_page">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llSearchBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="30dp"
|
||||
android:background="@drawable/shape_white_30_corners"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInputDish"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginHorizontal="28dp"
|
||||
android:layout_weight="1"
|
||||
android:autofillHints=""
|
||||
android:background="@color/white"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:hint="@string/dish_search_hint"
|
||||
android:imeOptions="actionSearch"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="3dp"
|
||||
android:paddingEnd="3dp"
|
||||
android:textColor="@color/black333"
|
||||
android:textColorHint="@color/gray_c8"
|
||||
android:textSize="40sp"
|
||||
tools:ignore="TextFields" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivDishSearch"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:src="@drawable/ic_search_gray"
|
||||
android:visibility="gone"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="30dp"
|
||||
android:background="@drawable/shape_white_30_corners"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvGoodsName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="豆腐" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMaterialType"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:textColor="@color/black999"
|
||||
android:textSize="26sp"
|
||||
tools:text="主材" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnGetCode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:text="获取溯源码"
|
||||
android:textSize="18sp"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/dish_green"
|
||||
android:background="@drawable/ripple_effect_light"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginHorizontal="30dp"
|
||||
app:dividerColor="@color/gray_eb" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:text="用量(克)"
|
||||
android:textColor="@color/black666"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvShowWeight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/shape_white_f6_10_corners"
|
||||
android:gravity="center"
|
||||
android:text="-"
|
||||
android:textColor="@color/gray_d6"
|
||||
android:textSize="50sp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivClearWeight"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="90dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:src="@drawable/ic_weight_clear" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivAddWeight"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="90dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:src="@drawable/ic_weight_add" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:text="在下方列表中选择食材计重"
|
||||
android:textColor="@color/black999"
|
||||
android:textSize="26sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="30dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/shape_white_30_corners"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginVertical="30dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/dish_composition"
|
||||
android:textColor="@color/black666"
|
||||
android:textSize="28sp" />
|
||||
|
||||
<!-- 重量显示已注释,重量识别功能整体已移除 -->
|
||||
<!--
|
||||
<TextView
|
||||
android:id="@+id/tvShowWeight"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="80dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:paddingHorizontal="30dp"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/black666"
|
||||
android:textSize="28sp" />
|
||||
-->
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flCameraContainer"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.yanzhenjie.recyclerview.SwipeRecyclerView
|
||||
android:id="@+id/rvMaterialList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="vertical"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/list_item_food_material" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnSubmit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
android:layout_margin="30dp"
|
||||
android:background="@drawable/shape_green_bg"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:text="提交"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user