Compare commits
7
Commits
9cfc481cf3
...
6595c4bde0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6595c4bde0 | ||
|
|
2de5ec39b5 | ||
|
|
e5fc0c265d | ||
|
|
05063ee9de | ||
|
|
af737b910c | ||
|
|
12c6b5cf94 | ||
|
|
e990652981 |
+8
-5
@@ -5,13 +5,16 @@ 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.ListItemDishRecordBinding
|
||||
import com.shuwei.dish.match.databinding.ListItemFood2Binding
|
||||
import com.shuwei.dish.match.entity.FoodRecord
|
||||
|
||||
class FoodRecordAdapter(list: MutableList<FoodRecord>) :
|
||||
BaseQuickAdapter<FoodRecord, FoodRecordAdapter.VH>(list) {
|
||||
/**
|
||||
* 菜品搜索页面的适配器
|
||||
*/
|
||||
class Food2Adapter(list: MutableList<FoodRecord>) :
|
||||
BaseQuickAdapter<FoodRecord, Food2Adapter.VH>(list) {
|
||||
|
||||
inner class VH(var binding: ListItemDishRecordBinding) : QuickViewHolder(binding.root)
|
||||
inner class VH(var binding: ListItemFood2Binding) : QuickViewHolder(binding.root)
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: VH,
|
||||
@@ -27,7 +30,7 @@ class FoodRecordAdapter(list: MutableList<FoodRecord>) :
|
||||
viewType: Int
|
||||
): VH {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val binding = ListItemDishRecordBinding.inflate(inflater, parent, false)
|
||||
val binding = ListItemFood2Binding.inflate(inflater, parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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.ListItemFood3Binding
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
|
||||
/**
|
||||
* 向量采集页面的适配器
|
||||
*/
|
||||
class Food3Adapter(list: MutableList<GoodsItem>) :
|
||||
BaseQuickAdapter<GoodsItem, Food3Adapter.VH>(list) {
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: VH,
|
||||
position: Int,
|
||||
item: GoodsItem?
|
||||
) {
|
||||
item ?: return
|
||||
holder.binding.tvFoodName.run {
|
||||
text = item.goodsName
|
||||
isChecked = item.isClicked
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
context: Context,
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): VH {
|
||||
val binding = ListItemFood3Binding.inflate(LayoutInflater.from(context), parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
inner class VH(var binding: ListItemFood3Binding) : QuickViewHolder(binding.root)
|
||||
|
||||
}
|
||||
+15
-12
@@ -7,41 +7,45 @@ import androidx.core.content.ContextCompat
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.databinding.ListItemSearchGoodsInfoBinding
|
||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.databinding.ListItemFoodBinding
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import com.shuwei.dish.match.utils.ext.gone
|
||||
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
|
||||
import com.shuwei.dish.match.utils.ext.visible
|
||||
|
||||
class GoodsInfoSearchAdapter(private var list: MutableList<CookFoodGoodsEntity>) :
|
||||
BaseQuickAdapter<CookFoodGoodsEntity, GoodsInfoSearchAdapter.VH>(list) {
|
||||
/**
|
||||
* 识别页面和搜索弹窗的适配器
|
||||
*/
|
||||
class FoodAdapter(list: MutableList<GoodsItem>) :
|
||||
BaseQuickAdapter<GoodsItem, FoodAdapter.VH>(list) {
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: VH,
|
||||
position: Int,
|
||||
item: CookFoodGoodsEntity?
|
||||
item: GoodsItem?
|
||||
) {
|
||||
item ?: return
|
||||
holder.binding.tvGoodsInfo.run {
|
||||
text = item.goodsName
|
||||
setBackgroundResource(
|
||||
if (item.isClicked == true) R.drawable.shape_green_stroke
|
||||
if (item.isClicked) R.drawable.shape_green_stroke
|
||||
else R.drawable.shape_white_12_corners
|
||||
)
|
||||
setTextColor(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
if (item.isClicked == true) R.color.dish_green
|
||||
if (item.isClicked) R.color.dish_green
|
||||
else R.color.black666
|
||||
)
|
||||
)
|
||||
}
|
||||
if (item.foodScore == -1){
|
||||
if (item.foodScore == -1) {
|
||||
holder.binding.tvFoodScore.gone()
|
||||
} else {
|
||||
holder.binding.tvFoodScore.let {
|
||||
it.visible()
|
||||
it.text = "${(item.foodScore / 100.0).roundedDecimalPlace(2)}%"
|
||||
val foodScoreText = "${(item.foodScore / 100.0).roundedDecimalPlace(2)}%"
|
||||
it.text = foodScoreText
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,11 +56,10 @@ class GoodsInfoSearchAdapter(private var list: MutableList<CookFoodGoodsEntity>)
|
||||
viewType: Int
|
||||
): VH {
|
||||
val binding =
|
||||
ListItemSearchGoodsInfoBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
|
||||
ListItemFoodBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
inner class VH(var binding: ListItemSearchGoodsInfoBinding) : QuickViewHolder(binding.root)
|
||||
inner class VH(var binding: ListItemFoodBinding) : QuickViewHolder(binding.root)
|
||||
|
||||
}
|
||||
+7
-7
@@ -7,27 +7,27 @@ import androidx.core.content.ContextCompat
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.databinding.ListItemDishCookBinding
|
||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.databinding.ListItemFoodMaterialBinding
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import com.shuwei.dish.match.utils.ext.gone
|
||||
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
|
||||
import com.shuwei.dish.match.utils.ext.visible
|
||||
|
||||
class DishPartAdapter(list: MutableList<CookFoodGoodsEntity>) :
|
||||
BaseQuickAdapter<CookFoodGoodsEntity, DishPartAdapter.VH>(list) {
|
||||
class FoodMaterialAdapter(list: MutableList<GoodsItem>) :
|
||||
BaseQuickAdapter<GoodsItem, FoodMaterialAdapter.VH>(list) {
|
||||
|
||||
/** item 主体点击回调 */
|
||||
var onItemClick: ((position: Int) -> Unit)? = null
|
||||
|
||||
inner class VH(var binding: ListItemDishCookBinding) : QuickViewHolder(binding.root)
|
||||
inner class VH(var binding: ListItemFoodMaterialBinding) : QuickViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val binding = ListItemDishCookBinding.inflate(inflater, parent, false)
|
||||
val binding = ListItemFoodMaterialBinding.inflate(inflater, parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int, item: CookFoodGoodsEntity?) {
|
||||
override fun onBindViewHolder(holder: VH, position: Int, item: GoodsItem?) {
|
||||
holder.binding.run {
|
||||
val data = item ?: return
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.shuwei.dish.match.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.databinding.ListItemSearchFoodBinding
|
||||
import com.shuwei.dish.match.databinding.ListItemSearchGoodsInfoBinding
|
||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.utils.ext.gone
|
||||
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
|
||||
import com.shuwei.dish.match.utils.ext.visible
|
||||
|
||||
class FoodSearchAdapter(private var list: MutableList<CookFoodGoodsEntity>) :
|
||||
BaseQuickAdapter<CookFoodGoodsEntity, FoodSearchAdapter.VH>(list) {
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: VH,
|
||||
position: Int,
|
||||
item: CookFoodGoodsEntity?
|
||||
) {
|
||||
item ?: return
|
||||
holder.binding.tvFoodName.run {
|
||||
text = item.goodsName
|
||||
isChecked = item.isClicked
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
context: Context,
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): VH {
|
||||
val binding = ListItemSearchFoodBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
inner class VH(var binding: ListItemSearchFoodBinding) : QuickViewHolder(binding.root)
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.shuwei.dish.match.adapter
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.databinding.ListItemScaleRowBinding
|
||||
@@ -11,6 +12,8 @@ import com.shuwei.dish.match.utils.WeightUtil
|
||||
/**
|
||||
* 线性列表秤数据 Adapter
|
||||
* 用于 SlaveActivity 非22/18格子设备的秤列表展示
|
||||
* 通过 updateItem / updateName / removeByAddress 同步更新,避免 submitList 的
|
||||
* AsyncListDiffer 异步窗口期导致点击事件丢失
|
||||
*/
|
||||
class ScaleRowAdapter : BaseQuickAdapter<ScaleRowAdapter.ScaleItem, ScaleRowAdapter.VH>() {
|
||||
|
||||
@@ -23,6 +26,9 @@ class ScaleRowAdapter : BaseQuickAdapter<ScaleRowAdapter.ScaleItem, ScaleRowAdap
|
||||
*/
|
||||
data class ScaleItem(val address: Int, val weight: Double, val state: Int, val name: String = "")
|
||||
|
||||
/** item 点击回调 */
|
||||
var onItemClick: ((ScaleItem) -> Unit)? = null
|
||||
|
||||
inner class VH(val b: ListItemScaleRowBinding) : QuickViewHolder(b.root)
|
||||
|
||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||
@@ -40,5 +46,48 @@ class ScaleRowAdapter : BaseQuickAdapter<ScaleRowAdapter.ScaleItem, ScaleRowAdap
|
||||
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
||||
else -> "${item.state}"
|
||||
}
|
||||
// 点击时通过 bindingAdapterPosition 取最新数据,避免闭包捕获过期 item
|
||||
holder.b.root.setOnClickListener {
|
||||
val pos = holder.bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
items.getOrNull(pos)?.let { onItemClick?.invoke(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步更新单条秤数据:存在则定点刷新,不存在则按 address 升序插入
|
||||
* 使用 set/add 替代 submitList,消除 AsyncListDiffer 异步窗口期
|
||||
* @param item 最新秤数据
|
||||
*/
|
||||
fun updateItem(item: ScaleItem) {
|
||||
val idx = items.indexOfFirst { it.address == item.address }
|
||||
if (idx >= 0) {
|
||||
set(idx, item)
|
||||
} else {
|
||||
// 找到按 address 升序的插入位置
|
||||
val insertPos = items.indexOfFirst { it.address > item.address }
|
||||
.let { if (it < 0) items.size else it }
|
||||
add(insertPos, item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步更新指定地址的调料名称
|
||||
* @param address 秤地址
|
||||
* @param name 新名称
|
||||
*/
|
||||
fun updateName(address: Int, name: String) {
|
||||
val idx = items.indexOfFirst { it.address == address }
|
||||
if (idx >= 0) set(idx, items[idx].copy(name = name))
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步移除指定地址的条目
|
||||
* @param address 秤地址
|
||||
*/
|
||||
fun removeByAddress(address: Int) {
|
||||
val idx = items.indexOfFirst { it.address == address }
|
||||
if (idx >= 0) removeAt(idx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ import androidx.core.content.ContextCompat
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.databinding.ListItemSearchGoodsInfoBinding
|
||||
import com.shuwei.dish.match.databinding.ListItemFoodBinding
|
||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
|
||||
/**
|
||||
* 调料搜索弹窗适配器
|
||||
*/
|
||||
class SeasoningSearchAdapter(private var list: MutableList<SeasoningEntity>) :
|
||||
BaseQuickAdapter<SeasoningEntity, SeasoningSearchAdapter.VH>(list) {
|
||||
|
||||
@@ -40,10 +43,10 @@ class SeasoningSearchAdapter(private var list: MutableList<SeasoningEntity>) :
|
||||
viewType: Int
|
||||
): VH {
|
||||
val binding =
|
||||
ListItemSearchGoodsInfoBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
ListItemFoodBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
inner class VH(var binding: ListItemSearchGoodsInfoBinding) : QuickViewHolder(binding.root)
|
||||
inner class VH(var binding: ListItemFoodBinding) : QuickViewHolder(binding.root)
|
||||
|
||||
}
|
||||
@@ -1,136 +1,136 @@
|
||||
package com.shuwei.dish.match.adapter
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.icu.text.DecimalFormat
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.LineHeightSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.databinding.ListItemCellBinding
|
||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
import com.shuwei.dish.match.utils.ext.appendText
|
||||
import com.shuwei.dish.match.utils.ext.buildSpannableString
|
||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||
import com.shuwei.dish.match.utils.ext.dp
|
||||
import com.shuwei.dish.match.utils.ext.invisible
|
||||
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
|
||||
import com.shuwei.dish.match.utils.ext.visible
|
||||
|
||||
class TextCellAdapter(var list: MutableList<SeasoningEntity>) :
|
||||
RecyclerView.Adapter<TextCellAdapter.VH>() {
|
||||
|
||||
inner class VH(var binding: ListItemCellBinding) : QuickViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = ListItemCellBinding.inflate(inflater, parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
val item = list[position]
|
||||
holder.binding.run {
|
||||
if (position == 10 || position == 11 || position == 13 || position == 14) {
|
||||
root.invisible()
|
||||
} else {
|
||||
root.visible()
|
||||
}
|
||||
loadCell(tvCell, item)
|
||||
//root.setBackgroundResource(R.color.dish_green)
|
||||
(root.layoutParams as RecyclerView.LayoutParams).let {
|
||||
// it.bottomMargin = 15.dp
|
||||
it.width = 226.dp
|
||||
it.height = 120.dp
|
||||
// it.rightMargin = 0
|
||||
// it.leftMargin = if (position % 3 == 0) 0 else 15.dp
|
||||
|
||||
it.leftMargin = 7.dp
|
||||
it.rightMargin = 8.dp
|
||||
it.topMargin = 7.dp
|
||||
it.bottomMargin = 8.dp
|
||||
}
|
||||
if (item.pageType == 1) {
|
||||
root.clickWithDebounce { onCellClick?.invoke(position) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = list.size
|
||||
|
||||
var onCellClick: ((positon: Int) -> Unit)? = null
|
||||
|
||||
companion object {
|
||||
fun loadLayout(frameLayout: FrameLayout, item: SeasoningEntity) {
|
||||
val child: TextView = frameLayout.getChildAt(0) as TextView
|
||||
child.text = getTextSpan(item)
|
||||
}
|
||||
fun loadCell(tvCell: TextView, item: SeasoningEntity) {
|
||||
tvCell.text = getTextSpan(item)
|
||||
}
|
||||
// fun loadCell2(tvCell: TextView, item: SeasoningEntity) {
|
||||
// if (item.goodsName.isNullOrBlank()) {
|
||||
// tvCell.run {
|
||||
// text = buildSpannableString {
|
||||
//package com.shuwei.dish.match.adapter
|
||||
//
|
||||
//import android.graphics.Typeface
|
||||
//import android.icu.text.DecimalFormat
|
||||
//import android.text.SpannableStringBuilder
|
||||
//import android.text.style.AbsoluteSizeSpan
|
||||
//import android.text.style.ForegroundColorSpan
|
||||
//import android.text.style.LineHeightSpan
|
||||
//import android.text.style.StyleSpan
|
||||
//import android.view.LayoutInflater
|
||||
//import android.view.ViewGroup
|
||||
//import android.widget.FrameLayout
|
||||
//import android.widget.TextView
|
||||
//import androidx.core.content.ContextCompat
|
||||
//import androidx.core.graphics.toColorInt
|
||||
//import androidx.recyclerview.widget.RecyclerView
|
||||
//import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
//import com.shuwei.dish.match.R
|
||||
//import com.shuwei.dish.match.databinding.ListItemCellBinding
|
||||
//import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
//import com.shuwei.dish.match.utils.ext.appendText
|
||||
//import com.shuwei.dish.match.utils.ext.buildSpannableString
|
||||
//import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||
//import com.shuwei.dish.match.utils.ext.dp
|
||||
//import com.shuwei.dish.match.utils.ext.invisible
|
||||
//import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
|
||||
//import com.shuwei.dish.match.utils.ext.visible
|
||||
//
|
||||
//class TextCellAdapter(var list: MutableList<SeasoningEntity>) :
|
||||
// RecyclerView.Adapter<TextCellAdapter.VH>() {
|
||||
//
|
||||
// inner class VH(var binding: ListItemCellBinding) : QuickViewHolder(binding.root)
|
||||
//
|
||||
// override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
// val inflater = LayoutInflater.from(parent.context)
|
||||
// val binding = ListItemCellBinding.inflate(inflater, parent, false)
|
||||
// return VH(binding)
|
||||
// }
|
||||
//
|
||||
// override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
// val item = list[position]
|
||||
// holder.binding.run {
|
||||
// if (position == 10 || position == 11 || position == 13 || position == 14) {
|
||||
// root.invisible()
|
||||
// } else {
|
||||
// root.visible()
|
||||
// }
|
||||
// loadCell(tvCell, item)
|
||||
// //root.setBackgroundResource(R.color.dish_green)
|
||||
// (root.layoutParams as RecyclerView.LayoutParams).let {
|
||||
//// it.bottomMargin = 15.dp
|
||||
// it.width = 226.dp
|
||||
// it.height = 120.dp
|
||||
//// it.rightMargin = 0
|
||||
//// it.leftMargin = if (position % 3 == 0) 0 else 15.dp
|
||||
//
|
||||
// it.leftMargin = 7.dp
|
||||
// it.rightMargin = 8.dp
|
||||
// it.topMargin = 7.dp
|
||||
// it.bottomMargin = 8.dp
|
||||
// }
|
||||
// if (item.pageType == 1) {
|
||||
// root.clickWithDebounce { onCellClick?.invoke(position) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun getItemCount() = list.size
|
||||
//
|
||||
// var onCellClick: ((positon: Int) -> Unit)? = null
|
||||
//
|
||||
// companion object {
|
||||
// fun loadLayout(frameLayout: FrameLayout, item: SeasoningEntity) {
|
||||
// val child: TextView = frameLayout.getChildAt(0) as TextView
|
||||
// child.text = getTextSpan(item)
|
||||
// }
|
||||
// fun loadCell(tvCell: TextView, item: SeasoningEntity) {
|
||||
// tvCell.text = getTextSpan(item)
|
||||
// }
|
||||
//// fun loadCell2(tvCell: TextView, item: SeasoningEntity) {
|
||||
//// if (item.goodsName.isNullOrBlank()) {
|
||||
//// tvCell.run {
|
||||
//// text = buildSpannableString {
|
||||
//// appendText(
|
||||
//// "未配置",
|
||||
//// ForegroundColorSpan("#999999".toColorInt()),
|
||||
//// StyleSpan(Typeface.BOLD),
|
||||
//// AbsoluteSizeSpan(28, true),
|
||||
//// LineHeightSpan { text, start, end, spanstartv, v, fm ->
|
||||
//// fm.descent += 10.dp // 增加行间距
|
||||
//// }
|
||||
//// )}
|
||||
//// //setTextColor(ContextCompat.getColor(context, R.color.black999))
|
||||
//// }
|
||||
//// } else {
|
||||
////// if (item.pageType == 1) {
|
||||
////// tvCell.run {
|
||||
////// text = item.goodsName
|
||||
////// setTextColor(ContextCompat.getColor(context, R.color.black))
|
||||
////// }
|
||||
////// } else {
|
||||
//// tvCell.text = getTextSpan(item)
|
||||
////// }
|
||||
//// }
|
||||
//// }
|
||||
//
|
||||
// fun getTextSpan(item: SeasoningEntity): SpannableStringBuilder {
|
||||
// val endIndex = if (item.sort == 9) 10 else 6
|
||||
// var name = if (item.goodsName.isNullOrBlank()) "未配置" else item.goodsName!!
|
||||
// if (name.length > endIndex) {
|
||||
// name = "${name.substring(0, endIndex)}…"
|
||||
// }
|
||||
// return buildSpannableString {
|
||||
// appendText(
|
||||
// "未配置",
|
||||
// ForegroundColorSpan("#999999".toColorInt()),
|
||||
// name,
|
||||
// ForegroundColorSpan(if (item.goodsName.isNullOrBlank()) "#999999".toColorInt() else "#000000".toColorInt()),
|
||||
// StyleSpan(Typeface.BOLD),
|
||||
// AbsoluteSizeSpan(28, true),
|
||||
// LineHeightSpan { text, start, end, spanstartv, v, fm ->
|
||||
// fm.descent += 10.dp // 增加行间距
|
||||
// }
|
||||
// )}
|
||||
// //setTextColor(ContextCompat.getColor(context, R.color.black999))
|
||||
// }
|
||||
// } else {
|
||||
//// if (item.pageType == 1) {
|
||||
//// tvCell.run {
|
||||
//// text = item.goodsName
|
||||
//// setTextColor(ContextCompat.getColor(context, R.color.black))
|
||||
//// }
|
||||
//// } else {
|
||||
// tvCell.text = getTextSpan(item)
|
||||
//// }
|
||||
// )
|
||||
// append("\n")
|
||||
// val weight = item.useWeight ?: 0.toDouble()
|
||||
// val weightColor = if (weight == 0.toDouble()) "#999999" else "#00BC71"
|
||||
// appendText(
|
||||
//// "${DecimalFormat("#").format(weight)}g",
|
||||
// "${weight.roundedOneDecimalPlace()}g",
|
||||
// ForegroundColorSpan(weightColor.toColorInt()),
|
||||
// AbsoluteSizeSpan(30, true)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
fun getTextSpan(item: SeasoningEntity): SpannableStringBuilder {
|
||||
val endIndex = if (item.sort == 9) 10 else 6
|
||||
var name = if (item.goodsName.isNullOrBlank()) "未配置" else item.goodsName!!
|
||||
if (name.length > endIndex) {
|
||||
name = "${name.substring(0, endIndex)}…"
|
||||
}
|
||||
return buildSpannableString {
|
||||
appendText(
|
||||
name,
|
||||
ForegroundColorSpan(if (item.goodsName.isNullOrBlank()) "#999999".toColorInt() else "#000000".toColorInt()),
|
||||
StyleSpan(Typeface.BOLD),
|
||||
AbsoluteSizeSpan(28, true),
|
||||
LineHeightSpan { text, start, end, spanstartv, v, fm ->
|
||||
fm.descent += 10.dp // 增加行间距
|
||||
}
|
||||
)
|
||||
append("\n")
|
||||
val weight = item.useWeight ?: 0.toDouble()
|
||||
val weightColor = if (weight == 0.toDouble()) "#999999" else "#00BC71"
|
||||
appendText(
|
||||
// "${DecimalFormat("#").format(weight)}g",
|
||||
"${weight.roundedOneDecimalPlace()}g",
|
||||
ForegroundColorSpan(weightColor.toColorInt()),
|
||||
AbsoluteSizeSpan(30, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -25,7 +25,7 @@ import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
||||
CookFoodGoodsEntity::class,
|
||||
SeasoningSlotEntity::class,
|
||||
],
|
||||
version = 8,
|
||||
version = 9,
|
||||
exportSchema = true
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@@ -214,6 +214,16 @@ val MIGRATION_6_7 = MigrationImpl(6, 7) {db ->
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本8→9:为 dm_cook_food_goods 和 dm_seasoning 表新增 zjmCode、materId 字段
|
||||
*/
|
||||
val MIGRATION_8_9 = MigrationImpl(8, 9) { db ->
|
||||
db.execSQL("ALTER TABLE dm_cook_food_goods ADD COLUMN zjmCode TEXT DEFAULT ''")
|
||||
db.execSQL("ALTER TABLE dm_cook_food_goods ADD COLUMN materId TEXT DEFAULT ''")
|
||||
db.execSQL("ALTER TABLE dm_seasoning ADD COLUMN zjmCode TEXT DEFAULT ''")
|
||||
db.execSQL("ALTER TABLE dm_seasoning ADD COLUMN materId TEXT DEFAULT ''")
|
||||
}
|
||||
|
||||
class DatabaseProvider(private val context: Context) {
|
||||
// 单例模式确保全局唯一实例
|
||||
val instance: AppDatabase by lazy {
|
||||
@@ -233,6 +243,7 @@ class DatabaseProvider(private val context: Context) {
|
||||
// .addMigrations(MIGRATION_5_6)
|
||||
// .addMigrations(MIGRATION_6_7)
|
||||
.addMigrations(MIGRATION_7_8)
|
||||
.addMigrations(MIGRATION_8_9)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.shuwei.dish.match.dialog
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
@@ -8,13 +7,12 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.shuwei.dish.match.base.DeviceRole
|
||||
import com.shuwei.dish.match.base.GlobalData
|
||||
import com.shuwei.dish.match.databinding.DialogCommonBinding
|
||||
import com.shuwei.dish.match.utils.ext.dp
|
||||
import java.lang.ref.WeakReference
|
||||
import com.shuwei.dish.match.R
|
||||
|
||||
open class CommonDialog(
|
||||
context: Context
|
||||
@@ -77,7 +75,7 @@ open class CommonDialog(
|
||||
// 透明背景,让 shape_dialog_bg 的阴影层正常显示
|
||||
setBackgroundDrawable(ColorDrawable())
|
||||
// 应用缩放+淡入淡出动画
|
||||
setWindowAnimations(com.shuwei.dish.match.R.style.CommonDialogAnimation)
|
||||
setWindowAnimations(R.style.CommonDialogAnimation)
|
||||
}
|
||||
// 主/从设备边距微调:主设备内容区域更大,适当增加上下留白
|
||||
val verticalMargin = if (GlobalData.deviceRole == DeviceRole.MASTER) 48.dp else 36.dp
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
package com.shuwei.dish.match.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter
|
||||
import com.shuwei.dish.match.base.BaseActivity
|
||||
import com.shuwei.dish.match.databinding.DialogFoodRecognizeBinding
|
||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||
|
||||
/**
|
||||
* 菜品识别结果弹窗,继承 BottomSheetDialog 确保只初始化一次
|
||||
* @param activity 宿主 Activity
|
||||
* @param onDismiss 弹窗关闭时的额外回调(可选)
|
||||
* @param onItemSelected 用户点击某一菜品时的回调
|
||||
*/
|
||||
class FoodRecognizeDialog(
|
||||
private val activity: BaseActivity,
|
||||
private val onDismiss: (() -> Unit)? = null,
|
||||
private val onItemSelected: (item: CookFoodGoodsEntity) -> Unit
|
||||
) : BottomSheetDialog(activity, R.style.BottomSheet) {
|
||||
|
||||
private val binding = DialogFoodRecognizeBinding.inflate(LayoutInflater.from(activity))
|
||||
private val list = mutableListOf<CookFoodGoodsEntity>()
|
||||
private val adapter = GoodsInfoSearchAdapter(list).apply {
|
||||
setOnItemClickListener { _, _, position ->
|
||||
list[position].isClicked = true
|
||||
notifyItemChanged(position)
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
onItemSelected(list[position])
|
||||
dismiss()
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setContentView(binding.root)
|
||||
setCancelable(true)
|
||||
behavior.skipCollapsed = false
|
||||
setOnDismissListener { activity.hideStatusBar(); onDismiss?.invoke() }
|
||||
binding.recyclerView.run {
|
||||
layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
|
||||
adapter = this@FoodRecognizeDialog.adapter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载识别结果数据并显示弹窗
|
||||
* @param items 识别到的菜品列表
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun loadData(items: List<CookFoodGoodsEntity>) {
|
||||
list.clear()
|
||||
list.addAll(items)
|
||||
adapter.notifyDataSetChanged()
|
||||
if (!isShowing) show()
|
||||
}
|
||||
}
|
||||
//package com.shuwei.dish.match.dialog
|
||||
//
|
||||
//import android.annotation.SuppressLint
|
||||
//import android.os.Handler
|
||||
//import android.os.Looper
|
||||
//import android.view.LayoutInflater
|
||||
//import androidx.recyclerview.widget.GridLayoutManager
|
||||
//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.databinding.DialogFoodRecognizeBinding
|
||||
//import com.shuwei.dish.match.entity.GoodsItem
|
||||
//
|
||||
///**
|
||||
// * 菜品识别结果弹窗,继承 BottomSheetDialog 确保只初始化一次
|
||||
// * @param activity 宿主 Activity
|
||||
// * @param onDismiss 弹窗关闭时的额外回调(可选)
|
||||
// * @param onItemSelected 用户点击某一菜品时的回调
|
||||
// */
|
||||
//class FoodRecognizeDialog(
|
||||
// private val activity: BaseActivity,
|
||||
// private val onDismiss: (() -> Unit)? = null,
|
||||
// private val onItemSelected: (item: GoodsItem) -> Unit
|
||||
//) : BottomSheetDialog(activity, R.style.BottomSheet) {
|
||||
//
|
||||
// private val binding = DialogFoodRecognizeBinding.inflate(LayoutInflater.from(activity))
|
||||
// private val list = mutableListOf<GoodsItem>()
|
||||
// private val adapter = FoodAdapter(list).apply {
|
||||
// setOnItemClickListener { _, _, position ->
|
||||
// list[position].isClicked = true
|
||||
// notifyItemChanged(position)
|
||||
// Handler(Looper.getMainLooper()).postDelayed({
|
||||
// onItemSelected(list[position])
|
||||
// dismiss()
|
||||
// }, 300)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// init {
|
||||
// setContentView(binding.root)
|
||||
// setCancelable(true)
|
||||
// behavior.skipCollapsed = false
|
||||
// setOnDismissListener { activity.hideStatusBar(); onDismiss?.invoke() }
|
||||
// binding.recyclerView.run {
|
||||
// layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
|
||||
// adapter = this@FoodRecognizeDialog.adapter
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 加载识别结果数据并显示弹窗
|
||||
// * @param items 识别到的菜品列表
|
||||
// */
|
||||
// @SuppressLint("NotifyDataSetChanged")
|
||||
// fun loadData(items: List<GoodsItem>) {
|
||||
// list.clear()
|
||||
// list.addAll(items)
|
||||
// adapter.notifyDataSetChanged()
|
||||
// if (!isShowing) show()
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
package com.shuwei.dish.match.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.graphics.Rect
|
||||
import android.text.Editable
|
||||
import android.view.KeyboardShortcutGroup
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isEmpty
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter
|
||||
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.entity.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
|
||||
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
|
||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||
import com.shuwei.dish.match.utils.ext.toast
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -36,24 +41,38 @@ import kotlinx.coroutines.launch
|
||||
class FoodSearchDialog(
|
||||
private val activity: BaseActivity,
|
||||
private val defGoodsName: String? = null,
|
||||
private val onItemSelected: (item: CookFoodGoodsEntity) -> Unit
|
||||
private val onItemSelected: (item: GoodsItem) -> Unit
|
||||
) : BottomSheetDialog(activity, R.style.BottomSheet) {
|
||||
|
||||
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
|
||||
private val list = mutableListOf<CookFoodGoodsEntity>().apply {
|
||||
add(CookFoodGoodsEntity(goodsId = "200001", goodsName = "土豆丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200002", goodsName = "土豆片"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200003", goodsName = "土豆丁"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200004", goodsName = "胡萝卜丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200005", goodsName = "胡萝卜片"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200006", goodsName = "胡萝卜丁"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200007", goodsName = "黄瓜丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200008", goodsName = "黄瓜片"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200009", goodsName = "黄瓜丁"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200010", goodsName = "洋葱丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200011", goodsName = "洋葱丁"))
|
||||
companion object {
|
||||
const val PAGE_SIZE = 50
|
||||
}
|
||||
private val adapter = GoodsInfoSearchAdapter(list).apply {
|
||||
|
||||
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 = "洋葱丁"))
|
||||
add(GoodsItem(goodsId = "200012", goodsName = "A"))
|
||||
add(GoodsItem(goodsId = "200013", goodsName = "B"))
|
||||
add(GoodsItem(goodsId = "200014", goodsName = "C"))
|
||||
add(GoodsItem(goodsId = "200015", goodsName = "D"))
|
||||
add(GoodsItem(goodsId = "200016", goodsName = "E"))
|
||||
add(GoodsItem(goodsId = "200017", goodsName = "F"))
|
||||
add(GoodsItem(goodsId = "200018", goodsName = "G"))
|
||||
add(GoodsItem(goodsId = "200019", goodsName = "H"))
|
||||
}
|
||||
private val adapter = FoodAdapter(list).apply {
|
||||
isStateViewEnable = true
|
||||
setOnItemClickListener { _, _, position ->
|
||||
list[position].isClicked = true
|
||||
notifyItemChanged(position)
|
||||
@@ -66,7 +85,6 @@ class FoodSearchDialog(
|
||||
|
||||
private var goodsName: String? = null
|
||||
private var pageNo = 1
|
||||
private val pageSize = 50
|
||||
|
||||
init {
|
||||
setContentView(binding.root)
|
||||
@@ -91,18 +109,18 @@ class FoodSearchDialog(
|
||||
addOnActionSearchListener { searchGoods(this) }
|
||||
}
|
||||
|
||||
// RecyclerView 初始化及滑动冲突处理
|
||||
// RecyclerView 初始化
|
||||
binding.recyclerView.run {
|
||||
layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
|
||||
adapter = this@FoodSearchDialog.adapter
|
||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(rv, dx, dy)
|
||||
// 解决 RecyclerView 与 SmartRefreshLayout 滑动冲突
|
||||
val topRowVerticalPosition = if (rv.isEmpty()) 0 else rv.getChildAt(0).top
|
||||
binding.refreshLayout.setNestedScrollingEnabled(topRowVerticalPosition >= 0)
|
||||
}
|
||||
})
|
||||
// addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
// override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
|
||||
// super.onScrolled(rv, dx, dy)
|
||||
// // 解决 RecyclerView 与 SmartRefreshLayout 滑动冲突
|
||||
// val topRowVerticalPosition = if (rv.isEmpty()) 0 else rv.getChildAt(0).top
|
||||
// binding.refreshLayout.setNestedScrollingEnabled(topRowVerticalPosition >= 0)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
binding.refreshLayout.run {
|
||||
@@ -117,10 +135,50 @@ class FoodSearchDialog(
|
||||
|
||||
initObserver()
|
||||
|
||||
// 若有默认食材名称,自动填充并触发搜索
|
||||
if (!defGoodsName.isNullOrBlank()) {
|
||||
binding.etSheetInput.setText(defGoodsName)
|
||||
searchGoods(binding.etSheetInput)
|
||||
// // 若有默认食材名称,自动填充并触发搜索
|
||||
// //if (!defGoodsName.isNullOrBlank()) {
|
||||
// binding.etSheetInput.setText(defGoodsName)
|
||||
// pageNo = 1
|
||||
// getGoodsList()
|
||||
// KeyboardUtil.hideKeyboard(binding.root)
|
||||
// //}
|
||||
}
|
||||
|
||||
/** 弹窗显示时在 Window.Callback 层提前拦截触摸,解决 BottomSheet 拖拽与列表滑动的手势冲突 */
|
||||
override fun show() {
|
||||
super.show()
|
||||
// Window.Callback.dispatchTouchEvent 在整个 View 树的 onInterceptTouchEvent 之前执行,
|
||||
// 是最早能感知触摸的时机。在此处判断触摸点位置来控制 behavior.isDraggable,
|
||||
// 从而确保 BottomSheetBehavior 在 CoordinatorLayout.onInterceptTouchEvent 时
|
||||
// 已经是禁用状态,不会拦截列表区域的滑动手势。
|
||||
val originalCallback = window?.callback ?: return
|
||||
window?.callback = object : Window.Callback by originalCallback {
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
val rect = Rect()
|
||||
binding.refreshLayout.getGlobalVisibleRect(rect)
|
||||
// 触摸点落在列表区域内则禁止 BottomSheet 拖拽
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,32 +219,31 @@ 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将返回数据填充到列表
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun loadGoodsList(records: MutableList<CookFoodGoodsEntity>?) {
|
||||
private fun loadGoodsList(records: MutableList<GoodsItem>?) {
|
||||
finishRefresh()
|
||||
if (records.isNullOrEmpty()) {
|
||||
activity.toast("暂未搜索到食材信息")
|
||||
if (pageNo == 1) {
|
||||
loadEmptyView()
|
||||
}
|
||||
return
|
||||
}
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
|
||||
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++
|
||||
}
|
||||
@@ -195,4 +252,36 @@ class FoodSearchDialog(
|
||||
if (pageNo == 1) binding.refreshLayout.finishRefresh()
|
||||
else binding.refreshLayout.finishLoadMore()
|
||||
}
|
||||
|
||||
private var emptyViewBinding: LayoutEmptyViewBinding? = null
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun loadEmptyView() {
|
||||
try {
|
||||
binding.refreshLayout.setEnableRefresh(true)
|
||||
binding.refreshLayout.setEnableLoadMore(false)
|
||||
list.clear()
|
||||
adapter.notifyDataSetChanged()
|
||||
if (emptyViewBinding == null) {
|
||||
emptyViewBinding = LayoutEmptyViewBinding.inflate(
|
||||
LayoutInflater.from(activity),
|
||||
binding.recyclerView,
|
||||
false
|
||||
)
|
||||
}
|
||||
emptyViewBinding!!.tvContent.text = "暂无数据"
|
||||
emptyViewBinding!!.tvSubContent.text = "未查询到食材信息,请联系管理员添加"
|
||||
emptyViewBinding!!.root.layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(activity)
|
||||
binding.recyclerView.post {
|
||||
emptyViewBinding!!.root.minimumHeight = binding.recyclerView.height
|
||||
adapter.stateView = emptyViewBinding!!.root
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,259 +1,259 @@
|
||||
package com.shuwei.dish.match.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Typeface
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.LineHeightSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.core.view.isEmpty
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.DialogSeasoningSearchBinding
|
||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
import com.shuwei.dish.match.net.UiState
|
||||
import com.shuwei.dish.match.utils.KeyboardUtil
|
||||
import com.shuwei.dish.match.utils.WeightUtil
|
||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||
import com.shuwei.dish.match.utils.ext.appendText
|
||||
import com.shuwei.dish.match.utils.ext.buildSpannableString
|
||||
import com.shuwei.dish.match.utils.ext.dp
|
||||
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
|
||||
import com.shuwei.dish.match.utils.ext.toast
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 调料搜索弹窗,继承 BottomSheetDialog 确保只初始化一次
|
||||
* @param activity 宿主 Activity
|
||||
* @param weighIndex 秤的索引
|
||||
* @param weighAddress 秤的地址,用于监听重量和去皮
|
||||
* @param clickName 默认填充的调料名称(可选)
|
||||
* @param onItemSelected 用户点击某一调料时的回调,携带当前重量
|
||||
*/
|
||||
class SeasoningSearchDialog(
|
||||
private val activity: BaseActivity,
|
||||
private val weighIndex: Int,
|
||||
private val weighAddress: Int,
|
||||
private val clickName: String? = null,
|
||||
private val onItemSelected: (item: SeasoningEntity) -> Unit
|
||||
) : BottomSheetDialog(activity, R.style.BottomSheet) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "BottomDialog2"
|
||||
}
|
||||
|
||||
private val binding = DialogSeasoningSearchBinding.inflate(LayoutInflater.from(activity))
|
||||
private val list = mutableListOf<SeasoningEntity>()
|
||||
private val adapter = SeasoningSearchAdapter(list).apply {
|
||||
setOnItemClickListener { _, _, position ->
|
||||
list[position].isClicked = true
|
||||
notifyItemChanged(position)
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
onItemSelected(list[position].also { it.useWeight = currentWeight })
|
||||
dismiss()
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
private var goodsName: String? = null
|
||||
private var currentWeight = 0.0
|
||||
private var pageNo = 1
|
||||
private val pageSize = 50
|
||||
|
||||
init {
|
||||
setContentView(binding.root)
|
||||
setCancelable(true)
|
||||
behavior.skipCollapsed = false
|
||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
|
||||
window?.setGravity(Gravity.BOTTOM)
|
||||
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
window?.setWindowAnimations(R.style.DialogSoftInputAnimation)
|
||||
window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
|
||||
setOnDismissListener {
|
||||
// 弹窗关闭时移除重量监听,避免内存泄漏
|
||||
WeightUtil.removeWeightListener(TAG)
|
||||
activity.hideStatusBar()
|
||||
}
|
||||
|
||||
// 输入框:清空时重置列表
|
||||
binding.etSheetInput.run {
|
||||
hint = "输入调料名称"
|
||||
addTextChangedListener(
|
||||
onTextChanged = { text, _, _, _ -> goodsName = text.toString() },
|
||||
afterTextChanged = {
|
||||
if (it.isNullOrBlank()) {
|
||||
list.clear()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
)
|
||||
addOnActionSearchListener { searchGoods(this) }
|
||||
}
|
||||
|
||||
// RecyclerView 初始化及滑动冲突处理
|
||||
binding.recyclerView.let {
|
||||
it.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
|
||||
it.adapter = adapter
|
||||
it.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(rv, dx, dy)
|
||||
// 解决 RecyclerView 与 SmartRefreshLayout 滑动冲突
|
||||
val topRowVerticalPosition = if (rv.isEmpty()) 0 else rv.getChildAt(0).top
|
||||
binding.refreshLayout.setNestedScrollingEnabled(topRowVerticalPosition >= 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
binding.refreshLayout.run {
|
||||
setEnableRefresh(false)
|
||||
setEnableLoadMore(false)
|
||||
setOnRefreshListener { pageNo = 1; getGoodsList() }
|
||||
setOnLoadMoreListener { getGoodsList() }
|
||||
}
|
||||
|
||||
binding.ivSearch.setOnClickListener { searchGoods(it) }
|
||||
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
|
||||
|
||||
// 去皮按钮
|
||||
binding.tvClear.setOnClickListener {
|
||||
Log.d(TAG, "tareTwo: weighAddress=$weighAddress")
|
||||
WeightUtil.tareTwo(weighAddress)
|
||||
}
|
||||
|
||||
// 监听对应秤的重量变化
|
||||
WeightUtil.addWeightListener(
|
||||
weightKey = TAG,
|
||||
getWeight = { address, _, weight ->
|
||||
if (weighAddress == address) {
|
||||
currentWeight = weight.toDouble()
|
||||
binding.tvWeight.text = getTextSpan(weight)
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.tvWeight.text = getTextSpan(0.0)
|
||||
|
||||
initObserver()
|
||||
|
||||
// 若有默认调料名称,自动填充并触发搜索
|
||||
if (!clickName.isNullOrBlank()) {
|
||||
binding.etSheetInput.setText(clickName.trim())
|
||||
pageNo = 1
|
||||
getGoodsList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集 seasoningListState,统一处理 Loading / Success / Error 状态
|
||||
*/
|
||||
private fun initObserver() {
|
||||
activity.lifecycleScope.launch {
|
||||
activity.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
activity.netViewModel.seasoningListState.collect { state ->
|
||||
when (state) {
|
||||
is UiState.Success -> loadGoodsList(state.data)
|
||||
is UiState.Error -> {
|
||||
activity.toast(state.msg)
|
||||
finishRefresh()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发搜索:校验输入、隐藏键盘、发起请求
|
||||
*/
|
||||
private fun searchGoods(v: View) {
|
||||
if (goodsName.isNullOrBlank()) {
|
||||
activity.toast("请${binding.etSheetInput.hint}")
|
||||
return
|
||||
}
|
||||
pageNo = 1
|
||||
getGoodsList()
|
||||
KeyboardUtil.hideKeyboard(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求调料列表
|
||||
*/
|
||||
private fun getGoodsList() {
|
||||
val param = mutableMapOf<String, Any>(
|
||||
"goodsType" to "1",
|
||||
"placeId" to BaseApp.canteenId,
|
||||
"pageNum" to pageNo,
|
||||
"pageSize" to pageSize
|
||||
)
|
||||
if (!goodsName.isNullOrBlank()) {
|
||||
param["goodsName"] = goodsName!!
|
||||
}
|
||||
activity.netViewModel.querySeasoningList(param)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将返回数据填充到列表
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun loadGoodsList(records: MutableList<SeasoningEntity>?) {
|
||||
finishRefresh()
|
||||
if (records.isNullOrEmpty()) {
|
||||
activity.toast("暂未搜索到调料信息")
|
||||
return
|
||||
}
|
||||
if (pageNo == 1) list.clear()
|
||||
list.addAll(records)
|
||||
adapter.notifyDataSetChanged()
|
||||
val isLoadMoreEnable = records.size >= pageSize
|
||||
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
|
||||
if (isLoadMoreEnable) pageNo++
|
||||
}
|
||||
|
||||
private fun finishRefresh() {
|
||||
if (pageNo == 1) binding.refreshLayout.finishRefresh()
|
||||
else binding.refreshLayout.finishLoadMore()
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成重量显示的富文本:数值大字加粗,单位小字灰色
|
||||
*/
|
||||
fun getTextSpan(weight: Double): SpannableStringBuilder {
|
||||
val topWeight = if (weight >= 1000) "${(weight / 1000).roundedDecimalPlace(3)}" else "$weight"
|
||||
val bottomUnit = if (weight >= 1000) "千克" else "克"
|
||||
return buildSpannableString {
|
||||
appendText(
|
||||
topWeight,
|
||||
ForegroundColorSpan("#000000".toColorInt()),
|
||||
StyleSpan(Typeface.BOLD),
|
||||
AbsoluteSizeSpan(72, true),
|
||||
LineHeightSpan { _, _, _, _, _, fm -> fm.descent += 10.dp }
|
||||
)
|
||||
append("\n")
|
||||
appendText(
|
||||
bottomUnit,
|
||||
ForegroundColorSpan("#999999".toColorInt()),
|
||||
AbsoluteSizeSpan(30, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
//package com.shuwei.dish.match.dialog
|
||||
//
|
||||
//import android.annotation.SuppressLint
|
||||
//import android.graphics.Typeface
|
||||
//import android.os.Handler
|
||||
//import android.os.Looper
|
||||
//import android.text.SpannableStringBuilder
|
||||
//import android.text.style.AbsoluteSizeSpan
|
||||
//import android.text.style.ForegroundColorSpan
|
||||
//import android.text.style.LineHeightSpan
|
||||
//import android.text.style.StyleSpan
|
||||
//import android.util.Log
|
||||
//import android.view.Gravity
|
||||
//import android.view.LayoutInflater
|
||||
//import android.view.View
|
||||
//import android.view.ViewGroup
|
||||
//import android.view.WindowManager
|
||||
//import androidx.core.graphics.toColorInt
|
||||
//import androidx.core.view.isEmpty
|
||||
//import androidx.core.widget.addTextChangedListener
|
||||
//import androidx.lifecycle.Lifecycle
|
||||
//import androidx.lifecycle.lifecycleScope
|
||||
//import androidx.lifecycle.repeatOnLifecycle
|
||||
//import androidx.recyclerview.widget.GridLayoutManager
|
||||
//import androidx.recyclerview.widget.RecyclerView
|
||||
//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.DialogSeasoningSearchBinding
|
||||
//import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
//import com.shuwei.dish.match.net.UiState
|
||||
//import com.shuwei.dish.match.utils.KeyboardUtil
|
||||
//import com.shuwei.dish.match.utils.WeightUtil
|
||||
//import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||
//import com.shuwei.dish.match.utils.ext.appendText
|
||||
//import com.shuwei.dish.match.utils.ext.buildSpannableString
|
||||
//import com.shuwei.dish.match.utils.ext.dp
|
||||
//import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
|
||||
//import com.shuwei.dish.match.utils.ext.toast
|
||||
//import kotlinx.coroutines.launch
|
||||
//
|
||||
///**
|
||||
// * 调料搜索弹窗,继承 BottomSheetDialog 确保只初始化一次
|
||||
// * @param activity 宿主 Activity
|
||||
// * @param weighIndex 秤的索引
|
||||
// * @param weighAddress 秤的地址,用于监听重量和去皮
|
||||
// * @param clickName 默认填充的调料名称(可选)
|
||||
// * @param onItemSelected 用户点击某一调料时的回调,携带当前重量
|
||||
// */
|
||||
//class SeasoningSearchDialog(
|
||||
// private val activity: BaseActivity,
|
||||
// private val weighIndex: Int,
|
||||
// private val weighAddress: Int,
|
||||
// private val clickName: String? = null,
|
||||
// private val onItemSelected: (item: SeasoningEntity) -> Unit
|
||||
//) : BottomSheetDialog(activity, R.style.BottomSheet) {
|
||||
//
|
||||
// companion object {
|
||||
// const val TAG = "BottomDialog2"
|
||||
// }
|
||||
//
|
||||
// private val binding = DialogSeasoningSearchBinding.inflate(LayoutInflater.from(activity))
|
||||
// private val list = mutableListOf<SeasoningEntity>()
|
||||
// private val adapter = SeasoningSearchAdapter(list).apply {
|
||||
// setOnItemClickListener { _, _, position ->
|
||||
// list[position].isClicked = true
|
||||
// notifyItemChanged(position)
|
||||
// Handler(Looper.getMainLooper()).postDelayed({
|
||||
// onItemSelected(list[position].also { it.useWeight = currentWeight })
|
||||
// dismiss()
|
||||
// }, 300)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private var goodsName: String? = null
|
||||
// private var currentWeight = 0.0
|
||||
// private var pageNo = 1
|
||||
// private val pageSize = 50
|
||||
//
|
||||
// init {
|
||||
// setContentView(binding.root)
|
||||
// setCancelable(true)
|
||||
// behavior.skipCollapsed = false
|
||||
// window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
|
||||
// window?.setGravity(Gravity.BOTTOM)
|
||||
// window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
// window?.setWindowAnimations(R.style.DialogSoftInputAnimation)
|
||||
// window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
//
|
||||
// setOnDismissListener {
|
||||
// // 弹窗关闭时移除重量监听,避免内存泄漏
|
||||
// WeightUtil.removeWeightListener(TAG)
|
||||
// activity.hideStatusBar()
|
||||
// }
|
||||
//
|
||||
// // 输入框:清空时重置列表
|
||||
// binding.etSheetInput.run {
|
||||
// hint = "输入调料名称"
|
||||
// addTextChangedListener(
|
||||
// onTextChanged = { text, _, _, _ -> goodsName = text.toString() },
|
||||
// afterTextChanged = {
|
||||
// if (it.isNullOrBlank()) {
|
||||
// list.clear()
|
||||
// adapter.notifyDataSetChanged()
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// addOnActionSearchListener { searchGoods(this) }
|
||||
// }
|
||||
//
|
||||
// // RecyclerView 初始化及滑动冲突处理
|
||||
// binding.recyclerView.let {
|
||||
// it.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
|
||||
// it.adapter = adapter
|
||||
// it.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
// override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
|
||||
// super.onScrolled(rv, dx, dy)
|
||||
// // 解决 RecyclerView 与 SmartRefreshLayout 滑动冲突
|
||||
// val topRowVerticalPosition = if (rv.isEmpty()) 0 else rv.getChildAt(0).top
|
||||
// binding.refreshLayout.setNestedScrollingEnabled(topRowVerticalPosition >= 0)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// binding.refreshLayout.run {
|
||||
// setEnableRefresh(false)
|
||||
// setEnableLoadMore(false)
|
||||
// setOnRefreshListener { pageNo = 1; getGoodsList() }
|
||||
// setOnLoadMoreListener { getGoodsList() }
|
||||
// }
|
||||
//
|
||||
// binding.ivSearch.setOnClickListener { searchGoods(it) }
|
||||
// binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
|
||||
//
|
||||
// // 去皮按钮
|
||||
// binding.tvClear.setOnClickListener {
|
||||
// Log.d(TAG, "tareTwo: weighAddress=$weighAddress")
|
||||
// WeightUtil.tareTwo(weighAddress)
|
||||
// }
|
||||
//
|
||||
// // 监听对应秤的重量变化
|
||||
// WeightUtil.addWeightListener(
|
||||
// weightKey = TAG,
|
||||
// getWeight = { address, _, weight ->
|
||||
// if (weighAddress == address) {
|
||||
// currentWeight = weight.toDouble()
|
||||
// binding.tvWeight.text = getTextSpan(weight)
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// binding.tvWeight.text = getTextSpan(0.0)
|
||||
//
|
||||
// initObserver()
|
||||
//
|
||||
// // 若有默认调料名称,自动填充并触发搜索
|
||||
// if (!clickName.isNullOrBlank()) {
|
||||
// binding.etSheetInput.setText(clickName.trim())
|
||||
// pageNo = 1
|
||||
// getGoodsList()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 收集 seasoningListState,统一处理 Loading / Success / Error 状态
|
||||
// */
|
||||
// private fun initObserver() {
|
||||
// activity.lifecycleScope.launch {
|
||||
// activity.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
// activity.netViewModel.seasoningListState.collect { state ->
|
||||
// when (state) {
|
||||
// is UiState.Success -> loadGoodsList(state.data)
|
||||
// is UiState.Error -> {
|
||||
// activity.toast(state.msg)
|
||||
// finishRefresh()
|
||||
// }
|
||||
// else -> {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 触发搜索:校验输入、隐藏键盘、发起请求
|
||||
// */
|
||||
// private fun searchGoods(v: View) {
|
||||
// if (goodsName.isNullOrBlank()) {
|
||||
// activity.toast("请${binding.etSheetInput.hint}")
|
||||
// return
|
||||
// }
|
||||
// pageNo = 1
|
||||
// getGoodsList()
|
||||
// KeyboardUtil.hideKeyboard(v)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 请求调料列表
|
||||
// */
|
||||
// private fun getGoodsList() {
|
||||
// val param = mutableMapOf<String, Any>(
|
||||
// "goodsType" to "1",
|
||||
// "placeId" to BaseApp.canteenId,
|
||||
// "pageNum" to pageNo,
|
||||
// "pageSize" to pageSize
|
||||
// )
|
||||
// if (!goodsName.isNullOrBlank()) {
|
||||
// param["goodsName"] = goodsName!!
|
||||
// }
|
||||
// activity.netViewModel.querySeasoningList(param)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 将返回数据填充到列表
|
||||
// */
|
||||
// @SuppressLint("NotifyDataSetChanged")
|
||||
// private fun loadGoodsList(records: MutableList<SeasoningEntity>?) {
|
||||
// finishRefresh()
|
||||
// if (records.isNullOrEmpty()) {
|
||||
// activity.toast("暂未搜索到调料信息")
|
||||
// return
|
||||
// }
|
||||
// if (pageNo == 1) list.clear()
|
||||
// list.addAll(records)
|
||||
// adapter.notifyDataSetChanged()
|
||||
// val isLoadMoreEnable = records.size >= pageSize
|
||||
// binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
|
||||
// if (isLoadMoreEnable) pageNo++
|
||||
// }
|
||||
//
|
||||
// private fun finishRefresh() {
|
||||
// if (pageNo == 1) binding.refreshLayout.finishRefresh()
|
||||
// else binding.refreshLayout.finishLoadMore()
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 生成重量显示的富文本:数值大字加粗,单位小字灰色
|
||||
// */
|
||||
// fun getTextSpan(weight: Double): SpannableStringBuilder {
|
||||
// val topWeight = if (weight >= 1000) "${(weight / 1000).roundedDecimalPlace(3)}" else "$weight"
|
||||
// val bottomUnit = if (weight >= 1000) "千克" else "克"
|
||||
// return buildSpannableString {
|
||||
// appendText(
|
||||
// topWeight,
|
||||
// ForegroundColorSpan("#000000".toColorInt()),
|
||||
// StyleSpan(Typeface.BOLD),
|
||||
// AbsoluteSizeSpan(72, true),
|
||||
// LineHeightSpan { _, _, _, _, _, fm -> fm.descent += 10.dp }
|
||||
// )
|
||||
// append("\n")
|
||||
// appendText(
|
||||
// bottomUnit,
|
||||
// ForegroundColorSpan("#999999".toColorInt()),
|
||||
// AbsoluteSizeSpan(30, true)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -5,28 +5,37 @@ import android.graphics.Rect
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.Gravity
|
||||
import android.view.KeyboardShortcutGroup
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.databinding.DialogSeasoningSelectBinding
|
||||
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
import com.shuwei.dish.match.net.UiState
|
||||
import com.shuwei.dish.match.utils.KeyboardUtil
|
||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||
import com.shuwei.dish.match.utils.ext.toast
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 调料选择弹窗,不含重量显示,继承 BottomSheetDialog 确保只初始化一次
|
||||
* @param activity 宿主 Activity
|
||||
* @param clickIndex 点击的调料槽位索引
|
||||
* @param onItemSelected 用户点击某一调料时的回调
|
||||
*/
|
||||
class SeasoningSelectDialog(
|
||||
@@ -36,15 +45,17 @@ class SeasoningSelectDialog(
|
||||
|
||||
companion object {
|
||||
const val TAG = "SeasoningSelectDialog"
|
||||
const val PAGE_SIZE = 50
|
||||
}
|
||||
|
||||
private val binding = DialogSeasoningSelectBinding.inflate(LayoutInflater.from(activity))
|
||||
private val list = mutableListOf<SeasoningEntity>()
|
||||
private val adapter = SeasoningSearchAdapter(list).apply {
|
||||
setOnItemClickListener { _, _, position ->
|
||||
isStateViewEnable = true
|
||||
setOnItemClickListener { _, v, position ->
|
||||
list[position].isClicked = true
|
||||
notifyItemChanged(position)
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
v.postDelayed({
|
||||
onItemSelected(list[position])
|
||||
dismiss()
|
||||
}, 300)
|
||||
@@ -53,7 +64,7 @@ class SeasoningSelectDialog(
|
||||
|
||||
private var goodsName: String? = null
|
||||
private var pageNo = 1
|
||||
private val pageSize = 50
|
||||
private var collectJob: Job? = null
|
||||
|
||||
init {
|
||||
setContentView(binding.root)
|
||||
@@ -66,6 +77,7 @@ class SeasoningSelectDialog(
|
||||
window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
|
||||
setOnDismissListener {
|
||||
collectJob?.cancel()
|
||||
activity.hideStatusBar()
|
||||
}
|
||||
|
||||
@@ -104,6 +116,23 @@ class SeasoningSelectDialog(
|
||||
/** 弹窗显示时自动加载默认调料列表,并在 Window.Callback 层提前拦截触摸以解决滑动冲突 */
|
||||
override fun show() {
|
||||
super.show()
|
||||
// 启动 StateFlow 收集,弹窗关闭时由 setOnDismissListener 取消
|
||||
collectJob = activity.lifecycleScope.launch {
|
||||
activity.netViewModel.goodsListState.collect { state ->
|
||||
when (state) {
|
||||
is UiState.Loading -> {}
|
||||
is UiState.Success -> loadGoodsList(state.data)
|
||||
is UiState.Error -> {
|
||||
activity.toast(state.msg)
|
||||
finishRefresh()
|
||||
if (pageNo == 1) {
|
||||
loadEmptyView()
|
||||
}
|
||||
}
|
||||
is UiState.Idle -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageNo = 1
|
||||
getGoodsList()
|
||||
// Window.Callback.dispatchTouchEvent 在整个 View 树的 onInterceptTouchEvent 之前执行,
|
||||
@@ -126,6 +155,18 @@ class SeasoningSelectDialog(
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,52 +184,35 @@ class SeasoningSelectDialog(
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求调料列表(临时使用本地模拟数据,正式联调时替换为真实接口)
|
||||
* 请求调料列表
|
||||
*/
|
||||
private fun getGoodsList() {
|
||||
// TODO: 联调时注释掉下方模拟数据,取消注释真实接口调用
|
||||
val nameList = listOf(
|
||||
"盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
||||
"料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
||||
activity.netViewModel.queryGoodsList(
|
||||
goodsType = "1",
|
||||
pageNum = pageNo,
|
||||
pageSize = PAGE_SIZE,
|
||||
goodsName = goodsName.takeUnless { it.isNullOrBlank() }
|
||||
)
|
||||
val mockData = mutableListOf<SeasoningEntity>()
|
||||
nameList.forEachIndexed { index, name ->
|
||||
val padString = "${index + 1}".padStart(2, '0')
|
||||
mockData.add(SeasoningEntity(goodsId = "1000$padString", goodsName = name))
|
||||
}
|
||||
loadGoodsList(mockData)
|
||||
|
||||
// val param = mutableMapOf<String, Any>(
|
||||
// "goodsType" to "1",
|
||||
// "placeId" to BaseApp.canteenId,
|
||||
// "pageNum" to pageNo,
|
||||
// "pageSize" to pageSize
|
||||
// )
|
||||
// if (!goodsName.isNullOrBlank()) {
|
||||
// param["goodsName"] = goodsName!!
|
||||
// }
|
||||
// activity.querySeasoningList(param = param, onSuccess = {
|
||||
// loadGoodsList(it)
|
||||
// }, onFailure = { _, msg ->
|
||||
// activity.toast(msg)
|
||||
// finishRefresh()
|
||||
// })
|
||||
}
|
||||
|
||||
/**
|
||||
* 将返回数据填充到列表
|
||||
* 将返回数据转换并填充到列表
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun loadGoodsList(records: MutableList<SeasoningEntity>?) {
|
||||
private fun loadGoodsList(records: MutableList<GoodsItem>?) {
|
||||
finishRefresh()
|
||||
if (records.isNullOrEmpty()) {
|
||||
activity.toast("暂未搜索到调料信息")
|
||||
//activity.toast("暂未搜索到调料信息")
|
||||
if (pageNo == 1) {
|
||||
loadEmptyView()
|
||||
}
|
||||
return
|
||||
}
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.VERTICAL, false)
|
||||
if (pageNo == 1) list.clear()
|
||||
list.addAll(records)
|
||||
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++
|
||||
}
|
||||
@@ -197,4 +221,37 @@ class SeasoningSelectDialog(
|
||||
if (pageNo == 1) binding.refreshLayout.finishRefresh()
|
||||
else binding.refreshLayout.finishLoadMore()
|
||||
}
|
||||
|
||||
|
||||
private var emptyViewBinding: LayoutEmptyViewBinding? = null
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun loadEmptyView() {
|
||||
try {
|
||||
binding.refreshLayout.setEnableRefresh(true)
|
||||
binding.refreshLayout.setEnableLoadMore(false)
|
||||
list.clear()
|
||||
adapter.notifyDataSetChanged()
|
||||
if (emptyViewBinding == null) {
|
||||
emptyViewBinding = LayoutEmptyViewBinding.inflate(
|
||||
LayoutInflater.from(activity),
|
||||
binding.recyclerView,
|
||||
false
|
||||
)
|
||||
}
|
||||
emptyViewBinding!!.tvContent.text = "暂无数据"
|
||||
emptyViewBinding!!.tvSubContent.text = "未查询到调料信息,请联系管理员添加"
|
||||
emptyViewBinding!!.root.layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(activity)
|
||||
binding.recyclerView.post {
|
||||
emptyViewBinding!!.root.minimumHeight = binding.recyclerView.height
|
||||
adapter.stateView = emptyViewBinding!!.root
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.shuwei.dish.match.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.shuwei.dish.match.db.BaseEntity
|
||||
import com.shuwei.dish.match.utils.DateTimeUtil
|
||||
@@ -63,30 +62,28 @@ class CookFoodGoodsEntity(
|
||||
|
||||
var relateionType_dictText: String? = "",
|
||||
|
||||
var zjmCode: String? = "",
|
||||
|
||||
var materId: String? = "",
|
||||
|
||||
var isDel: Int = 0,
|
||||
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
|
||||
) : BaseEntity, Serializable {
|
||||
constructor() : this(goodsId = "") // 必需的空构造,
|
||||
constructor() : this(goodsId = "") // 必需的空构造
|
||||
|
||||
@Ignore
|
||||
var isSetFinished: Boolean = false
|
||||
|
||||
@Ignore
|
||||
var isItemClicked: Boolean = false
|
||||
|
||||
@Ignore
|
||||
var isSamplingPage: Boolean = false
|
||||
|
||||
@Ignore
|
||||
var isNewDishType: Boolean = false
|
||||
|
||||
@Ignore
|
||||
var isClicked: Boolean = false
|
||||
|
||||
//true-原始数据,false-非原始数据
|
||||
@Ignore
|
||||
var isOriginalData: Boolean = true
|
||||
|
||||
@Ignore
|
||||
var foodScore: Int = -1
|
||||
/**
|
||||
* 转换为 GoodsItem,用于 UI 层(DishPartAdapter、PrepareFoodActivity 等)
|
||||
*/
|
||||
fun toGoodsItem(): GoodsItem = GoodsItem(
|
||||
goodsId = goodsId,
|
||||
goodsName = goodsName,
|
||||
popularName = popularName,
|
||||
zjmCode = zjmCode,
|
||||
materId = materId,
|
||||
materialType = materialType,
|
||||
useWeight = useWeight,
|
||||
relateionType = relateionType,
|
||||
allEdible = allEdible,
|
||||
goodsOrRelationCode = goodsOrRelationCode
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import android.net.Uri
|
||||
import java.io.File
|
||||
|
||||
data class HomeModeBean(
|
||||
|
||||
var modeName: String = "",
|
||||
var modeDesc: String = "",
|
||||
var modeIconId: Int = 0,
|
||||
@@ -14,34 +13,6 @@ data class HomeModeBean(
|
||||
var onClick: (() -> Unit)? = null
|
||||
)
|
||||
|
||||
data class FoodRecordBean(
|
||||
var records: MutableList<FoodRecord>? = null,
|
||||
var total: Int,
|
||||
var size: Int,
|
||||
var current: Int,
|
||||
var page: Int
|
||||
)
|
||||
|
||||
data class GoodsRecordBean(
|
||||
var records: MutableList<CookFoodGoodsEntity>? = null,
|
||||
var total: Int,
|
||||
var size: Int,
|
||||
var current: Int,
|
||||
var page: Int
|
||||
)
|
||||
|
||||
data class SeasoningRecordBean(
|
||||
var records: MutableList<SeasoningEntity>? = null,
|
||||
var total: Int,
|
||||
var size: Int,
|
||||
var current: Int,
|
||||
var page: Int
|
||||
)
|
||||
|
||||
data class ResetReasoningRecord(
|
||||
val list: MutableList<SeasoningEntity>
|
||||
)
|
||||
|
||||
data class FoodCollectionBean(
|
||||
var imageUri: Uri? = null,
|
||||
var bitmap: Bitmap? = null,
|
||||
@@ -80,25 +51,4 @@ data class FoodCollectionBean(
|
||||
}
|
||||
}
|
||||
|
||||
//data class DishDetailEntity(
|
||||
// var foodId: String? = null,
|
||||
// var foodWeight: Double? = null,
|
||||
// var foodRealWeight: Double? = null,
|
||||
// var voList: List<DishPartEntity>? = null
|
||||
//)
|
||||
|
||||
//data class DishPartEntity(
|
||||
// var foodId: String? = null,
|
||||
// var foodName: String? = null,
|
||||
// var foodWeight: Int? = null,
|
||||
// var useWeight: Int? = null,
|
||||
// var useRealWeight: Int? = null,
|
||||
// var goodsId: Int? = null,
|
||||
// var goodsName: String? = null,
|
||||
// var materialType: Int? = null,
|
||||
// var isSetFinished: Boolean = false,
|
||||
// var isItemClicked: Boolean = false,
|
||||
// var isSamplingPage: Boolean = false
|
||||
//)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.shuwei.dish.match.entity
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 物品信息接口返回的数据类(DTO),不依赖 Room,同时承载 UI 状态字段
|
||||
* 对应接口:queryGoodsInfoList,食材(goodsType=0)和调料(goodsType=1)共用此结构
|
||||
*/
|
||||
data class GoodsItem(
|
||||
// 接口返回字段
|
||||
val goodsId: String = "",
|
||||
val goodsName: String? = null,
|
||||
val popularName: String? = null,
|
||||
val zjmCode: String? = null,
|
||||
val materId: String? = null,
|
||||
|
||||
// 从 CookFoodGoodsEntity 迁移的数据字段(DishPartAdapter 显示及转换用)
|
||||
var materialType: Int = 0,
|
||||
var useWeight: Double? = 0.0,
|
||||
var relateionType: Int = 0,
|
||||
var allEdible: Boolean = true,
|
||||
var goodsOrRelationCode: String? = "",
|
||||
|
||||
// 从 CookFoodGoodsEntity @Ignore 迁移的 UI 状态字段
|
||||
var isSetFinished: Boolean = false,
|
||||
var isItemClicked: Boolean = false,
|
||||
var isSamplingPage: Boolean = false,
|
||||
var isNewDishType: Boolean = false,
|
||||
var isClicked: Boolean = false,
|
||||
// true-原始数据,false-非原始数据(识图新增)
|
||||
var isOriginalData: Boolean = true,
|
||||
// 识图匹配分数,-1 表示不显示
|
||||
var foodScore: Int = -1
|
||||
) : Serializable {
|
||||
|
||||
/**
|
||||
* 转换为 CookFoodGoodsEntity,用于写入数据库或传给 SubmitFoodActivity
|
||||
*/
|
||||
fun toCookFoodGoodsEntity(): CookFoodGoodsEntity = CookFoodGoodsEntity(
|
||||
goodsId = goodsId,
|
||||
goodsName = goodsName,
|
||||
popularName = popularName,
|
||||
zjmCode = zjmCode,
|
||||
materId = materId,
|
||||
materialType = materialType,
|
||||
useWeight = useWeight,
|
||||
relateionType = relateionType,
|
||||
allEdible = allEdible,
|
||||
goodsOrRelationCode = goodsOrRelationCode
|
||||
)
|
||||
|
||||
/**
|
||||
* 转换为 SeasoningEntity,用于调料选择弹窗
|
||||
*/
|
||||
fun toSeasoningEntity(): SeasoningEntity = SeasoningEntity(
|
||||
goodsId = goodsId,
|
||||
goodsName = goodsName,
|
||||
popularName = popularName,
|
||||
zjmCode = zjmCode,
|
||||
materId = materId
|
||||
)
|
||||
}
|
||||
@@ -55,6 +55,10 @@ data class SeasoningEntity(
|
||||
|
||||
var relateionType_dictText: String? = "",
|
||||
|
||||
var zjmCode: String? = "",
|
||||
|
||||
var materId: String? = "",
|
||||
|
||||
var sort: Int = 9999,
|
||||
/**
|
||||
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
|
||||
|
||||
@@ -2,9 +2,8 @@ package com.shuwei.dish.match.net
|
||||
|
||||
import com.shuwei.dish.match.base.GlobalData
|
||||
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.entity.SeasoningEntity
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
@@ -51,63 +50,12 @@ interface ApiService {
|
||||
): ApiResponse<MutableList<FoodRecord>?>
|
||||
|
||||
/**
|
||||
* 物品信息
|
||||
* 物品信息(食材 goodsType=0,调料 goodsType=1 共用此接口)
|
||||
*/
|
||||
@POST
|
||||
suspend fun queryGoodsList(
|
||||
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryGoodsInfoList",
|
||||
@Body param: MutableMap<String, Any>
|
||||
): ApiResponse<MutableList<CookFoodGoodsEntity>?>
|
||||
|
||||
/**
|
||||
* 调料信息
|
||||
*/
|
||||
@POST
|
||||
suspend fun querySeasoningList(
|
||||
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryGoodsInfoList",
|
||||
@Body param: MutableMap<String, Any>
|
||||
): ApiResponse<MutableList<SeasoningEntity>?>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// /**
|
||||
// *获取配置信息
|
||||
// */
|
||||
// @GET("/equipment/stEquipment/queryByEquipmentCode")
|
||||
// suspend fun getDeviceInfo(
|
||||
// @Query("equipmentCode") equipmentCode: String,
|
||||
// @Query("appVersion") appVersion: String = App.appVersion
|
||||
// ): ApiResponse<DeviceConfigInfo>
|
||||
//
|
||||
// @GET
|
||||
// suspend fun getAccessToken(
|
||||
// @Url url: String = UrlConfig.GET_ACCESS_TOKEN,
|
||||
// @Query("qrcodeId") qrcodeId: String
|
||||
// ): ApiResponse<String?>
|
||||
//
|
||||
// @GET
|
||||
// suspend fun getShelfList(
|
||||
// @Url url: String = UrlConfig.GET_SHELF_LIST,
|
||||
// @Query("deviceId") deviceId: String
|
||||
// ): ApiResponse<ShelfResult>
|
||||
//
|
||||
// @GET
|
||||
// suspend fun getGoodsList(
|
||||
// @Url url: String = UrlConfig.GET_GOODS_LIST,
|
||||
// @Query("canteenId") canteenId: String = "0",
|
||||
// @Query("goodsName") goodsName: String? = null,
|
||||
// @Query("pageNo") pageNo: Int = 1,
|
||||
// @Query("pageSize") pageSize: Int = 50
|
||||
// ): ApiResponse<MutableList<GoodsModel>?>
|
||||
//
|
||||
// @POST
|
||||
// suspend fun saveShelfGoodsList(
|
||||
// @Url url: String = UrlConfig.SAVE_SHELF_GOODS_LIST,
|
||||
// @Body body: ShelfBody
|
||||
// ): ApiResponse<Any?>
|
||||
): ApiResponse<MutableList<GoodsItem>?>
|
||||
|
||||
}
|
||||
@@ -2,10 +2,10 @@ 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.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.entity.FoodRecord
|
||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -87,34 +87,39 @@ class NetViewModel(
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询物品信息列表的 UI 状态流,UI 层通过 collect 监听
|
||||
* 查询物品信息列表的 UI 状态流(食材/调料共用),UI 层通过 collect 监听
|
||||
*/
|
||||
private val _goodsListState = MutableStateFlow<UiState<MutableList<CookFoodGoodsEntity>?>>(UiState.Idle)
|
||||
val goodsListState: StateFlow<UiState<MutableList<CookFoodGoodsEntity>?>> = _goodsListState.asStateFlow()
|
||||
private val _goodsListState = MutableStateFlow<UiState<MutableList<GoodsItem>?>>(UiState.Idle)
|
||||
val goodsListState: StateFlow<UiState<MutableList<GoodsItem>?>> = _goodsListState.asStateFlow()
|
||||
|
||||
/**
|
||||
* 查询物品信息列表
|
||||
* 查询物品信息列表(食材 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询调料信息列表的 UI 状态流,UI 层通过 collect 监听
|
||||
*/
|
||||
private val _seasoningListState = MutableStateFlow<UiState<MutableList<SeasoningEntity>?>>(UiState.Idle)
|
||||
val seasoningListState: StateFlow<UiState<MutableList<SeasoningEntity>?>> = _seasoningListState.asStateFlow()
|
||||
|
||||
/**
|
||||
* 查询调料信息列表
|
||||
*/
|
||||
fun querySeasoningList(param: MutableMap<String, Any>) {
|
||||
viewModelScope.launch {
|
||||
_seasoningListState.value = UiState.Loading
|
||||
_seasoningListState.value = repository.querySeasoningList(param)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.shuwei.dish.match.net
|
||||
|
||||
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.entity.SeasoningEntity
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
|
||||
/**
|
||||
* 网络数据仓库,封装所有 ApiService 调用
|
||||
@@ -76,11 +75,11 @@ class RemoteRepository {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询物品信息列表
|
||||
* 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用)
|
||||
* @param param 查询参数
|
||||
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
|
||||
*/
|
||||
suspend fun queryGoodsList(param: MutableMap<String, Any>): UiState<MutableList<CookFoodGoodsEntity>?> {
|
||||
suspend fun queryGoodsList(param: MutableMap<String, Any>): UiState<MutableList<GoodsItem>?> {
|
||||
return try {
|
||||
val resp = apiService.queryGoodsList(param = param)
|
||||
if (resp.isSuccess()) UiState.Success(resp.data)
|
||||
@@ -91,21 +90,4 @@ class RemoteRepository {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询调料信息列表
|
||||
* @param param 查询参数
|
||||
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
|
||||
*/
|
||||
suspend fun querySeasoningList(param: MutableMap<String, Any>): UiState<MutableList<SeasoningEntity>?> {
|
||||
return try {
|
||||
val resp = apiService.querySeasoningList(param = param)
|
||||
if (resp.isSuccess()) UiState.Success(resp.data)
|
||||
else UiState.Error(resp.code, resp.msg ?: "")
|
||||
} catch (e: Exception) {
|
||||
val ex = getApiException(e)
|
||||
UiState.Error("-1", ex.errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -128,9 +128,8 @@ object ScaleServiceManager {
|
||||
if (role == DeviceRole.MASTER) {
|
||||
startMasterServices(context, deviceId)
|
||||
} else {
|
||||
// 子设备:启动 UDP 广播,作为 mDNS 的兜底发现机制
|
||||
val localIp = getLocalIp(context)
|
||||
udpSender = UdpBroadcastSender(deviceId, localIp).also { it.start() }
|
||||
// 子设备:启动 UDP 广播,作为 mDNS 的兜底发现机制;每次广播时动态获取 IP,避免启动时网络未就绪导致 IP 为空
|
||||
udpSender = UdpBroadcastSender(deviceId = deviceId, ipProvider = { getLocalIp(context) }).also { it.start() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +155,11 @@ object ScaleServiceManager {
|
||||
val client = ScaleWebSocketClient().also {
|
||||
it.onScaleData = { data -> aggregator?.onRemoteScaleData(data) }
|
||||
it.onScaleEvent = { event -> _onScaleEvent?.invoke(event) }
|
||||
it.onDeviceDisconnected = { remoteId -> aggregator?.removeDevice(remoteId) }
|
||||
it.onDeviceDisconnected = { remoteId ->
|
||||
aggregator?.removeDevice(remoteId)
|
||||
// 清除 UDP 缓存,确保子设备下次广播时能重新触发 onDeviceFound,立即重连而非等待指数退避
|
||||
udpReceiver?.removeDevice(remoteId)
|
||||
}
|
||||
it.onDeviceConnected = { remoteId ->
|
||||
// 子设备连接成功时,从 Room 读取全量配置并单独推送给该设备
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
@@ -117,7 +117,11 @@ class ScaleWebSocketServer(private val deviceId: String, private val context: Co
|
||||
private inner class InternalServer(port: Int) :
|
||||
WebSocketServer(InetSocketAddress(port)) {
|
||||
|
||||
/** 当前活跃连接计数,用于替代 connections.isNullOrEmpty() 避免竞态 */
|
||||
private var connectionCount = 0
|
||||
|
||||
override fun onOpen(conn: WebSocket, handshake: ClientHandshake) {
|
||||
connectionCount++
|
||||
Log.d(TAG, "主设备已连接: ${conn.remoteSocketAddress}")
|
||||
onConnectionChanged?.invoke(true)
|
||||
// 新客户端连接后,立即推送所有秤的最新数据
|
||||
@@ -132,8 +136,9 @@ class ScaleWebSocketServer(private val deviceId: String, private val context: Co
|
||||
|
||||
override fun onClose(conn: WebSocket, code: Int, reason: String, remote: Boolean) {
|
||||
Log.d(TAG, "主设备已断开: ${conn.remoteSocketAddress}, reason=$reason")
|
||||
// 若已无任何连接,通知子设备 UI 更新为未连接状态
|
||||
if (server?.connections.isNullOrEmpty()) {
|
||||
// 用计数器判断是否还有活跃连接,避免依赖 connections 集合的移除时机导致竞态
|
||||
if (--connectionCount <= 0) {
|
||||
connectionCount = 0
|
||||
onConnectionChanged?.invoke(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ import java.util.concurrent.TimeUnit
|
||||
* UDP 广播发送器(子设备运行)
|
||||
* 每隔固定间隔向局域网广播本机的 deviceId、IP、WebSocket 端口
|
||||
* 作为 mDNS 的兜底发现机制,确保主设备能可靠发现子设备
|
||||
*
|
||||
* @param ipProvider 动态获取本机 IP 的函数,每次广播时调用;返回空字符串时跳过本次广播
|
||||
*/
|
||||
class UdpBroadcastSender(
|
||||
private val deviceId: String,
|
||||
private val localIp: String,
|
||||
private val ipProvider: () -> String,
|
||||
private val wsPort: Int = MdnsRegisterManager.WS_PORT
|
||||
) {
|
||||
|
||||
@@ -43,12 +45,18 @@ class UdpBroadcastSender(
|
||||
task = scheduler.scheduleWithFixedDelay({
|
||||
sendBroadcast()
|
||||
}, 0, INTERVAL_SEC, TimeUnit.SECONDS)
|
||||
Log.d(TAG, "UDP 广播已启动, deviceId=$deviceId, ip=$localIp, port=$wsPort")
|
||||
Log.d(TAG, "UDP 广播已启动, deviceId=$deviceId, port=$wsPort")
|
||||
}
|
||||
|
||||
private fun sendBroadcast() {
|
||||
try {
|
||||
val payload = gson.toJson(Payload(deviceId, localIp, wsPort))
|
||||
val ip = ipProvider()
|
||||
// 网络未就绪时跳过本次广播,等待下一个周期重试
|
||||
if (ip.isEmpty()) {
|
||||
Log.d(TAG, "IP 未就绪,跳过本次广播")
|
||||
return
|
||||
}
|
||||
val payload = gson.toJson(Payload(deviceId, ip, wsPort))
|
||||
val data = payload.toByteArray(Charsets.UTF_8)
|
||||
DatagramSocket().use { socket ->
|
||||
socket.broadcast = true
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.base.BaseActivity
|
||||
import com.shuwei.dish.match.databinding.ActivityCookingModeBinding
|
||||
import com.shuwei.dish.match.dialog.CommonDialog
|
||||
import com.shuwei.dish.match.ui.fragment.DishListFragment
|
||||
import com.shuwei.dish.match.ui.fragment.FoodListFragment
|
||||
import com.shuwei.dish.match.utils.KeyboardUtil
|
||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||
import com.shuwei.dish.match.utils.ext.gone
|
||||
@@ -32,10 +32,10 @@ class CookingModeActivity : BaseActivity() {
|
||||
|
||||
private var dinnerType: String = "1"
|
||||
|
||||
private val fragmentList = mutableListOf<DishListFragment>().apply {
|
||||
add(DishListFragment.instance("1"))
|
||||
add(DishListFragment.instance("2"))
|
||||
add(DishListFragment.instance("3"))
|
||||
private val fragmentList = mutableListOf<FoodListFragment>().apply {
|
||||
add(FoodListFragment.instance("1"))
|
||||
add(FoodListFragment.instance("2"))
|
||||
add(FoodListFragment.instance("3"))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -7,11 +7,11 @@ import androidx.activity.addCallback
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter
|
||||
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.FoodSearchDialog
|
||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import com.shuwei.dish.match.utils.ActivityManager
|
||||
import com.shuwei.dish.match.utils.WeightUtil
|
||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||
@@ -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<CookFoodGoodsEntity>? = 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<CookFoodGoodsEntity> = arrayListOf(),
|
||||
finishCallback: (Boolean, CookFoodGoodsEntity?) -> Unit = { _, _ -> }
|
||||
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,
|
||||
CookFoodGoodsEntity::class.java)
|
||||
GoodsItem::class.java
|
||||
)
|
||||
}
|
||||
finishCallback(isManualCancel, entity)
|
||||
}
|
||||
@@ -82,7 +87,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityFoodRecognizeBinding
|
||||
|
||||
private val list = mutableListOf<CookFoodGoodsEntity>()
|
||||
private val list = mutableListOf<GoodsItem>()
|
||||
|
||||
/** 当前选中的列表位置,-1 表示无选中 */
|
||||
private var selectedPosition = -1
|
||||
@@ -102,13 +107,19 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
private val adapter = GoodsInfoSearchAdapter(list).apply {
|
||||
private val adapter = FoodAdapter(list).apply {
|
||||
setOnItemClickListener { _, _, position ->
|
||||
list.forEachIndexed { i, item -> item.isClicked = (i == position) }
|
||||
// 点击选择时记录当前秤重量
|
||||
@@ -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)
|
||||
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
|
||||
|
||||
@@ -281,7 +341,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
* 用新食材替换当前选中项(或追加到列表首位)
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun updateSelectedFood(item: CookFoodGoodsEntity) {
|
||||
private fun updateSelectedFood(item: GoodsItem) {
|
||||
val existIndex = list.indexOfFirst { it.goodsId == item.goodsId }
|
||||
if (existIndex >= 0) {
|
||||
list.forEachIndexed { i, e -> e.isClicked = (i == existIndex) }
|
||||
@@ -292,10 +352,14 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
selectedPosition = 0
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
if (list.isNotEmpty()) {
|
||||
binding.rvRecognizeList.visible()
|
||||
binding.tvNoData.gone()
|
||||
}
|
||||
updateRvParentLayoutHeight()
|
||||
binding.tvSelectedFood.text = item.goodsName ?: "-"
|
||||
updateListVisibility()
|
||||
binding.recyclerView.post { updateScrollIndicator() }
|
||||
binding.rvRecognizeList.post { updateScrollIndicator() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.adapter.FoodRecordAdapter
|
||||
import com.shuwei.dish.match.adapter.Food2Adapter
|
||||
import com.shuwei.dish.match.base.BaseActivity
|
||||
import com.shuwei.dish.match.base.BaseApp
|
||||
import com.shuwei.dish.match.databinding.ActivityFoodSearchBinding
|
||||
@@ -44,7 +44,7 @@ class FoodSearchActivity : BaseActivity() {
|
||||
private var pageType = 0
|
||||
private var dinnerType = "1"
|
||||
private val recordAdapter by lazy {
|
||||
FoodRecordAdapter(list).apply {
|
||||
Food2Adapter(list).apply {
|
||||
isStateViewEnable = true
|
||||
setOnItemClickListener { adapter, view, position ->
|
||||
if (pageType == 1) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.adapter.DishPartAdapter
|
||||
import com.shuwei.dish.match.adapter.FoodMaterialAdapter
|
||||
import com.shuwei.dish.match.base.BaseActivity
|
||||
import com.shuwei.dish.match.databinding.ActivityPrepareFoodBinding
|
||||
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
||||
@@ -21,6 +21,7 @@ import com.shuwei.dish.match.dialog.CommonDialog
|
||||
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.entity.GoodsItem
|
||||
import com.shuwei.dish.match.net.UiState
|
||||
import com.shuwei.dish.match.objbox.FoodModule
|
||||
import com.shuwei.dish.match.utils.AddressUtil
|
||||
@@ -72,8 +73,6 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
private var pageFrom: String? = null
|
||||
|
||||
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
@@ -169,8 +168,10 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
foodRecord.foodName = showFoodName
|
||||
foodRecord.cookMode = 1
|
||||
Log.d(TAG, "goToSubmit: goodsList:${list.toJsonString()}")
|
||||
// 采集模式传给 SubmitFoodActivity 时转换为 CookFoodGoodsEntity
|
||||
val submitList = list.map { it.toCookFoodGoodsEntity() }.toMutableList()
|
||||
startActivity<SubmitFoodActivity> {
|
||||
putExtra(SubmitFoodActivity.GOODS_LIST, list as Serializable)
|
||||
putExtra(SubmitFoodActivity.GOODS_LIST, submitList as Serializable)
|
||||
putExtra(SubmitFoodActivity.FOOD_ITEM, foodRecord as Serializable)
|
||||
}
|
||||
} else {
|
||||
@@ -227,19 +228,19 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
* - list 中不含该 goodsId:直接新增,isNewDishType = true
|
||||
* - list 中已含该 goodsId:累加 useWeight
|
||||
*/
|
||||
private val foodSelectCallback: (CookFoodGoodsEntity) -> Unit = { entity ->
|
||||
private val foodSelectCallback: (GoodsItem) -> Unit = { entity ->
|
||||
val existing = list.firstOrNull { it.goodsId == entity.goodsId }
|
||||
if (existing != null) {
|
||||
existing.useWeight = (existing.useWeight ?: 0.0) + (entity.useWeight ?: 0.0)
|
||||
existing.isSetFinished = true
|
||||
dishPartAdapter.notifyItemChanged(list.indexOf(existing))
|
||||
materialAdapter.notifyItemChanged(list.indexOf(existing))
|
||||
} else {
|
||||
dishPartAdapter.add(entity.also {
|
||||
materialAdapter.add(entity.also {
|
||||
it.isNewDishType = true
|
||||
it.isSetFinished = true
|
||||
})
|
||||
|
||||
binding.rvDishPartList.smoothScrollToPosition(list.size - 1)
|
||||
binding.rvMaterialList.smoothScrollToPosition(list.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,16 +261,10 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
if (goodsList == null) {
|
||||
goodsList = mutableListOf()
|
||||
}
|
||||
// 将 GoodsItem 转换为 CookFoodGoodsEntity 并补充菜品相关字段
|
||||
list.filter { it.isSetFinished }.forEach {
|
||||
goodsList?.add(CookFoodGoodsEntity().apply {
|
||||
goodsList?.add(it.toCookFoodGoodsEntity().apply {
|
||||
foodId = food!!.foodId
|
||||
goodsId = it.goodsId
|
||||
relateionType = it.relateionType
|
||||
allEdible = it.allEdible
|
||||
goodsName = it.goodsName
|
||||
materialType = it.materialType
|
||||
useWeight = it.useWeight ?: 0.toDouble()
|
||||
goodsOrRelationCode = it.goodsOrRelationCode
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -311,23 +306,6 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun getDishDetail() {
|
||||
// //649
|
||||
// val url = "${UrlConfig.DISH_DETAIL}?foodId=${food?.foodId}"
|
||||
// HttpUtil.get(
|
||||
// url = url,
|
||||
// doSuccess = {
|
||||
// val json = it.toJsonString()
|
||||
// Log.d(TAG, "getDishDetail: json:$json")
|
||||
// val detail: CookFoodEntity? = json.toObject<CookFoodEntity>()
|
||||
// if (detail == null) {
|
||||
// toast("查询菜品信息为空")
|
||||
// return@get
|
||||
// }
|
||||
// loadDishDetail(detail)
|
||||
// }) { code, msg ->
|
||||
// toast(msg)
|
||||
// }
|
||||
|
||||
netViewModel.getFoodDetail(food?.foodId ?: "")
|
||||
}
|
||||
|
||||
@@ -338,22 +316,20 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
if (voList.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
//筛选出主材和辅材
|
||||
// 筛选出主材和辅材,转换为 GoodsItem(useWeight 置零)
|
||||
val tempData = voList.filter { it.materialType == 1 || it.materialType == 2 }
|
||||
.apply {
|
||||
forEach { it.useWeight = 0.toDouble() }
|
||||
}
|
||||
.map { it.toGoodsItem().also { item -> item.useWeight = 0.0 } }
|
||||
list.clear()
|
||||
firstReqSize = list.size
|
||||
list.addAll(tempData)
|
||||
dishPartAdapter.notifyDataSetChanged()
|
||||
materialAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private var firstReqSize = 0
|
||||
private var isDetailLoaded = false
|
||||
private val list = mutableListOf<CookFoodGoodsEntity>()
|
||||
private val dishPartAdapter by lazy {
|
||||
DishPartAdapter(list = list).apply {
|
||||
private val list = mutableListOf<GoodsItem>()
|
||||
private val materialAdapter by lazy {
|
||||
FoodMaterialAdapter(list = list).apply {
|
||||
val adapter = this
|
||||
onItemClick = { positon ->
|
||||
list.forEachIndexed { index, entity ->
|
||||
@@ -381,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 之前调用)
|
||||
@@ -399,15 +375,15 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
.setContent("确定删除食材「${item.goodsName}」吗?")
|
||||
.setNegativeButton("取消")
|
||||
.setPositiveButton("删除") {
|
||||
dishPartAdapter.removeAt(position)
|
||||
materialAdapter.removeAt(position)
|
||||
toast("已删除")
|
||||
}.show()
|
||||
}
|
||||
// item 点击事件
|
||||
setOnItemClickListener { _, position ->
|
||||
dishPartAdapter.onItemClick?.invoke(position)
|
||||
materialAdapter.onItemClick?.invoke(position)
|
||||
}
|
||||
adapter = dishPartAdapter
|
||||
adapter = materialAdapter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,48 +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<CookFoodGoodsEntity>()
|
||||
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(
|
||||
CookFoodGoodsEntity(
|
||||
goodsId = foodId,
|
||||
goodsName = foodName,
|
||||
foodId = index.toString()
|
||||
).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<CookFoodGoodsEntity>) {
|
||||
private fun loadRecognizeResultPage(nameScoreList: List<FoodModule.IdNameScore>) {
|
||||
if (currentWeight < WEIGHT_RECOGNIZE_VALUE) {
|
||||
Log.d(TAG, "takePhoto queryFood, 已取消")
|
||||
toast("已取消")
|
||||
@@ -582,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
|
||||
|
||||
@@ -125,10 +125,7 @@ class SlaveActivity : BaseActivity() {
|
||||
else -> {
|
||||
binding.rvScaleList.updateLayoutParams<LinearLayout.LayoutParams> { topMargin = 20.dp }
|
||||
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
|
||||
linearAdapter.setOnItemClickListener { _, _, position ->
|
||||
val item = linearAdapter.items.getOrNull(position) ?: return@setOnItemClickListener
|
||||
showTareDialog(item.address)
|
||||
}
|
||||
linearAdapter.onItemClick = { item -> showTareDialog(item.address) }
|
||||
binding.rvScaleList.adapter = linearAdapter
|
||||
}
|
||||
}
|
||||
@@ -203,12 +200,8 @@ class SlaveActivity : BaseActivity() {
|
||||
if (pos >= 0) scale18Adapter!!.updateItemName(pos, "")
|
||||
}
|
||||
else -> {
|
||||
val list = linearAdapter.items.toMutableList()
|
||||
val idx = list.indexOfFirst { it.address == addr }
|
||||
if (idx >= 0) {
|
||||
list[idx] = list[idx].copy(name = "")
|
||||
linearAdapter.submitList(list)
|
||||
}
|
||||
val idx = linearAdapter.items.indexOfFirst { it.address == addr }
|
||||
if (idx >= 0) linearAdapter.removeByAddress(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,14 +218,7 @@ class SlaveActivity : BaseActivity() {
|
||||
val pos = ScaleDeviceConfig.SCALE_ORDER_18.indexOf(slot.address)
|
||||
if (pos >= 0) scale18Adapter!!.updateItemName(pos, slot.goodsName)
|
||||
}
|
||||
else -> {
|
||||
val list = linearAdapter.items.toMutableList()
|
||||
val idx = list.indexOfFirst { it.address == slot.address }
|
||||
if (idx >= 0) {
|
||||
list[idx] = list[idx].copy(name = slot.goodsName)
|
||||
linearAdapter.submitList(list)
|
||||
}
|
||||
}
|
||||
else -> linearAdapter.updateName(slot.address, slot.goodsName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,15 +258,7 @@ class SlaveActivity : BaseActivity() {
|
||||
}
|
||||
else -> {
|
||||
val item = ScaleRowAdapter.ScaleItem(address, weight, state, slotNameMap[address] ?: "")
|
||||
val list = linearAdapter.items.toMutableList()
|
||||
val idx = list.indexOfFirst { it.address == address }
|
||||
if (idx >= 0) {
|
||||
list[idx] = item
|
||||
} else {
|
||||
list.add(item)
|
||||
list.sortBy { it.address }
|
||||
}
|
||||
linearAdapter.submitList(list)
|
||||
linearAdapter.updateItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -14,7 +14,7 @@ import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.adapter.FoodListAdapter
|
||||
import com.shuwei.dish.match.base.BaseApp
|
||||
import com.shuwei.dish.match.base.BaseFragment
|
||||
import com.shuwei.dish.match.databinding.FragmentDishListBinding
|
||||
import com.shuwei.dish.match.databinding.FragmentFoodListBinding
|
||||
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
|
||||
import com.shuwei.dish.match.dialog.CommonDialog
|
||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||
@@ -31,14 +31,14 @@ import java.io.Serializable
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
||||
class FoodListFragment : BaseFragment<FragmentFoodListBinding>() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DishListFragment"
|
||||
|
||||
const val DINNER_TYPE = "dinnerType"
|
||||
public fun instance(dinnerType: String): DishListFragment {
|
||||
return DishListFragment().apply {
|
||||
public fun instance(dinnerType: String): FoodListFragment {
|
||||
return FoodListFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(DINNER_TYPE, dinnerType)
|
||||
}
|
||||
@@ -85,8 +85,8 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
||||
override fun inflateBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
): FragmentDishListBinding {
|
||||
return FragmentDishListBinding.inflate(inflater, container, false)
|
||||
): FragmentFoodListBinding {
|
||||
return FragmentFoodListBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun initialize() {
|
||||
@@ -17,13 +17,13 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.shuwei.dish.match.R
|
||||
import com.shuwei.dish.match.adapter.VectorCollectionAdapter
|
||||
import com.shuwei.dish.match.adapter.FoodSearchAdapter
|
||||
import com.shuwei.dish.match.adapter.Food3Adapter
|
||||
import com.shuwei.dish.match.base.BaseFragment
|
||||
import com.shuwei.dish.match.databinding.FragmentVectorCollectionBinding
|
||||
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
||||
import com.shuwei.dish.match.dialog.Loading
|
||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||
import com.shuwei.dish.match.entity.FoodCollectionBean
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import com.shuwei.dish.match.objbox.FoodModule
|
||||
import com.shuwei.dish.match.ui.CollectedFoodActivity
|
||||
import com.shuwei.dish.match.ui.SingleFragmentActivity
|
||||
@@ -53,28 +53,28 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
}
|
||||
}
|
||||
|
||||
private val searchList = mutableListOf<CookFoodGoodsEntity>().apply {
|
||||
add(CookFoodGoodsEntity(goodsId = "200001", goodsName = "土豆丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200002", goodsName = "土豆片"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200003", goodsName = "土豆丁"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200004", goodsName = "胡萝卜丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200005", goodsName = "胡萝卜片"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200006", goodsName = "胡萝卜丁"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200007", goodsName = "黄瓜丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200008", goodsName = "黄瓜片"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200009", goodsName = "黄瓜丁"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200010", goodsName = "洋葱丝"))
|
||||
add(CookFoodGoodsEntity(goodsId = "200011", goodsName = "洋葱丁"))
|
||||
private val searchList = 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 var checkedItem: CookFoodGoodsEntity? = null
|
||||
private var checkedItem: GoodsItem? = null
|
||||
private val searchAdapter by lazy {
|
||||
FoodSearchAdapter(searchList).apply {
|
||||
Food3Adapter(searchList).apply {
|
||||
setOnItemClickListener { adapter, view, position ->
|
||||
searchList.forEachIndexed { index, item -> item.isClicked = index == position }
|
||||
checkedItem = searchList[position]
|
||||
notifyDataSetChanged()
|
||||
checkedItem?.let {
|
||||
selectedFoodId = it.foodId
|
||||
selectedFoodId = it.goodsId
|
||||
selectedFoodName = it.goodsName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
package com.shuwei.dish.match.utils
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.shuwei.dish.match.adapter.DishPartAdapter
|
||||
import com.shuwei.dish.match.adapter.FoodListAdapter
|
||||
|
||||
class SwipeCallback(
|
||||
private val adapter: BaseQuickAdapter<*,*>,
|
||||
private val callback: (position: Int) -> Unit = {}
|
||||
) :
|
||||
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
return false // 不允许拖动,只允许滑动删除
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.getBindingAdapterPosition()
|
||||
callback(position)
|
||||
}
|
||||
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val position = viewHolder.getBindingAdapterPosition()
|
||||
if (position < 0) {
|
||||
return makeMovementFlags(0, 0)
|
||||
}
|
||||
// 默认的拖动和滑动方向
|
||||
val dragFlags = 0
|
||||
// ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
var swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
|
||||
|
||||
if (adapter is FoodListAdapter) {
|
||||
if (adapter.items.isEmpty()) {
|
||||
return makeMovementFlags(0, 0)
|
||||
}
|
||||
if (!adapter.items[position].isCooking) {
|
||||
swipeFlags = 0
|
||||
}
|
||||
} else if (adapter is DishPartAdapter) {
|
||||
if (adapter.items.isEmpty()) {
|
||||
return makeMovementFlags(0, 0)
|
||||
}
|
||||
if (!adapter.items[position].isNewDishType) {
|
||||
swipeFlags = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 根据viewHolder的逻辑判断是否允许滑动
|
||||
val count = recyclerView.adapter?.itemCount ?:0
|
||||
//BaseQuickAdapter中空布局使用的ViewHolder是这个import com.chad.library.adapter4.viewholder.StateLayoutVH
|
||||
if (count == 1 && viewHolder !is QuickViewHolder) {
|
||||
swipeFlags = 0 // 不允许滑动
|
||||
}
|
||||
return makeMovementFlags(dragFlags, swipeFlags)
|
||||
}
|
||||
|
||||
}
|
||||
//package com.shuwei.dish.match.utils
|
||||
//
|
||||
//import androidx.recyclerview.widget.ItemTouchHelper
|
||||
//import androidx.recyclerview.widget.RecyclerView
|
||||
//import com.chad.library.adapter4.BaseQuickAdapter
|
||||
//import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
//import com.shuwei.dish.match.adapter.DishPartAdapter
|
||||
//import com.shuwei.dish.match.adapter.FoodListAdapter
|
||||
//
|
||||
//class SwipeCallback(
|
||||
// private val adapter: BaseQuickAdapter<*,*>,
|
||||
// private val callback: (position: Int) -> Unit = {}
|
||||
//) :
|
||||
// ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
// override fun onMove(
|
||||
// recyclerView: RecyclerView,
|
||||
// viewHolder: RecyclerView.ViewHolder,
|
||||
// target: RecyclerView.ViewHolder
|
||||
// ): Boolean {
|
||||
// return false // 不允许拖动,只允许滑动删除
|
||||
// }
|
||||
//
|
||||
// override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
// val position = viewHolder.getBindingAdapterPosition()
|
||||
// callback(position)
|
||||
// }
|
||||
//
|
||||
// override fun getMovementFlags(
|
||||
// recyclerView: RecyclerView,
|
||||
// viewHolder: RecyclerView.ViewHolder
|
||||
// ): Int {
|
||||
// val position = viewHolder.getBindingAdapterPosition()
|
||||
// if (position < 0) {
|
||||
// return makeMovementFlags(0, 0)
|
||||
// }
|
||||
// // 默认的拖动和滑动方向
|
||||
// val dragFlags = 0
|
||||
// // ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
// var swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
|
||||
//
|
||||
// if (adapter is FoodListAdapter) {
|
||||
// if (adapter.items.isEmpty()) {
|
||||
// return makeMovementFlags(0, 0)
|
||||
// }
|
||||
// if (!adapter.items[position].isCooking) {
|
||||
// swipeFlags = 0
|
||||
// }
|
||||
// } else if (adapter is DishPartAdapter) {
|
||||
// if (adapter.items.isEmpty()) {
|
||||
// return makeMovementFlags(0, 0)
|
||||
// }
|
||||
// if (!adapter.items[position].isNewDishType) {
|
||||
// swipeFlags = 0
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 根据viewHolder的逻辑判断是否允许滑动
|
||||
// val count = recyclerView.adapter?.itemCount ?:0
|
||||
// //BaseQuickAdapter中空布局使用的ViewHolder是这个import com.chad.library.adapter4.viewholder.StateLayoutVH
|
||||
// if (count == 1 && viewHolder !is QuickViewHolder) {
|
||||
// swipeFlags = 0 // 不允许滑动
|
||||
// }
|
||||
// return makeMovementFlags(dragFlags, swipeFlags)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/white"/>
|
||||
<corners android:radius="15dp"/>
|
||||
<stroke android:color="#DCDCDC" android:width="1dp"/>
|
||||
</shape>
|
||||
@@ -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"
|
||||
@@ -46,7 +46,7 @@
|
||||
app:spanCount="2"
|
||||
tools:itemCount="4"
|
||||
android:background="@drawable/shape_white_12_corners"
|
||||
tools:listitem="@layout/list_item_search_goods_info"
|
||||
tools:listitem="@layout/list_item_food"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
@@ -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
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
android:layout_marginBottom="20dp"
|
||||
android:overScrollMode="never"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/list_item_dish_record" />
|
||||
tools:listitem="@layout/list_item_food2" />
|
||||
|
||||
<com.scwang.smart.refresh.footer.ClassicsFooter
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:background="@color/bg_color">
|
||||
tools:background="@drawable/bg_other_page">
|
||||
|
||||
<!-- 顶部标题 -->
|
||||
<TextView
|
||||
@@ -12,13 +12,12 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:text="全设备秤数据监控"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:textColor="@color/home_title"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="36sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@@ -30,18 +29,16 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/tvMasterTitle"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/home_sub_title"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp"
|
||||
tools:text="已发现设备:3 台" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivMasterBack"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="start"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingHorizontal="30dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/tvMasterTitle"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvMasterTitle"
|
||||
|
||||
@@ -93,14 +93,14 @@
|
||||
</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"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="vertical"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/list_item_dish_cook" />
|
||||
tools:listitem="@layout/list_item_food_material" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:background="@drawable/shape_white_fb_15_corners2"
|
||||
android:background="@drawable/shape_white_dc_15_corners"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:clipToOutline="true">
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:background="@drawable/shape_white_fb_15_corners2"
|
||||
android:background="@drawable/shape_white_dc_15_corners"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:clipToOutline="true">
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:background="@drawable/shape_white_fb_15_corners2"
|
||||
android:background="@drawable/shape_white_dc_15_corners"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:clipToOutline="true">
|
||||
|
||||
|
||||
@@ -39,6 +39,6 @@
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="10"
|
||||
tools:listitem="@layout/list_item_search_goods_info" />
|
||||
tools:listitem="@layout/list_item_food" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="10"
|
||||
tools:listitem="@layout/list_item_search_goods_info" />
|
||||
tools:listitem="@layout/list_item_food" />
|
||||
|
||||
<com.scwang.smart.refresh.footer.ClassicsFooter
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="10"
|
||||
tools:listitem="@layout/list_item_search_goods_info" />
|
||||
tools:listitem="@layout/list_item_food" />
|
||||
|
||||
<com.scwang.smart.refresh.footer.ClassicsFooter
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="10"
|
||||
tools:listitem="@layout/list_item_search_goods_info" />
|
||||
tools:listitem="@layout/list_item_food" />
|
||||
|
||||
<com.scwang.smart.refresh.footer.ClassicsFooter
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
tools:itemCount="10"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
tools:listitem="@layout/list_item_search_food"/>
|
||||
tools:listitem="@layout/list_item_food3"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnSave"
|
||||
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
# scale 包架构分析
|
||||
|
||||
## 概览
|
||||
|
||||
scale 包负责多设备秤数据的采集、发现、传输与聚合,采用 **mDNS + UDP 双路冗余发现 + WebSocket 长连接**的架构。
|
||||
|
||||
根据设备角色(MASTER/SLAVE)运行不同的服务组合:
|
||||
|
||||
| 类 | 子设备 | 主设备 |
|
||||
|---|:---:|:---:|
|
||||
| MdnsRegisterManager | ✅ | ✅ |
|
||||
| ScaleWebSocketServer | ✅ | ✅ |
|
||||
| UdpBroadcastSender | ✅ | ❌ |
|
||||
| MdnsDiscoveryManager | ❌ | ✅ |
|
||||
| UdpBroadcastReceiver | ❌ | ✅ |
|
||||
| ScaleWebSocketClient | ❌ | ✅ |
|
||||
| ScaleDataAggregator | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 整体数据流
|
||||
|
||||
```
|
||||
子设备 主设备
|
||||
│ │
|
||||
├─ UdpBroadcastSender ──UDP广播(8766)──▶ UdpBroadcastReceiver ─┐
|
||||
├─ MdnsRegisterManager ──mDNS注册──▶ MdnsDiscoveryManager ─┤
|
||||
│ │ │
|
||||
│ onDeviceFound │
|
||||
│ │ │
|
||||
├─ ScaleWebSocketServer ◀──WS连接(8765)── ScaleWebSocketClient ◀┘
|
||||
│ │ 秤数据推送 │
|
||||
│ └──────────────────────────────▶ ScaleDataAggregator
|
||||
│ │
|
||||
│ StateFlow → UI
|
||||
│
|
||||
│ ◀── ScaleCommand(清零指令)─────────────────┤
|
||||
│ ◀── ScaleEvent(配置同步)──────────────────┤
|
||||
│ ──▶ ScaleEvent(调料添加通知)──────────────▶│
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据模型(3个)
|
||||
|
||||
### ScaleData
|
||||
单个秤的数据快照,是整个包内流转的核心数据结构。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| deviceId | String | 所属设备 ID |
|
||||
| address | Int | 秤硬件地址编号 |
|
||||
| weight | Double | 重量(克) |
|
||||
| state | Int | 1=稳定,0=不稳定,2=量程溢出 |
|
||||
| ts | Long | 数据时间戳(毫秒) |
|
||||
| ip | String | 所属设备 IP,用于 UI 展示;网络传输数据中可能为空 |
|
||||
| name | String? | 秤槽位名称(可选),默认为 null |
|
||||
|
||||
### ScaleEvent
|
||||
主子设备之间的**非重量类**通知,通过 WebSocket 传输。
|
||||
|
||||
| 事件类型 | 方向 | 说明 |
|
||||
|---|---|---|
|
||||
| `seasoning_added` | 子设备 → 主设备 | 某秤检测到调料添加,携带 `delta`(重量变化量) |
|
||||
| `seasoning_config` | 主设备 → 子设备 | 调料槽位配置同步,携带 `List<SlotConfig>` |
|
||||
|
||||
### ScaleCommand
|
||||
主设备向子设备发送的**控制指令**,子设备收到后校验 `deviceId` 是否匹配自身再执行。
|
||||
|
||||
| 指令 | 说明 |
|
||||
|---|---|
|
||||
| `tare` | 清零指定地址的秤 |
|
||||
|
||||
---
|
||||
|
||||
## 设备发现(4个)
|
||||
|
||||
采用 mDNS + UDP 双路冗余,任意一路发现子设备均可触发连接。
|
||||
|
||||
### MdnsRegisterManager(主设备和子设备均运行)
|
||||
将本机 WebSocket 服务以 mDNS 形式注册到局域网。
|
||||
|
||||
- 服务名格式:`DishMatch-{deviceId}`
|
||||
- 服务类型:`_dishmatch._tcp.`
|
||||
- 端口:`8765`
|
||||
|
||||
### MdnsDiscoveryManager(仅主设备运行)
|
||||
持续扫描局域网中所有 `DishMatch-*` 的 mDNS 服务,解析出 IP:PORT 后触发 `onDeviceFound`。
|
||||
|
||||
> **关键细节**:Android `NsdManager.resolveService` 不支持并发调用,多台子设备同时被发现时会报 `FAILURE_ALREADY_ACTIVE(3)`。内部使用串行队列(`resolveQueue`)逐一解析,避免解析失败。
|
||||
|
||||
### UdpBroadcastSender(仅子设备运行)
|
||||
每 5 秒向 `255.255.255.255:8766` 广播一个 JSON 包,作为 mDNS 的兜底发现机制。
|
||||
|
||||
广播包结构:
|
||||
```json
|
||||
{ "deviceId": "xxx", "ip": "192.168.1.x", "port": 8765 }
|
||||
```
|
||||
|
||||
### UdpBroadcastReceiver(仅主设备运行)
|
||||
监听 `8766` 端口,接收子设备的 UDP 广播包,解析后触发 `onDeviceFound`。
|
||||
|
||||
- 内部用 `knownDevices`(`ConcurrentHashMap`)缓存已发现的设备,避免每 5 秒重复触发连接
|
||||
- 设备断线时需调用 `removeDevice()` 清除缓存,才能在重连时重新触发 `onDeviceFound`
|
||||
|
||||
---
|
||||
|
||||
## 数据传输(2个)
|
||||
|
||||
### ScaleWebSocketServer(主设备和子设备均运行)
|
||||
基于 `java-websocket` 的服务端,监听 `8765` 端口。
|
||||
|
||||
**职责:**
|
||||
- 监听本机 `WeightUtil` 回调,将秤数据实时推送给所有已连接客户端(节流 100ms)
|
||||
- 新客户端连接时,立即推送所有秤的最新快照(`latestData` 缓存)
|
||||
- 接收主设备下发的 `ScaleCommand`(清零)和 `ScaleEvent`(配置同步)
|
||||
- 通过 `connectionCount` 计数器跟踪连接数,避免 `connections` 集合竞态问题
|
||||
|
||||
### ScaleWebSocketClient(仅主设备运行)
|
||||
管理主设备与多台子设备的 WebSocket 长连接。
|
||||
|
||||
**职责:**
|
||||
- 多设备并发连接(`ConcurrentHashMap` 管理)
|
||||
- 断线自动重连(指数退避:2s → 4s → 8s → ... → 30s)
|
||||
- 向指定设备或全部设备发送指令/事件
|
||||
- 子设备首次连接成功时触发 `onDeviceConnected`,供主设备推送全量配置
|
||||
|
||||
---
|
||||
|
||||
## 数据聚合(1个)
|
||||
|
||||
### ScaleDataAggregator(仅主设备运行)
|
||||
将本机秤和所有子设备秤的数据统一汇总,以 `StateFlow` 暴露给 UI 层。
|
||||
|
||||
- **本机秤**:直接监听 `WeightUtil` 回调,无需经过网络
|
||||
- **子设备秤**:由 `ScaleWebSocketClient.onScaleData` 回调写入
|
||||
- **Map key 格式**:`{deviceId}#{address}`,便于 UI 按设备分组展示
|
||||
- **IP 回填**:若数据包先于 IP 信息到达,`setDeviceIp()` 会回填已缓存数据中的空 IP 字段
|
||||
|
||||
---
|
||||
|
||||
## 配置(1个)
|
||||
|
||||
### ScaleDeviceConfig
|
||||
硬编码各子设备的固定 UUID 和秤地址显示顺序。
|
||||
|
||||
| 常量 | 说明 |
|
||||
|---|---|
|
||||
| `DEVICE_ID_2/22/18/1` | 各子设备固定 UUID |
|
||||
| `SCALE_ORDER_22` | 22个秤的物理位置排列顺序 |
|
||||
| `SCALE_ORDER_18` | 18个秤的物理位置排列顺序 |
|
||||
| `DEVICE_ORDER` | 设备在列表中的显示顺序 |
|
||||
|
||||
---
|
||||
|
||||
## 门面(1个)
|
||||
|
||||
### ScaleServiceManager
|
||||
整个包的统一入口(单例),根据设备角色决定启动哪些服务,并将各组件串联起来。
|
||||
|
||||
**外部使用方式:**
|
||||
```kotlin
|
||||
// Application.onCreate()
|
||||
ScaleServiceManager.start(context)
|
||||
|
||||
// 主设备 UI 订阅全量秤数据
|
||||
ScaleServiceManager.allScales?.collect { scales -> ... }
|
||||
|
||||
// 主设备发送清零指令
|
||||
ScaleServiceManager.sendTare(deviceId, address)
|
||||
|
||||
// 主设备广播调料配置
|
||||
ScaleServiceManager.sendSeasoningConfig(slots)
|
||||
|
||||
// 主设备监听子设备秤事件(如调料添加)
|
||||
ScaleServiceManager.onScaleEvent = { event -> ... }
|
||||
|
||||
// 子设备监听主设备下发的调料配置同步
|
||||
ScaleServiceManager.onSeasoningConfig = { event -> ... }
|
||||
|
||||
// 子设备向主设备广播秤事件
|
||||
ScaleServiceManager.broadcastEvent(event)
|
||||
|
||||
// 子设备监听主设备连接状态变化
|
||||
ScaleServiceManager.onMasterConnectionChanged = { connected -> ... }
|
||||
|
||||
// 子设备查询当前是否有主设备连接
|
||||
ScaleServiceManager.isMasterConnected
|
||||
|
||||
// Application.onTerminate()
|
||||
ScaleServiceManager.stop()
|
||||
```
|
||||
|
||||
外部代码只需与 `ScaleServiceManager` 交互,无需感知内部任何组件。
|
||||
Reference in New Issue
Block a user