Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6b5088107 | ||
|
|
bbdebc0c9c | ||
|
|
0a90a8b368 | ||
|
|
07eb626e12 | ||
|
|
736e4820dd | ||
|
|
3fc54d5f52 | ||
|
|
b16d7d19cd | ||
|
|
a81e914b63 | ||
|
|
ffdf364ee8 | ||
|
|
6d3bb96d61 | ||
|
|
da712ed25a | ||
|
|
6b312ec689 | ||
|
|
e175893790 |
@@ -106,11 +106,6 @@
|
|||||||
android:theme="@style/Theme.DishMatch.NoSplash"
|
android:theme="@style/Theme.DishMatch.NoSplash"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
|
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
|
||||||
<activity
|
|
||||||
android:name="com.shuwei.dish.match.ui.Scale22GridActivity"
|
|
||||||
android:theme="@style/Theme.DishMatch.NoSplash"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.shuwei.dish.match.ui.FoodRecognizeActivity"
|
android:name="com.shuwei.dish.match.ui.FoodRecognizeActivity"
|
||||||
android:theme="@style/Theme.DishMatch.NoSplash"
|
android:theme="@style/Theme.DishMatch.NoSplash"
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
package com.shuwei.dish.match.adapter
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
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.ListItemScale22RowBinding
|
|
||||||
import com.shuwei.dish.match.scale.ScaleData
|
|
||||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 18个秤的网格 Adapter
|
|
||||||
* 使用 GridLayoutManager(6列)排列,第1行格子 margin 较大(视觉更小),第2-3行 margin 较小
|
|
||||||
* 点击格子触发清零回调
|
|
||||||
*/
|
|
||||||
class Scale18GridAdapter : BaseQuickAdapter<ScaleData, Scale18GridAdapter.VH>(mutableListOf()) {
|
|
||||||
|
|
||||||
inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root)
|
|
||||||
|
|
||||||
/** 用 address=0 的哨兵表示空位 */
|
|
||||||
private val empty = ScaleData("", 0, 0.0, -1, 0)
|
|
||||||
|
|
||||||
/** 当前高亮的格子位置,-1 表示无高亮 */
|
|
||||||
private var highlightedPosition: Int = -1
|
|
||||||
|
|
||||||
/** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */
|
|
||||||
var onItemClick: ((ScaleData) -> Unit)? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按 SCALE_ORDER_18 顺序更新数据,空位用哨兵填充
|
|
||||||
* 只更新显示字段有变化的格子,避免全量刷新
|
|
||||||
*/
|
|
||||||
fun update(newList: List<ScaleData?>) {
|
|
||||||
val filled = newList.map { it ?: empty }
|
|
||||||
if (items.isEmpty()) {
|
|
||||||
submitList(filled)
|
|
||||||
} else {
|
|
||||||
filled.forEachIndexed { i, scale ->
|
|
||||||
val cur = items.getOrNull(i)
|
|
||||||
if (cur == null || cur.address != scale.address || cur.weight != scale.weight || cur.state != scale.state) {
|
|
||||||
set(i, scale)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 ScaleData 列表按 SCALE_ORDER_18 重排后调用 update
|
|
||||||
*/
|
|
||||||
fun updateByAddress(scales: List<ScaleData>) {
|
|
||||||
val byAddress = scales.associateBy { it.address }
|
|
||||||
update(ScaleDeviceConfig.SCALE_ORDER_18.map { byAddress[it] })
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 前6个位置为第1行(viewType=0,margin大),其余为第2-3行(viewType=1,margin小) */
|
|
||||||
override fun getItemViewType(position: Int, list: List<ScaleData>) = if (position < 6) 0 else 1
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
|
||||||
val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false)
|
|
||||||
// 第1行 margin=12dp(格子视觉更小),第2-3行 margin=2dp
|
|
||||||
val margin = if (viewType == 0) 12.dp else 2.dp
|
|
||||||
val screenWidth = parent.resources.displayMetrics.widthPixels
|
|
||||||
val cellSize = (screenWidth - 6 * margin * 2 - 10.dp) / 6
|
|
||||||
b.root.layoutParams = (b.root.layoutParams as ViewGroup.MarginLayoutParams).also {
|
|
||||||
it.width = cellSize
|
|
||||||
it.height = cellSize
|
|
||||||
// 第1行:顶部间距为0,底部间距2dp,左右保持原有 margin
|
|
||||||
if (viewType == 0) it.setMargins(margin, 0, margin, 2.dp)
|
|
||||||
else it.setMargins(margin, margin, margin, margin)
|
|
||||||
}
|
|
||||||
val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt()
|
|
||||||
b.tvAddr.textSize = (baseSp * 0.16f).coerceIn(9f, 14f)
|
|
||||||
b.tvWeight.textSize = (baseSp * 0.22f).coerceIn(12f, 20f)
|
|
||||||
b.tvState.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
|
||||||
val vh = VH(b)
|
|
||||||
// 在 ViewHolder 创建时绑定点击,只执行一次,不受 notifyItemChanged 重绑定影响
|
|
||||||
b.root.setOnClickListener {
|
|
||||||
val pos = vh.bindingAdapterPosition
|
|
||||||
if (pos != RecyclerView.NO_POSITION) {
|
|
||||||
items.getOrNull(pos)?.takeIf { it.address != 0 }?.let { onItemClick?.invoke(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vh
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) {
|
|
||||||
val valid = (item?.address ?: 0) != 0
|
|
||||||
holder.b.tvAddr.text = if (valid) "秤${item!!.address}" else ""
|
|
||||||
holder.b.tvWeight.text = if (valid) "${item!!.weight}g" else ""
|
|
||||||
holder.b.tvState.text = if (valid) stateStr(item!!.state) else ""
|
|
||||||
// 高亮格子:红底白字;其余恢复默认
|
|
||||||
if (position == highlightedPosition) {
|
|
||||||
holder.b.root.setBackgroundColor(ContextCompat.getColor(holder.b.root.context, R.color.red_ff4444))
|
|
||||||
val white = ContextCompat.getColor(holder.b.root.context, R.color.white)
|
|
||||||
holder.b.tvAddr.setTextColor(white)
|
|
||||||
holder.b.tvWeight.setTextColor(white)
|
|
||||||
holder.b.tvState.setTextColor(white)
|
|
||||||
} else {
|
|
||||||
holder.b.root.setBackgroundResource(R.drawable.shape_scale_cell)
|
|
||||||
val normal = ContextCompat.getColor(holder.b.root.context, R.color.home_title)
|
|
||||||
holder.b.tvAddr.setTextColor(normal)
|
|
||||||
holder.b.tvWeight.setTextColor(normal)
|
|
||||||
holder.b.tvState.setTextColor(normal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 高亮指定位置的格子(红底白字),同时清除上一个高亮
|
|
||||||
* @param position 要高亮的格子索引
|
|
||||||
*/
|
|
||||||
fun highlightPosition(position: Int) {
|
|
||||||
val prev = highlightedPosition
|
|
||||||
highlightedPosition = position
|
|
||||||
if (prev >= 0) notifyItemChanged(prev)
|
|
||||||
if (position >= 0) notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除所有高亮,恢复默认样式
|
|
||||||
*/
|
|
||||||
fun clearHighlight() {
|
|
||||||
highlightPosition(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stateStr(state: Int) = when (state) {
|
|
||||||
WeightUtil.STATE_STABLE -> "稳定"
|
|
||||||
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
|
||||||
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
|
||||||
else -> "$state"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
package com.shuwei.dish.match.adapter
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.chad.library.adapter4.BaseQuickAdapter
|
|
||||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
|
||||||
import com.shuwei.dish.match.R
|
|
||||||
import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
|
|
||||||
import com.shuwei.dish.match.scale.ScaleData
|
|
||||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 22个秤的网格 Adapter
|
|
||||||
* 使用 FlexboxLayoutManager 按列排列,前4个格子较大(对应物理上的大秤),其余较小
|
|
||||||
* 点击格子触发清零回调
|
|
||||||
*
|
|
||||||
* @param largeSize 前4个大格子的尺寸(px)
|
|
||||||
* @param smallSize 其余小格子的尺寸(px)
|
|
||||||
*/
|
|
||||||
class Scale22GridAdapter(
|
|
||||||
private val largeSize: Int,
|
|
||||||
private val smallSize: Int
|
|
||||||
) : BaseQuickAdapter<ScaleData, Scale22GridAdapter.VH>(mutableListOf()) {
|
|
||||||
|
|
||||||
inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root)
|
|
||||||
|
|
||||||
/** 用 address=0 的哨兵表示空位 */
|
|
||||||
private val empty = ScaleData("", 0, 0.0, -1, 0)
|
|
||||||
|
|
||||||
/** 当前高亮的格子位置,-1 表示无高亮 */
|
|
||||||
private var highlightedPosition: Int = -1
|
|
||||||
|
|
||||||
/** 点击回调,在 onCreateViewHolder 中绑定,不受 notifyItemChanged 影响 */
|
|
||||||
var onItemClick: ((ScaleData) -> Unit)? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按 SCALE_ORDER_22 顺序更新数据,空位用哨兵填充
|
|
||||||
* 只更新显示字段有变化的格子,避免全量刷新
|
|
||||||
*/
|
|
||||||
fun update(newList: List<ScaleData?>) {
|
|
||||||
val filled = newList.map { it ?: empty }
|
|
||||||
if (items.isEmpty()) {
|
|
||||||
submitList(filled)
|
|
||||||
} else {
|
|
||||||
filled.forEachIndexed { i, scale ->
|
|
||||||
val cur = items.getOrNull(i)
|
|
||||||
if (cur == null || cur.address != scale.address || cur.weight != scale.weight || cur.state != scale.state) {
|
|
||||||
set(i, scale)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 ScaleData 列表按 SCALE_ORDER_22 重排后调用 update
|
|
||||||
*/
|
|
||||||
fun updateByAddress(scales: List<ScaleData>) {
|
|
||||||
val byAddress = scales.associateBy { it.address }
|
|
||||||
update(ScaleDeviceConfig.SCALE_ORDER_22.map { byAddress[it] })
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 前4个位置为大格子(viewType=1),其余为小格子(viewType=0) */
|
|
||||||
override fun getItemViewType(position: Int, list: List<ScaleData>) = if (position < 4) 1 else 0
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
|
||||||
val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false)
|
|
||||||
val cellSize = if (viewType == 1) largeSize else smallSize
|
|
||||||
(b.root.layoutParams as? FlexboxLayoutManager.LayoutParams)?.apply {
|
|
||||||
width = cellSize
|
|
||||||
height = cellSize
|
|
||||||
setMargins(2.dp, 2.dp, 2.dp, 2.dp)
|
|
||||||
}
|
|
||||||
val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt()
|
|
||||||
b.tvAddr.textSize = (baseSp * 0.16f).coerceIn(9f, 14f)
|
|
||||||
b.tvWeight.textSize = (baseSp * 0.22f).coerceIn(12f, 20f)
|
|
||||||
b.tvState.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
|
||||||
val vh = VH(b)
|
|
||||||
// 在 ViewHolder 创建时绑定点击,只执行一次,不受 notifyItemChanged 重绑定影响
|
|
||||||
b.root.setOnClickListener {
|
|
||||||
val pos = vh.bindingAdapterPosition
|
|
||||||
if (pos != RecyclerView.NO_POSITION) {
|
|
||||||
items.getOrNull(pos)?.takeIf { it.address != 0 }?.let { onItemClick?.invoke(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vh
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) {
|
|
||||||
val valid = (item?.address ?: 0) != 0
|
|
||||||
holder.b.tvAddr.text = if (valid) "秤${item!!.address}" else ""
|
|
||||||
holder.b.tvWeight.text = if (valid) "${item!!.weight}g" else ""
|
|
||||||
holder.b.tvState.text = if (valid) stateStr(item!!.state) else ""
|
|
||||||
// 高亮格子:红底白字;其余恢复默认
|
|
||||||
if (position == highlightedPosition) {
|
|
||||||
holder.b.root.setBackgroundColor(ContextCompat.getColor(holder.b.root.context, R.color.red_ff4444))
|
|
||||||
val white = ContextCompat.getColor(holder.b.root.context, R.color.white)
|
|
||||||
holder.b.tvAddr.setTextColor(white)
|
|
||||||
holder.b.tvWeight.setTextColor(white)
|
|
||||||
holder.b.tvState.setTextColor(white)
|
|
||||||
} else {
|
|
||||||
holder.b.root.setBackgroundResource(R.drawable.shape_scale_cell)
|
|
||||||
val normal = ContextCompat.getColor(holder.b.root.context, R.color.home_title)
|
|
||||||
holder.b.tvAddr.setTextColor(normal)
|
|
||||||
holder.b.tvWeight.setTextColor(normal)
|
|
||||||
holder.b.tvState.setTextColor(normal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 高亮指定位置的格子(红底白字),同时清除上一个高亮
|
|
||||||
* @param position 要高亮的格子索引
|
|
||||||
*/
|
|
||||||
fun highlightPosition(position: Int) {
|
|
||||||
val prev = highlightedPosition
|
|
||||||
highlightedPosition = position
|
|
||||||
if (prev >= 0) notifyItemChanged(prev)
|
|
||||||
if (position >= 0) notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除所有高亮,恢复默认样式
|
|
||||||
*/
|
|
||||||
fun clearHighlight() {
|
|
||||||
highlightPosition(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stateStr(state: Int) = when (state) {
|
|
||||||
WeightUtil.STATE_STABLE -> "稳定"
|
|
||||||
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
|
||||||
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
|
||||||
else -> "$state"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,8 +19,9 @@ class ScaleRowAdapter : BaseQuickAdapter<ScaleRowAdapter.ScaleItem, ScaleRowAdap
|
|||||||
* @param address 秤地址
|
* @param address 秤地址
|
||||||
* @param weight 重量(克)
|
* @param weight 重量(克)
|
||||||
* @param state 状态码
|
* @param state 状态码
|
||||||
|
* @param name 调料名称,未配置时为空
|
||||||
*/
|
*/
|
||||||
data class ScaleItem(val address: Int, val weight: Double, val state: Int)
|
data class ScaleItem(val address: Int, val weight: Double, val state: Int, val name: String = "")
|
||||||
|
|
||||||
inner class VH(val b: ListItemScaleRowBinding) : QuickViewHolder(b.root)
|
inner class VH(val b: ListItemScaleRowBinding) : QuickViewHolder(b.root)
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ class ScaleRowAdapter : BaseQuickAdapter<ScaleRowAdapter.ScaleItem, ScaleRowAdap
|
|||||||
override fun onBindViewHolder(holder: VH, position: Int, item: ScaleItem?) {
|
override fun onBindViewHolder(holder: VH, position: Int, item: ScaleItem?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
holder.b.tvScaleLabel.text = "秤 ${item.address}"
|
holder.b.tvScaleLabel.text = "秤 ${item.address}"
|
||||||
|
holder.b.tvName.text = item.name.ifEmpty { "-" }
|
||||||
holder.b.tvWeight.text = "${item.weight} g"
|
holder.b.tvWeight.text = "${item.weight} g"
|
||||||
holder.b.tvState.text = when (item.state) {
|
holder.b.tvState.text = when (item.state) {
|
||||||
WeightUtil.STATE_STABLE -> "稳定"
|
WeightUtil.STATE_STABLE -> "稳定"
|
||||||
|
|||||||
@@ -1,80 +1,115 @@
|
|||||||
package com.shuwei.dish.match.adapter
|
package com.shuwei.dish.match.adapter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.chad.library.adapter4.BaseQuickAdapter
|
import com.chad.library.adapter4.BaseQuickAdapter
|
||||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
import com.shuwei.dish.match.databinding.ListItemSeasoningCellBinding
|
import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
|
||||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
import com.shuwei.dish.match.scale.ScaleData
|
||||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
||||||
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调料配置-下层(18格)网格 Adapter
|
* 18格网格 Adapter,同时服务于调料配置页和主设备秤总览页。
|
||||||
* 使用 GridLayoutManager(6列)排列,第1行格子 margin 较大,第2-3行 margin 较小
|
*
|
||||||
* 实体类为 SeasoningEntity,格子显示调料名称 goodsName
|
* @param showNameOnly true = 调料配置模式,仅显示 tvName,隐藏 tvWeight/tvState;
|
||||||
|
* false = 秤总览模式,三行全部显示
|
||||||
|
* @param showAddress true = 显示秤地址行(tvAddress),仅 MasterScaleActivity 使用
|
||||||
*/
|
*/
|
||||||
class Seasoning18GridAdapter : BaseQuickAdapter<SeasoningEntity, Seasoning18GridAdapter.VH>(mutableListOf()) {
|
class Seasoning18GridAdapter(
|
||||||
|
private val showNameOnly: Boolean = false,
|
||||||
|
private val showAddress: Boolean = false
|
||||||
|
) : BaseQuickAdapter<ScaleData, Seasoning18GridAdapter.VH>(mutableListOf()) {
|
||||||
|
|
||||||
/** 空位哨兵,sort=-1 表示无效 */
|
/** 空位哨兵,address=0 表示无效 */
|
||||||
private val empty = SeasoningEntity(goodsId = "").also { it.sort = -1 }
|
private val empty = ScaleData("", 0, 0.0, -1, 0)
|
||||||
|
|
||||||
/** 当前高亮的格子位置,-1 表示无高亮 */
|
/** 当前高亮的格子位置,-1 表示无高亮 */
|
||||||
private var highlightedPosition: Int = -1
|
private var highlightedPosition: Int = -1
|
||||||
|
|
||||||
/** 点击回调,预留,暂不使用 */
|
/** 点击回调 */
|
||||||
var onItemClick: ((SeasoningEntity, Int) -> Unit)? = null
|
var onItemClick: ((ScaleData, Int) -> Unit)? = null
|
||||||
|
|
||||||
inner class VH(val b: ListItemSeasoningCellBinding) : QuickViewHolder(b.root)
|
inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root)
|
||||||
|
|
||||||
/** 前6个位置为第1行(viewType=0,margin大),其余为第2-3行(viewType=1,margin小) */
|
/** 前6个位置为第1行(viewType=0,margin大),其余为第2-3行(viewType=1,margin小) */
|
||||||
override fun getItemViewType(position: Int, list: List<SeasoningEntity>) =
|
override fun getItemViewType(position: Int, list: List<ScaleData>) =
|
||||||
if (position < 6) 0 else 1
|
if (position < 6) 0 else 1
|
||||||
|
|
||||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||||
val b = ListItemSeasoningCellBinding.inflate(LayoutInflater.from(context), parent, false)
|
val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
val margin = if (viewType == 0) 12.dp else 2.dp
|
val margin = if (viewType == 0) 12.dp else 2.dp
|
||||||
val screenWidth = parent.resources.displayMetrics.widthPixels
|
val screenWidth = parent.resources.displayMetrics.widthPixels
|
||||||
val cellSize = (screenWidth - 6 * margin * 2 - 10.dp) / 6
|
val cellSize = (screenWidth - 6 * margin * 2 - 10.dp) / 6
|
||||||
(b.root.layoutParams as? ViewGroup.MarginLayoutParams)?.also {
|
(b.root.layoutParams as? ViewGroup.MarginLayoutParams)?.also {
|
||||||
it.width = cellSize
|
it.width = cellSize
|
||||||
it.height = cellSize
|
it.height = cellSize
|
||||||
// 第1行:顶部间距为0,底部间距2dp,左右保持原有 margin
|
|
||||||
if (viewType == 0) it.setMargins(margin, 0, margin, 2.dp)
|
if (viewType == 0) it.setMargins(margin, 0, margin, 2.dp)
|
||||||
else it.setMargins(margin, margin, margin, margin)
|
else it.setMargins(margin, margin, margin, margin)
|
||||||
}
|
}
|
||||||
val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt()
|
val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt()
|
||||||
b.tvGoodsName.textSize = (baseSp * 0.18f).coerceIn(10f, 16f)
|
b.tvAddress.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
||||||
|
b.tvName.textSize = (baseSp * 0.18f).coerceIn(10f, 16f)
|
||||||
|
b.tvWeight.textSize = (baseSp * 0.22f).coerceIn(12f, 20f)
|
||||||
|
b.tvState.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
||||||
|
// 秤地址行:仅 MasterScaleActivity 显示
|
||||||
|
b.tvAddress.visibility = if (showAddress) View.VISIBLE else View.GONE
|
||||||
|
// 调料配置模式隐藏重量和状态行
|
||||||
|
if (showNameOnly) {
|
||||||
|
b.tvWeight.visibility = View.GONE
|
||||||
|
b.tvState.visibility = View.GONE
|
||||||
|
}
|
||||||
val vh = VH(b)
|
val vh = VH(b)
|
||||||
// 点击事件预留,暂不处理
|
|
||||||
b.root.setOnClickListener {
|
b.root.setOnClickListener {
|
||||||
val pos = vh.bindingAdapterPosition
|
val pos = vh.bindingAdapterPosition
|
||||||
if (pos != RecyclerView.NO_POSITION) {
|
if (pos != RecyclerView.NO_POSITION) {
|
||||||
items.getOrNull(pos)?.let { item -> onItemClick?.invoke(item, pos) }
|
items.getOrNull(pos)?.takeIf { it.address != 0 }?.let { item -> onItemClick?.invoke(item, pos) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vh
|
return vh
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int, item: SeasoningEntity?) {
|
@SuppressLint("SetTextI18n")
|
||||||
holder.b.tvGoodsName.text = item?.goodsName?.takeIf { it.isNotBlank() } ?: "-"
|
override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) {
|
||||||
// 高亮格子:红底白字;其余恢复默认
|
val valid = (item?.address ?: 0) != 0
|
||||||
if (position == highlightedPosition) {
|
if (!valid) {
|
||||||
holder.b.tvGoodsName.setBackgroundColor(ContextCompat.getColor(holder.b.root.context, R.color.red_ff4444))
|
holder.b.tvAddress.text = ""
|
||||||
holder.b.tvGoodsName.setTextColor(ContextCompat.getColor(holder.b.root.context, R.color.white))
|
holder.b.tvName.text = "-"
|
||||||
|
holder.b.tvWeight.text = ""
|
||||||
|
holder.b.tvState.text = ""
|
||||||
} else {
|
} else {
|
||||||
holder.b.tvGoodsName.setBackgroundResource(R.drawable.shape_scale_cell)
|
holder.b.tvAddress.text = "秤${item!!.address}"
|
||||||
holder.b.tvGoodsName.setTextColor(ContextCompat.getColor(holder.b.root.context, R.color.home_title))
|
holder.b.tvName.text = if (!item.name.isNullOrBlank()) item.name else "-"
|
||||||
|
holder.b.tvWeight.text = "${item.weight}g"
|
||||||
|
holder.b.tvState.text = stateStr(item.state)
|
||||||
|
}
|
||||||
|
if (position == highlightedPosition) {
|
||||||
|
holder.b.root.setBackgroundColor(ContextCompat.getColor(holder.b.root.context, R.color.red_ff4444))
|
||||||
|
val white = ContextCompat.getColor(holder.b.root.context, R.color.white)
|
||||||
|
holder.b.tvAddress.setTextColor(white)
|
||||||
|
holder.b.tvName.setTextColor(white)
|
||||||
|
holder.b.tvWeight.setTextColor(white)
|
||||||
|
holder.b.tvState.setTextColor(white)
|
||||||
|
} else {
|
||||||
|
holder.b.root.setBackgroundResource(R.drawable.shape_scale_cell)
|
||||||
|
val normal = ContextCompat.getColor(holder.b.root.context, R.color.home_title)
|
||||||
|
val sub = ContextCompat.getColor(holder.b.root.context, R.color.home_sub_title)
|
||||||
|
holder.b.tvAddress.setTextColor(sub)
|
||||||
|
holder.b.tvName.setTextColor(sub)
|
||||||
|
holder.b.tvWeight.setTextColor(normal)
|
||||||
|
holder.b.tvState.setTextColor(sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 高亮指定位置的格子(红底白字),同时清除上一个高亮
|
* 高亮指定位置的格子(红底白字),同时清除上一个高亮
|
||||||
* @param position 要高亮的格子索引
|
|
||||||
*/
|
*/
|
||||||
fun highlightPosition(position: Int) {
|
fun highlightPosition(position: Int) {
|
||||||
val prev = highlightedPosition
|
val prev = highlightedPosition
|
||||||
@@ -91,23 +126,46 @@ class Seasoning18GridAdapter : BaseQuickAdapter<SeasoningEntity, Seasoning18Grid
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新指定位置的调料名称并刷新对应格子
|
* 更新指定位置的名称并刷新对应格子
|
||||||
* @param position 格子索引
|
|
||||||
* @param name 新的调料名称
|
|
||||||
*/
|
*/
|
||||||
fun updateItemName(position: Int, name: String) {
|
fun updateItemName(position: Int, name: String) {
|
||||||
items.getOrNull(position)?.goodsName = name
|
items.getOrNull(position)?.let {
|
||||||
notifyItemChanged(position)
|
set(position, it.copy(name = name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按 SCALE_ORDER_18 的 sort 顺序更新数据,空位用哨兵填充
|
* 按 SCALE_ORDER_18 的 sort 顺序更新数据,空位用哨兵填充
|
||||||
|
* 只更新显示字段有变化的格子,避免全量刷新导致 ViewHolder 重建影响点击监听
|
||||||
*/
|
*/
|
||||||
fun update(seasoningList: List<SeasoningEntity>) {
|
fun update(list: List<ScaleData>) {
|
||||||
val bySort = seasoningList.associateBy { it.sort }
|
val byAddress = list.associateBy { it.address }
|
||||||
val filled = ScaleDeviceConfig.SCALE_ORDER_18.map { sort ->
|
val filled = ScaleDeviceConfig.SCALE_ORDER_18.map { addr ->
|
||||||
bySort[sort] ?: empty.copy().also { it.sort = sort }
|
byAddress[addr] ?: empty.copy(address = addr)
|
||||||
}
|
}
|
||||||
submitList(filled)
|
if (items.isEmpty()) {
|
||||||
|
submitList(filled)
|
||||||
|
} else {
|
||||||
|
filled.forEachIndexed { i, scale ->
|
||||||
|
val cur = items.getOrNull(i)
|
||||||
|
if (cur == null || cur.address != scale.address || cur.weight != scale.weight || cur.state != scale.state || cur.name != scale.name) {
|
||||||
|
set(i, scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 ScaleData 列表按 SCALE_ORDER_18 重排后调用 update
|
||||||
|
*/
|
||||||
|
fun updateByAddress(scales: List<ScaleData>) {
|
||||||
|
update(scales)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stateStr(state: Int) = when (state) {
|
||||||
|
WeightUtil.STATE_STABLE -> "稳定"
|
||||||
|
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
||||||
|
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
||||||
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.shuwei.dish.match.adapter
|
package com.shuwei.dish.match.adapter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -9,41 +11,45 @@ import com.chad.library.adapter4.BaseQuickAdapter
|
|||||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
import com.shuwei.dish.match.databinding.ListItemSeasoningCellBinding
|
import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
|
||||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
import com.shuwei.dish.match.scale.ScaleData
|
||||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
||||||
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调料配置-上层(22格)网格 Adapter
|
* 22格网格 Adapter,同时服务于调料配置页和主设备秤总览页。
|
||||||
* 使用 FlexboxLayoutManager 按列排列,前4个格子较大,其余较小
|
|
||||||
* 实体类为 SeasoningEntity,格子显示调料名称 goodsName
|
|
||||||
*
|
*
|
||||||
* @param largeSize 前4个大格子的尺寸(px)
|
* @param largeSize 前4个大格子的尺寸(px)
|
||||||
* @param smallSize 其余小格子的尺寸(px)
|
* @param smallSize 其余小格子的尺寸(px)
|
||||||
|
* @param showNameOnly true = 调料配置模式,仅显示 tvName,隐藏 tvWeight/tvState;
|
||||||
|
* false = 秤总览模式,三行全部显示
|
||||||
|
* @param showAddress true = 显示秤地址行(tvAddress),仅 MasterScaleActivity 使用
|
||||||
*/
|
*/
|
||||||
class Seasoning22GridAdapter(
|
class Seasoning22GridAdapter(
|
||||||
private val largeSize: Int,
|
private val largeSize: Int,
|
||||||
private val smallSize: Int
|
private val smallSize: Int,
|
||||||
) : BaseQuickAdapter<SeasoningEntity, Seasoning22GridAdapter.VH>(mutableListOf()) {
|
private val showNameOnly: Boolean = false,
|
||||||
|
private val showAddress: Boolean = false
|
||||||
|
) : BaseQuickAdapter<ScaleData, Seasoning22GridAdapter.VH>(mutableListOf()) {
|
||||||
|
|
||||||
/** 空位哨兵,sort=-1 表示无效 */
|
/** 空位哨兵,address=0 表示无效 */
|
||||||
private val empty = SeasoningEntity(goodsId = "").also { it.sort = -1 }
|
private val empty = ScaleData("", 0, 0.0, -1, 0)
|
||||||
|
|
||||||
/** 当前高亮的格子位置,-1 表示无高亮 */
|
/** 当前高亮的格子位置,-1 表示无高亮 */
|
||||||
private var highlightedPosition: Int = -1
|
private var highlightedPosition: Int = -1
|
||||||
|
|
||||||
/** 点击回调,预留,暂不使用 */
|
/** 点击回调 */
|
||||||
var onItemClick: ((SeasoningEntity, Int) -> Unit)? = null
|
var onItemClick: ((ScaleData, Int) -> Unit)? = null
|
||||||
|
|
||||||
inner class VH(val b: ListItemSeasoningCellBinding) : QuickViewHolder(b.root)
|
inner class VH(val b: ListItemScale22RowBinding) : QuickViewHolder(b.root)
|
||||||
|
|
||||||
/** 前4个位置为大格子(viewType=1),其余为小格子(viewType=0) */
|
/** 前4个位置为大格子(viewType=1),其余为小格子(viewType=0) */
|
||||||
override fun getItemViewType(position: Int, list: List<SeasoningEntity>) =
|
override fun getItemViewType(position: Int, list: List<ScaleData>) =
|
||||||
if (position < 4) 1 else 0
|
if (position < 4) 1 else 0
|
||||||
|
|
||||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||||
val b = ListItemSeasoningCellBinding.inflate(LayoutInflater.from(context), parent, false)
|
val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
val cellSize = if (viewType == 1) largeSize else smallSize
|
val cellSize = if (viewType == 1) largeSize else smallSize
|
||||||
(b.root.layoutParams as? FlexboxLayoutManager.LayoutParams)?.apply {
|
(b.root.layoutParams as? FlexboxLayoutManager.LayoutParams)?.apply {
|
||||||
width = cellSize
|
width = cellSize
|
||||||
@@ -51,33 +57,62 @@ class Seasoning22GridAdapter(
|
|||||||
setMargins(2.dp, 2.dp, 2.dp, 2.dp)
|
setMargins(2.dp, 2.dp, 2.dp, 2.dp)
|
||||||
}
|
}
|
||||||
val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt()
|
val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt()
|
||||||
b.tvGoodsName.textSize = (baseSp * 0.18f).coerceIn(10f, 16f)
|
b.tvAddress.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
||||||
|
b.tvName.textSize = (baseSp * 0.18f).coerceIn(10f, 16f)
|
||||||
|
b.tvWeight.textSize = (baseSp * 0.22f).coerceIn(12f, 20f)
|
||||||
|
b.tvState.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
||||||
|
// 秤地址行:仅 MasterScaleActivity 显示
|
||||||
|
b.tvAddress.visibility = if (showAddress) View.VISIBLE else View.GONE
|
||||||
|
// 调料配置模式隐藏重量和状态行
|
||||||
|
if (showNameOnly) {
|
||||||
|
b.tvWeight.visibility = View.GONE
|
||||||
|
b.tvState.visibility = View.GONE
|
||||||
|
}
|
||||||
val vh = VH(b)
|
val vh = VH(b)
|
||||||
// 点击事件预留,暂不处理
|
|
||||||
b.root.setOnClickListener {
|
b.root.setOnClickListener {
|
||||||
val pos = vh.bindingAdapterPosition
|
val pos = vh.bindingAdapterPosition
|
||||||
if (pos != RecyclerView.NO_POSITION) {
|
if (pos != RecyclerView.NO_POSITION) {
|
||||||
items.getOrNull(pos)?.let { item -> onItemClick?.invoke(item, pos) }
|
items.getOrNull(pos)?.takeIf { it.address != 0 }?.let { item -> onItemClick?.invoke(item, pos) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vh
|
return vh
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int, item: SeasoningEntity?) {
|
@SuppressLint("SetTextI18n")
|
||||||
holder.b.tvGoodsName.text = item?.goodsName?.takeIf { it.isNotBlank() } ?: "-"
|
override fun onBindViewHolder(holder: VH, position: Int, item: ScaleData?) {
|
||||||
|
val valid = (item?.address ?: 0) != 0
|
||||||
|
if (!valid) {
|
||||||
|
holder.b.tvAddress.text = ""
|
||||||
|
holder.b.tvName.text = "-"
|
||||||
|
holder.b.tvWeight.text = ""
|
||||||
|
holder.b.tvState.text = ""
|
||||||
|
} else {
|
||||||
|
holder.b.tvAddress.text = "秤${item!!.address}"
|
||||||
|
holder.b.tvName.text = if (!item.name.isNullOrBlank()) item.name else "-"
|
||||||
|
holder.b.tvWeight.text = "${item.weight}g"
|
||||||
|
holder.b.tvState.text = stateStr(item.state)
|
||||||
|
}
|
||||||
// 高亮格子:红底白字;其余恢复默认
|
// 高亮格子:红底白字;其余恢复默认
|
||||||
if (position == highlightedPosition) {
|
if (position == highlightedPosition) {
|
||||||
holder.b.tvGoodsName.setBackgroundColor(ContextCompat.getColor(holder.b.root.context, R.color.red_ff4444))
|
holder.b.root.setBackgroundColor(ContextCompat.getColor(holder.b.root.context, R.color.red_ff4444))
|
||||||
holder.b.tvGoodsName.setTextColor(ContextCompat.getColor(holder.b.root.context, R.color.white))
|
val white = ContextCompat.getColor(holder.b.root.context, R.color.white)
|
||||||
|
holder.b.tvAddress.setTextColor(white)
|
||||||
|
holder.b.tvName.setTextColor(white)
|
||||||
|
holder.b.tvWeight.setTextColor(white)
|
||||||
|
holder.b.tvState.setTextColor(white)
|
||||||
} else {
|
} else {
|
||||||
holder.b.tvGoodsName.setBackgroundResource(R.drawable.shape_scale_cell)
|
holder.b.root.setBackgroundResource(R.drawable.shape_scale_cell)
|
||||||
holder.b.tvGoodsName.setTextColor(ContextCompat.getColor(holder.b.root.context, R.color.home_title))
|
val normal = ContextCompat.getColor(holder.b.root.context, R.color.home_title)
|
||||||
|
val sub = ContextCompat.getColor(holder.b.root.context, R.color.home_sub_title)
|
||||||
|
holder.b.tvAddress.setTextColor(sub)
|
||||||
|
holder.b.tvName.setTextColor(sub)
|
||||||
|
holder.b.tvWeight.setTextColor(normal)
|
||||||
|
holder.b.tvState.setTextColor(sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 高亮指定位置的格子(红底白字),同时清除上一个高亮
|
* 高亮指定位置的格子(红底白字),同时清除上一个高亮
|
||||||
* @param position 要高亮的格子索引
|
|
||||||
*/
|
*/
|
||||||
fun highlightPosition(position: Int) {
|
fun highlightPosition(position: Int) {
|
||||||
val prev = highlightedPosition
|
val prev = highlightedPosition
|
||||||
@@ -94,23 +129,46 @@ class Seasoning22GridAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新指定位置的调料名称并刷新对应格子
|
* 更新指定位置的名称并刷新对应格子
|
||||||
* @param position 格子索引
|
|
||||||
* @param name 新的调料名称
|
|
||||||
*/
|
*/
|
||||||
fun updateItemName(position: Int, name: String) {
|
fun updateItemName(position: Int, name: String) {
|
||||||
items.getOrNull(position)?.goodsName = name
|
items.getOrNull(position)?.let {
|
||||||
notifyItemChanged(position)
|
set(position, it.copy(name = name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按 SCALE_ORDER_22 的 sort 顺序更新数据,空位用哨兵填充
|
* 按 SCALE_ORDER_22 的 sort 顺序更新数据,空位用哨兵填充
|
||||||
|
* 只更新显示字段有变化的格子,避免全量刷新导致 ViewHolder 重建影响点击监听
|
||||||
*/
|
*/
|
||||||
fun update(seasoningList: List<SeasoningEntity>) {
|
fun update(list: List<ScaleData>) {
|
||||||
val bySort = seasoningList.associateBy { it.sort }
|
val byAddress = list.associateBy { it.address }
|
||||||
val filled = ScaleDeviceConfig.SCALE_ORDER_22.map { sort ->
|
val filled = ScaleDeviceConfig.SCALE_ORDER_22.map { addr ->
|
||||||
bySort[sort] ?: empty.copy().also { it.sort = sort }
|
byAddress[addr] ?: empty.copy(address = addr)
|
||||||
}
|
}
|
||||||
submitList(filled)
|
if (items.isEmpty()) {
|
||||||
|
submitList(filled)
|
||||||
|
} else {
|
||||||
|
filled.forEachIndexed { i, scale ->
|
||||||
|
val cur = items.getOrNull(i)
|
||||||
|
if (cur == null || cur.address != scale.address || cur.weight != scale.weight || cur.state != scale.state || cur.name != scale.name) {
|
||||||
|
set(i, scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 ScaleData 列表按 SCALE_ORDER_22 重排后调用 update
|
||||||
|
*/
|
||||||
|
fun updateByAddress(scales: List<ScaleData>) {
|
||||||
|
update(scales)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stateStr(state: Int) = when (state) {
|
||||||
|
WeightUtil.STATE_STABLE -> "稳定"
|
||||||
|
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
||||||
|
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
||||||
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.shuwei.dish.match.adapter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.shuwei.dish.match.databinding.ItemSeasoningWeightBinding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调料使用重量列表 Adapter
|
||||||
|
* 用于 SubmitFoodActivity 展示各调料槽位的实时用量
|
||||||
|
* 同名调料已在外部聚合,adapter 按 goodsName 作为唯一标识
|
||||||
|
*/
|
||||||
|
class SeasoningWeightAdapter : RecyclerView.Adapter<SeasoningWeightAdapter.VH>() {
|
||||||
|
|
||||||
|
/** 调料展示数据项,key = goodsName(同名调料已聚合) */
|
||||||
|
data class Item(
|
||||||
|
val goodsId: String,
|
||||||
|
val goodsName: String,
|
||||||
|
/** 使用重量(克),同名调料已累加 */
|
||||||
|
val useWeight: Double
|
||||||
|
)
|
||||||
|
|
||||||
|
val items = mutableListOf<Item>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新单个 item,按 goodsName 匹配;不存在则追加
|
||||||
|
*/
|
||||||
|
fun updateItem(item: Item) {
|
||||||
|
val idx = items.indexOfFirst { it.goodsName == item.goodsName }
|
||||||
|
if (idx >= 0) {
|
||||||
|
items[idx] = item
|
||||||
|
notifyItemChanged(idx)
|
||||||
|
} else {
|
||||||
|
items.add(item)
|
||||||
|
notifyItemInserted(items.lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除指定 goodsName 的 item(聚合后用量不足阈值时调用)
|
||||||
|
*/
|
||||||
|
fun removeItem(goodsName: String) {
|
||||||
|
val idx = items.indexOfFirst { it.goodsName == goodsName }
|
||||||
|
if (idx >= 0) {
|
||||||
|
items.removeAt(idx)
|
||||||
|
notifyItemRemoved(idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||||
|
val binding = ItemSeasoningWeightBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context), parent, false
|
||||||
|
)
|
||||||
|
return VH(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
class VH(private val b: ItemSeasoningWeightBinding) : RecyclerView.ViewHolder(b.root) {
|
||||||
|
fun bind(item: Item) {
|
||||||
|
b.tvSeasoningName.text = item.goodsName
|
||||||
|
b.tvSeasoningWeight.text = "${item.useWeight}g"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,11 @@ import androidx.room.RoomDatabase
|
|||||||
import androidx.room.migration.Migration
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import com.shuwei.dish.match.db.dao.AppDao
|
import com.shuwei.dish.match.db.dao.AppDao
|
||||||
|
import com.shuwei.dish.match.db.dao.SeasoningSlotDao
|
||||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||||
|
import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
||||||
|
|
||||||
|
|
||||||
// 步骤1:更新版本号
|
// 步骤1:更新版本号
|
||||||
@@ -19,8 +21,9 @@ import com.shuwei.dish.match.entity.SeasoningEntity
|
|||||||
SeasoningEntity::class,
|
SeasoningEntity::class,
|
||||||
CookFoodEntity::class,
|
CookFoodEntity::class,
|
||||||
CookFoodGoodsEntity::class,
|
CookFoodGoodsEntity::class,
|
||||||
|
SeasoningSlotEntity::class,
|
||||||
],
|
],
|
||||||
version = 7,
|
version = 8,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
)
|
)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
@@ -30,6 +33,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
|
|
||||||
abstract fun appDao(): AppDao
|
abstract fun appDao(): AppDao
|
||||||
|
|
||||||
|
abstract fun seasoningSlotDao(): SeasoningSlotDao
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 步骤2:创建Migration对象
|
// 步骤2:创建Migration对象
|
||||||
@@ -140,6 +145,20 @@ val MIGRATION_5_6 = MigrationImpl(5, 6) { db ->
|
|||||||
db.endTransaction()
|
db.endTransaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MIGRATION_7_8 = MigrationImpl(7, 8) { db ->
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS `dm_seasoning_slot` (
|
||||||
|
`deviceId` TEXT NOT NULL,
|
||||||
|
`address` INTEGER NOT NULL,
|
||||||
|
`goodsId` TEXT NOT NULL,
|
||||||
|
`goodsName` TEXT NOT NULL,
|
||||||
|
PRIMARY KEY(`deviceId`, `address`)
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val MIGRATION_6_7 = MigrationImpl(6, 7) {db ->
|
val MIGRATION_6_7 = MigrationImpl(6, 7) {db ->
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|
||||||
@@ -210,6 +229,7 @@ class DatabaseProvider(private val context: Context) {
|
|||||||
// .addMigrations(MIGRATION_4_5)
|
// .addMigrations(MIGRATION_4_5)
|
||||||
// .addMigrations(MIGRATION_5_6)
|
// .addMigrations(MIGRATION_5_6)
|
||||||
// .addMigrations(MIGRATION_6_7)
|
// .addMigrations(MIGRATION_6_7)
|
||||||
|
.addMigrations(MIGRATION_7_8)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.shuwei.dish.match.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调料槽位配置 DAO
|
||||||
|
* 以 (deviceId, address) 为主键做 upsert,保证同一槽位只保留最新配置
|
||||||
|
*/
|
||||||
|
@Dao
|
||||||
|
interface SeasoningSlotDao {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入或更新单个槽位配置(主键冲突时覆盖)
|
||||||
|
*/
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun upsert(slot: SeasoningSlotEntity)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入或更新槽位配置(主设备广播全量时使用)
|
||||||
|
*/
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun upsertAll(slots: List<SeasoningSlotEntity>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定设备的全部槽位配置
|
||||||
|
* @param deviceId 目标设备 ID
|
||||||
|
*/
|
||||||
|
@Query("SELECT * FROM dm_seasoning_slot WHERE deviceId = :deviceId")
|
||||||
|
suspend fun queryByDeviceId(deviceId: String): List<SeasoningSlotEntity>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有设备的全部槽位配置(主设备广播时使用)
|
||||||
|
*/
|
||||||
|
@Query("SELECT * FROM dm_seasoning_slot")
|
||||||
|
suspend fun queryAll(): List<SeasoningSlotEntity>
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@ class SeasoningSelectDialog(
|
|||||||
|
|
||||||
// RecyclerView 初始化及滑动冲突处理
|
// RecyclerView 初始化及滑动冲突处理
|
||||||
binding.recyclerView.run {
|
binding.recyclerView.run {
|
||||||
layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
|
layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.VERTICAL, false)
|
||||||
adapter = this@SeasoningSelectDialog.adapter
|
adapter = this@SeasoningSelectDialog.adapter
|
||||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
|
||||||
@@ -134,19 +134,15 @@ class SeasoningSelectDialog(
|
|||||||
*/
|
*/
|
||||||
private fun getGoodsList() {
|
private fun getGoodsList() {
|
||||||
// TODO: 联调时注释掉下方模拟数据,取消注释真实接口调用
|
// TODO: 联调时注释掉下方模拟数据,取消注释真实接口调用
|
||||||
val mockData = mutableListOf(
|
val nameList = listOf(
|
||||||
SeasoningEntity(goodsId = "1", goodsName = "精盐"),
|
"盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
||||||
SeasoningEntity(goodsId = "2", goodsName = "鸡精"),
|
"料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
||||||
SeasoningEntity(goodsId = "3", goodsName = "陈醋"),
|
|
||||||
SeasoningEntity(goodsId = "4", goodsName = "老抽"),
|
|
||||||
SeasoningEntity(goodsId = "5", goodsName = "生抽"),
|
|
||||||
SeasoningEntity(goodsId = "6", goodsName = "花生油"),
|
|
||||||
SeasoningEntity(goodsId = "7", goodsName = "菜籽油"),
|
|
||||||
SeasoningEntity(goodsId = "8", goodsName = "胡椒粉"),
|
|
||||||
SeasoningEntity(goodsId = "9", goodsName = "十三香"),
|
|
||||||
SeasoningEntity(goodsId = "10", goodsName = "料酒"),
|
|
||||||
SeasoningEntity(goodsId = "11", goodsName = "白醋")
|
|
||||||
)
|
)
|
||||||
|
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)
|
loadGoodsList(mockData)
|
||||||
|
|
||||||
// val param = mutableMapOf<String, Any>(
|
// val param = mutableMapOf<String, Any>(
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.shuwei.dish.match.entity
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调料槽位配置表,记录每个格子对应哪种调料
|
||||||
|
* 主键为 (deviceId, address) 组合,唯一标识一个物理秤槽位
|
||||||
|
*
|
||||||
|
* @param deviceId 所属设备 ID,用于区分 22格/18格 设备
|
||||||
|
* @param address 秤硬件地址,对应 ScaleDeviceConfig.SCALE_ORDER_22/18 中的值
|
||||||
|
* @param goodsId 调料 ID
|
||||||
|
* @param goodsName 调料名称,用于 adapter 展示
|
||||||
|
*/
|
||||||
|
@Entity(
|
||||||
|
tableName = "dm_seasoning_slot",
|
||||||
|
primaryKeys = ["deviceId", "address"]
|
||||||
|
)
|
||||||
|
data class SeasoningSlotEntity(
|
||||||
|
val deviceId: String,
|
||||||
|
val address: Int,
|
||||||
|
val goodsId: String,
|
||||||
|
val goodsName: String
|
||||||
|
)
|
||||||
@@ -15,5 +15,6 @@ data class ScaleData(
|
|||||||
val weight: Double,
|
val weight: Double,
|
||||||
val state: Int,
|
val state: Int,
|
||||||
val ts: Long,
|
val ts: Long,
|
||||||
val ip: String = ""
|
val ip: String = "",
|
||||||
|
val name: String? = null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,20 +1,38 @@
|
|||||||
package com.shuwei.dish.match.scale
|
package com.shuwei.dish.match.scale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 秤事件数据类,用于子设备向主设备发送非重量类通知
|
* 秤事件数据类,用于主设备和子设备之间的非重量类通知
|
||||||
* @param type 事件类型,参见 companion object 中的常量
|
* @param type 事件类型,参见 companion object 中的常量
|
||||||
* @param deviceId 发送方设备 ID
|
* @param deviceId 发送方设备 ID
|
||||||
* @param address 触发事件的秤地址
|
* @param address 触发事件的秤地址,TYPE_SEASONING_CONFIG 时为 0(无意义)
|
||||||
* @param delta 重量变化量(克),仅 TYPE_SEASONING_ADDED 时有意义
|
* @param delta 重量变化量(克),仅 TYPE_SEASONING_ADDED 时有意义
|
||||||
|
* @param slots 调料槽位配置列表,仅 TYPE_SEASONING_CONFIG 时有效
|
||||||
*/
|
*/
|
||||||
data class ScaleEvent(
|
data class ScaleEvent(
|
||||||
val type: String,
|
val type: String,
|
||||||
val deviceId: String,
|
val deviceId: String,
|
||||||
val address: Int,
|
val address: Int = 0,
|
||||||
val delta: Double = 0.0
|
val delta: Double = 0.0,
|
||||||
|
val slots: List<SlotConfig>? = null
|
||||||
) {
|
) {
|
||||||
|
/**
|
||||||
|
* 单个槽位的配置信息,随 TYPE_SEASONING_CONFIG 事件一起传输
|
||||||
|
* @param deviceId 所属设备 ID
|
||||||
|
* @param address 秤硬件地址
|
||||||
|
* @param goodsId 调料 ID
|
||||||
|
* @param goodsName 调料名称
|
||||||
|
*/
|
||||||
|
data class SlotConfig(
|
||||||
|
val deviceId: String,
|
||||||
|
val address: Int,
|
||||||
|
val goodsId: String,
|
||||||
|
val goodsName: String
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** 调料添加事件:某秤重量增加超过阈值 */
|
/** 调料添加事件:某秤重量增加超过阈值 */
|
||||||
const val TYPE_SEASONING_ADDED = "seasoning_added"
|
const val TYPE_SEASONING_ADDED = "seasoning_added"
|
||||||
|
/** 调料槽位配置同步事件:主设备配置变更后广播给子设备 */
|
||||||
|
const val TYPE_SEASONING_CONFIG = "seasoning_config"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ package com.shuwei.dish.match.scale
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.shuwei.dish.match.base.BaseApp
|
||||||
import com.shuwei.dish.match.base.DeviceRole
|
import com.shuwei.dish.match.base.DeviceRole
|
||||||
import com.shuwei.dish.match.base.GlobalData
|
import com.shuwei.dish.match.base.GlobalData
|
||||||
import com.shuwei.dish.match.utils.NetworkUtil
|
import com.shuwei.dish.match.utils.NetworkUtil
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 秤服务统一门面(单例)
|
* 秤服务统一门面(单例)
|
||||||
@@ -59,6 +63,36 @@ object ScaleServiceManager {
|
|||||||
wsClient?.onScaleEvent = value
|
wsClient?.onScaleEvent = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收到主设备下发的调料配置同步事件时的回调,仅子设备有效
|
||||||
|
* 在子线程中调用,需自行切换到主线程更新 UI
|
||||||
|
*/
|
||||||
|
var onSeasoningConfig: ((ScaleEvent) -> Unit)?
|
||||||
|
get() = wsServer?.onSeasoningConfig
|
||||||
|
set(value) { wsServer?.onSeasoningConfig = value }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向所有已连接子设备广播调料配置(主设备调用)
|
||||||
|
* @param slots 全量槽位配置列表
|
||||||
|
*/
|
||||||
|
fun sendSeasoningConfig(slots: List<ScaleEvent.SlotConfig>) {
|
||||||
|
wsClient?.sendToAllSlaves(buildConfigEvent(slots))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向单台刚连接的子设备推送调料配置(连接时按需调用)
|
||||||
|
* @param deviceId 目标子设备 ID
|
||||||
|
*/
|
||||||
|
private fun sendSeasoningConfigTo(deviceId: String, slots: List<ScaleEvent.SlotConfig>) {
|
||||||
|
wsClient?.sendToDevice(deviceId, buildConfigEvent(slots))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildConfigEvent(slots: List<ScaleEvent.SlotConfig>) = ScaleEvent(
|
||||||
|
type = ScaleEvent.TYPE_SEASONING_CONFIG,
|
||||||
|
deviceId = GlobalData.deviceId,
|
||||||
|
slots = slots
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向主设备广播秤事件(子设备调用)
|
* 向主设备广播秤事件(子设备调用)
|
||||||
* @param event 要广播的事件
|
* @param event 要广播的事件
|
||||||
@@ -123,6 +157,19 @@ object ScaleServiceManager {
|
|||||||
it.onScaleData = { data -> aggregator?.onRemoteScaleData(data) }
|
it.onScaleData = { data -> aggregator?.onRemoteScaleData(data) }
|
||||||
it.onScaleEvent = { event -> _onScaleEvent?.invoke(event) }
|
it.onScaleEvent = { event -> _onScaleEvent?.invoke(event) }
|
||||||
it.onDeviceDisconnected = { remoteId -> aggregator?.removeDevice(remoteId) }
|
it.onDeviceDisconnected = { remoteId -> aggregator?.removeDevice(remoteId) }
|
||||||
|
it.onDeviceConnected = { remoteId ->
|
||||||
|
// 子设备连接成功时,从 Room 读取全量配置并单独推送给该设备
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val slots = BaseApp.instance!!.database.seasoningSlotDao()
|
||||||
|
.queryAll()
|
||||||
|
.map { slot ->
|
||||||
|
ScaleEvent.SlotConfig(
|
||||||
|
slot.deviceId, slot.address, slot.goodsId, slot.goodsName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (slots.isNotEmpty()) sendSeasoningConfigTo(remoteId, slots)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
wsClient = client
|
wsClient = client
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ class ScaleWebSocketClient {
|
|||||||
/** 设备断线时的回调(连接失败或关闭),在子线程调用 */
|
/** 设备断线时的回调(连接失败或关闭),在子线程调用 */
|
||||||
var onDeviceDisconnected: ((deviceId: String) -> Unit)? = null
|
var onDeviceDisconnected: ((deviceId: String) -> Unit)? = null
|
||||||
|
|
||||||
|
/** 子设备首次连接成功时的回调,在子线程调用;可用于主设备主动推送全量配置 */
|
||||||
|
var onDeviceConnected: ((deviceId: String) -> Unit)? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接到指定子设备
|
* 连接到指定子设备
|
||||||
* @param deviceId 子设备 ID
|
* @param deviceId 子设备 ID
|
||||||
@@ -79,6 +82,7 @@ class ScaleWebSocketClient {
|
|||||||
connections[deviceId] = webSocket
|
connections[deviceId] = webSocket
|
||||||
reconnectDelays[deviceId] = RECONNECT_BASE_MS
|
reconnectDelays[deviceId] = RECONNECT_BASE_MS
|
||||||
reconnectTasks.remove(deviceId)?.cancel(false)
|
reconnectTasks.remove(deviceId)?.cancel(false)
|
||||||
|
onDeviceConnected?.invoke(deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||||
@@ -148,6 +152,31 @@ class ScaleWebSocketClient {
|
|||||||
return ws.send(gson.toJson(command))
|
return ws.send(gson.toJson(command))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向所有已连接的子设备广播事件(主设备调用,用于配置同步等场景)
|
||||||
|
* @param event 要广播的事件
|
||||||
|
*/
|
||||||
|
fun sendToAllSlaves(event: ScaleEvent) {
|
||||||
|
val json = gson.toJson(event)
|
||||||
|
connections.values.forEach { ws ->
|
||||||
|
try { ws.send(json) } catch (e: Exception) {
|
||||||
|
Log.w(TAG, "sendToAllSlaves 失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向指定子设备发送事件(用于连接时单独推送配置)
|
||||||
|
* @param deviceId 目标子设备 ID
|
||||||
|
* @param event 要发送的事件
|
||||||
|
*/
|
||||||
|
fun sendToDevice(deviceId: String, event: ScaleEvent) {
|
||||||
|
val ws = connections[deviceId] ?: return
|
||||||
|
try { ws.send(gson.toJson(event)) } catch (e: Exception) {
|
||||||
|
Log.w(TAG, "sendToDevice $deviceId 失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 断开指定子设备连接(不再重连)
|
* 断开指定子设备连接(不再重连)
|
||||||
* @param deviceId 子设备 ID
|
* @param deviceId 子设备 ID
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class ScaleWebSocketServer(private val deviceId: String, private val context: Co
|
|||||||
/** 主设备连接状态变化回调:true=已连接,false=已断开;在主线程外调用,需自行切换线程 */
|
/** 主设备连接状态变化回调:true=已连接,false=已断开;在主线程外调用,需自行切换线程 */
|
||||||
var onConnectionChanged: ((connected: Boolean) -> Unit)? = null
|
var onConnectionChanged: ((connected: Boolean) -> Unit)? = null
|
||||||
|
|
||||||
|
/** 收到主设备下发的调料配置同步事件时的回调,在子线程调用 */
|
||||||
|
var onSeasoningConfig: ((event: ScaleEvent) -> Unit)? = null
|
||||||
|
|
||||||
/** 当前是否有主设备连接 */
|
/** 当前是否有主设备连接 */
|
||||||
val isConnected: Boolean
|
val isConnected: Boolean
|
||||||
get() = server?.connections?.isNotEmpty() == true
|
get() = server?.connections?.isNotEmpty() == true
|
||||||
@@ -72,6 +75,8 @@ class ScaleWebSocketServer(private val deviceId: String, private val context: Co
|
|||||||
*/
|
*/
|
||||||
fun stop() {
|
fun stop() {
|
||||||
WeightUtil.removeWeightListener(TAG)
|
WeightUtil.removeWeightListener(TAG)
|
||||||
|
onConnectionChanged = null
|
||||||
|
onSeasoningConfig = null
|
||||||
try {
|
try {
|
||||||
server?.stop(1000)
|
server?.stop(1000)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -135,6 +140,15 @@ class ScaleWebSocketServer(private val deviceId: String, private val context: Co
|
|||||||
|
|
||||||
override fun onMessage(conn: WebSocket, message: String) {
|
override fun onMessage(conn: WebSocket, message: String) {
|
||||||
try {
|
try {
|
||||||
|
val raw = gson.fromJson(message, Map::class.java)
|
||||||
|
if (raw.containsKey("type")) {
|
||||||
|
// 主设备下发的事件(如调料配置同步)
|
||||||
|
val event = gson.fromJson(message, ScaleEvent::class.java)
|
||||||
|
when (event.type) {
|
||||||
|
ScaleEvent.TYPE_SEASONING_CONFIG -> onSeasoningConfig?.invoke(event)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
val cmd = gson.fromJson(message, ScaleCommand::class.java)
|
val cmd = gson.fromJson(message, ScaleCommand::class.java)
|
||||||
// 校验指令目标设备是否为本机
|
// 校验指令目标设备是否为本机
|
||||||
if (cmd.deviceId != deviceId) {
|
if (cmd.deviceId != deviceId) {
|
||||||
@@ -148,7 +162,7 @@ class ScaleWebSocketServer(private val deviceId: String, private val context: Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "收到清零指令, 解析指令失败: ${e.message}")
|
Log.w(TAG, "收到消息, 解析失败: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package com.shuwei.dish.match.ui
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.addCallback
|
||||||
|
import androidx.core.content.IntentCompat
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter
|
import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter
|
||||||
import com.shuwei.dish.match.base.BaseActivity
|
import com.shuwei.dish.match.base.BaseActivity
|
||||||
@@ -28,6 +29,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
private const val TAG = "FoodRecognizeActivity"
|
private const val TAG = "FoodRecognizeActivity"
|
||||||
private const val EXTRA_IMAGE_URI = "extra_image_uri"
|
private const val EXTRA_IMAGE_URI = "extra_image_uri"
|
||||||
const val EXTRA_SELECTED_ITEM = "extra_selected_item"
|
const val EXTRA_SELECTED_ITEM = "extra_selected_item"
|
||||||
|
const val IS_MANUAL_CANCEL = "isManualCancel"
|
||||||
|
|
||||||
/** 内存缓存食材列表,避免通过 Intent 序列化传递大数据 */
|
/** 内存缓存食材列表,避免通过 Intent 序列化传递大数据 */
|
||||||
private var pendingGoodsList: ArrayList<CookFoodGoodsEntity>? = null
|
private var pendingGoodsList: ArrayList<CookFoodGoodsEntity>? = null
|
||||||
@@ -37,22 +39,27 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
* @param activity 宿主 Activity
|
* @param activity 宿主 Activity
|
||||||
* @param imageUri 食材照片路径(可为空)
|
* @param imageUri 食材照片路径(可为空)
|
||||||
* @param goodsList 识别到的食材列表
|
* @param goodsList 识别到的食材列表
|
||||||
* @param onResult 页面关闭后回调,携带用户选中的食材(未选中时为 null)
|
* @param finishCallback 页面关闭后回调,携带用户选中的食材(未选中时为 null)
|
||||||
*/
|
*/
|
||||||
fun start(
|
fun start(
|
||||||
activity: BaseActivity,
|
activity: BaseActivity,
|
||||||
imageUri: String? = null,
|
imageUri: String? = null,
|
||||||
goodsList: ArrayList<CookFoodGoodsEntity> = arrayListOf(),
|
goodsList: ArrayList<CookFoodGoodsEntity> = arrayListOf(),
|
||||||
onResult: (CookFoodGoodsEntity?) -> Unit = {}
|
finishCallback: (Boolean, CookFoodGoodsEntity?) -> Unit = { _, _ -> }
|
||||||
) {
|
) {
|
||||||
pendingGoodsList = goodsList
|
pendingGoodsList = goodsList
|
||||||
val intent = Intent(activity, FoodRecognizeActivity::class.java).apply {
|
val launchIntent = Intent(activity, FoodRecognizeActivity::class.java).apply {
|
||||||
putExtra(EXTRA_IMAGE_URI, imageUri)
|
putExtra(EXTRA_IMAGE_URI, imageUri)
|
||||||
}
|
}
|
||||||
activity.startActivity(intent) { resultIntent ->
|
activity.startActivity(launchIntent) { resultIntent ->
|
||||||
@Suppress("DEPRECATION")
|
val isManualCancel = resultIntent?.getBooleanExtra(IS_MANUAL_CANCEL, false) ?: false
|
||||||
val item = resultIntent?.getSerializableExtra(EXTRA_SELECTED_ITEM) as? CookFoodGoodsEntity
|
val entity = resultIntent?.let {
|
||||||
onResult(item)
|
IntentCompat.getSerializableExtra(
|
||||||
|
it,
|
||||||
|
EXTRA_SELECTED_ITEM,
|
||||||
|
CookFoodGoodsEntity::class.java)
|
||||||
|
}
|
||||||
|
finishCallback(isManualCancel, entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +71,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
ActivityManager.getActivityStack()
|
ActivityManager.getActivityStack()
|
||||||
.filterIsInstance<FoodRecognizeActivity>()
|
.filterIsInstance<FoodRecognizeActivity>()
|
||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
?.finishWithResult()
|
?.finishWithResult(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,11 +110,30 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
setHeaderBackground()
|
setHeaderBackground()
|
||||||
|
|
||||||
// 显示标题栏
|
// 显示标题栏
|
||||||
setTitleBar(titleBarAction = { it.visible() }, titleAction = { it.text = "食材识别" })
|
setTitleBar(
|
||||||
|
titleBarAction = { it.visible() },
|
||||||
|
titleAction = { it.text = "食材识别" },
|
||||||
|
backAction = {
|
||||||
|
it.setOnClickListener {
|
||||||
|
manualBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
initData()
|
initData()
|
||||||
initRecyclerView()
|
initRecyclerView()
|
||||||
initClickListeners()
|
initClickListeners()
|
||||||
|
|
||||||
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
|
manualBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动返回
|
||||||
|
*/
|
||||||
|
private fun manualBack() {
|
||||||
|
finishWithResult(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +194,7 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 取消:关闭页面
|
// 取消:关闭页面
|
||||||
binding.btnCancel.setOnClickListener { finish() }
|
binding.btnCancel.setOnClickListener { manualBack() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,12 +234,8 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
if (weight >= PrepareCookActivity.WEIGHT_RECOGNIZE_VALUE) {
|
if (weight >= PrepareCookActivity.WEIGHT_RECOGNIZE_VALUE) {
|
||||||
adapter.items.firstOrNull { it.isClicked }?.let { item ->
|
adapter.items.firstOrNull { it.isClicked }?.let { item ->
|
||||||
if (weight > (item.useWeight ?: 0.0)) {
|
if (pendingWeight != weight) {
|
||||||
// 重量增加:立即更新
|
// 无论增减,均延迟更新,避免手拿物品时的瞬时增重干扰
|
||||||
cancelPendingWeightUpdate()
|
|
||||||
item.useWeight = currentWeight
|
|
||||||
} else if (pendingWeight != weight) {
|
|
||||||
// 重量减少且目标值变化:重置延迟任务,3 秒无新变化后更新
|
|
||||||
cancelPendingWeightUpdate()
|
cancelPendingWeightUpdate()
|
||||||
pendingWeight = weight
|
pendingWeight = weight
|
||||||
pendingWeightRunnable = Runnable {
|
pendingWeightRunnable = Runnable {
|
||||||
@@ -239,10 +261,20 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
/**
|
/**
|
||||||
* 携带当前选中食材关闭页面
|
* 携带当前选中食材关闭页面
|
||||||
*/
|
*/
|
||||||
private fun finishWithResult() {
|
private fun finishWithResult(isManualCancel: Boolean) {
|
||||||
val selected = list.getOrNull(selectedPosition)
|
cancelPendingWeightUpdate()
|
||||||
|
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
selected?.let { putExtra(EXTRA_SELECTED_ITEM, it) }
|
putExtra(IS_MANUAL_CANCEL, isManualCancel)
|
||||||
|
if (isManualCancel.not()) {
|
||||||
|
list.getOrNull(selectedPosition)?.let {
|
||||||
|
// 兜底:若 useWeight 仍为空或0,使用当前实时重量
|
||||||
|
if ((it.useWeight ?: 0.0) <= 0.0) {
|
||||||
|
it.useWeight = currentWeight
|
||||||
|
}
|
||||||
|
putExtra(EXTRA_SELECTED_ITEM, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setResult(RESULT_OK, intent)
|
setResult(RESULT_OK, intent)
|
||||||
finish()
|
finish()
|
||||||
@@ -251,6 +283,6 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
cancelPendingWeightUpdate()
|
cancelPendingWeightUpdate()
|
||||||
// WeightUtil.removeWeightListener(TAG)
|
WeightUtil.removeWeightListener(TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ package com.shuwei.dish.match.ui
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import androidx.core.view.updateLayoutParams
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -18,8 +16,8 @@ import com.google.android.flexbox.FlexDirection
|
|||||||
import com.google.android.flexbox.FlexWrap
|
import com.google.android.flexbox.FlexWrap
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
import com.shuwei.dish.match.adapter.Scale18GridAdapter
|
import com.shuwei.dish.match.adapter.Seasoning18GridAdapter
|
||||||
import com.shuwei.dish.match.adapter.Scale22GridAdapter
|
import com.shuwei.dish.match.adapter.Seasoning22GridAdapter
|
||||||
import com.shuwei.dish.match.base.BaseActivity
|
import com.shuwei.dish.match.base.BaseActivity
|
||||||
import com.shuwei.dish.match.dialog.CommonDialog
|
import com.shuwei.dish.match.dialog.CommonDialog
|
||||||
import com.shuwei.dish.match.databinding.ActivityMasterScaleBinding
|
import com.shuwei.dish.match.databinding.ActivityMasterScaleBinding
|
||||||
@@ -27,9 +25,13 @@ import com.shuwei.dish.match.databinding.ListTypeScale1Binding
|
|||||||
import com.shuwei.dish.match.databinding.ListTypeScale18Binding
|
import com.shuwei.dish.match.databinding.ListTypeScale18Binding
|
||||||
import com.shuwei.dish.match.databinding.ListTypeScale2Binding
|
import com.shuwei.dish.match.databinding.ListTypeScale2Binding
|
||||||
import com.shuwei.dish.match.databinding.ListTypeScale22Binding
|
import com.shuwei.dish.match.databinding.ListTypeScale22Binding
|
||||||
|
import com.shuwei.dish.match.base.BaseApp
|
||||||
|
import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
|
||||||
import com.shuwei.dish.match.scale.ScaleData
|
import com.shuwei.dish.match.scale.ScaleData
|
||||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
||||||
import com.shuwei.dish.match.scale.ScaleServiceManager
|
import com.shuwei.dish.match.scale.ScaleServiceManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import com.shuwei.dish.match.utils.NetworkUtil
|
import com.shuwei.dish.match.utils.NetworkUtil
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
@@ -37,10 +39,10 @@ import com.shuwei.dish.match.utils.ext.toast
|
|||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private const val VIEW_TYPE_SCALE2 = 0
|
private const val VIEW_TYPE_SCALE2 = 0
|
||||||
private const val VIEW_TYPE_SCALE22 = 1
|
private const val VIEW_TYPE_SCALE22 = 1
|
||||||
private const val VIEW_TYPE_SCALE18 = 2
|
private const val VIEW_TYPE_SCALE18 = 2
|
||||||
private const val VIEW_TYPE_SCALE1 = 3
|
private const val VIEW_TYPE_SCALE1 = 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主设备秤数据总览页面
|
* 主设备秤数据总览页面
|
||||||
@@ -59,6 +61,9 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
private val groupList = mutableListOf<DeviceGroup>()
|
private val groupList = mutableListOf<DeviceGroup>()
|
||||||
private val adapter = DeviceGroupAdapter(groupList)
|
private val adapter = DeviceGroupAdapter(groupList)
|
||||||
|
|
||||||
|
/** key = "$deviceId#$address",value = 调料名称,用于注入 ScaleData.name */
|
||||||
|
private val slotNameMap = mutableMapOf<String, String>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMasterScaleBinding.inflate(layoutInflater)
|
binding = ActivityMasterScaleBinding.inflate(layoutInflater)
|
||||||
@@ -70,7 +75,26 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
binding.rvScaleList.itemAnimator = null
|
binding.rvScaleList.itemAnimator = null
|
||||||
binding.rvScaleList.adapter = adapter
|
binding.rvScaleList.adapter = adapter
|
||||||
|
|
||||||
observeScaleData()
|
loadSlotNames()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从 Room 加载槽位配置到 slotNameMap,加载完成后启动数据观测 */
|
||||||
|
private fun loadSlotNames() {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val slots = BaseApp.instance!!.database.seasoningSlotDao().queryAll()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
slots.forEach { slot ->
|
||||||
|
slotNameMap["${slot.deviceId}#${slot.address}"] = slot.goodsName
|
||||||
|
}
|
||||||
|
observeScaleData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用 slotNameMap 为 ScaleData 注入名称 */
|
||||||
|
private fun ScaleData.withSlotName(): ScaleData {
|
||||||
|
val name = slotNameMap["$deviceId#$address"]
|
||||||
|
return if (name != null) copy(name = name) else this
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeScaleData() {
|
private fun observeScaleData() {
|
||||||
@@ -83,24 +107,30 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
.groupBy { it.deviceId }
|
.groupBy { it.deviceId }
|
||||||
.entries
|
.entries
|
||||||
.sortedWith(compareBy { entry ->
|
.sortedWith(compareBy { entry ->
|
||||||
ScaleDeviceConfig.DEVICE_ORDER.indexOf(entry.key).let { if (it == -1) Int.MAX_VALUE else it }
|
ScaleDeviceConfig.DEVICE_ORDER.indexOf(entry.key)
|
||||||
|
.let { if (it == -1) Int.MAX_VALUE else it }
|
||||||
})
|
})
|
||||||
.map { (deviceId, scales) ->
|
.map { (deviceId, scales) ->
|
||||||
DeviceGroup(
|
DeviceGroup(
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
ip = if (deviceId == localId) localIp
|
ip = if (deviceId == localId) localIp
|
||||||
else scales.firstOrNull { it.ip.isNotEmpty() }?.ip ?: "",
|
else scales.firstOrNull { it.ip.isNotEmpty() }?.ip ?: "",
|
||||||
scales = scales.sortedBy { it.address }
|
scales = scales.map { it.withSlotName() }.sortedBy { it.address }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val structureChanged = groups.size != groupList.size ||
|
val structureChanged = groups.size != groupList.size ||
|
||||||
groups.indices.any { groups[it].deviceId != groupList[it].deviceId }
|
groups.indices.any { groups[it].deviceId != groupList[it].deviceId }
|
||||||
|
|
||||||
if (structureChanged) {
|
if (structureChanged) {
|
||||||
updateList(groups)
|
updateList(groups)
|
||||||
} else {
|
} else {
|
||||||
groups.forEach { group -> adapter.updateScalesForDevice(group.deviceId, group.scales) }
|
groups.forEach { group ->
|
||||||
|
adapter.updateScalesForDevice(
|
||||||
|
group.deviceId,
|
||||||
|
group.scales
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatusBar(scaleMap)
|
updateStatusBar(scaleMap)
|
||||||
@@ -142,8 +172,8 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
private inner class DeviceGroupAdapter(data: MutableList<DeviceGroup>) :
|
private inner class DeviceGroupAdapter(data: MutableList<DeviceGroup>) :
|
||||||
BaseMultiItemAdapter<DeviceGroup>(data) {
|
BaseMultiItemAdapter<DeviceGroup>(data) {
|
||||||
|
|
||||||
private val scale1VHMap = mutableMapOf<String, Scale1VH>()
|
private val scale1VHMap = mutableMapOf<String, Scale1VH>()
|
||||||
private val scale2VHMap = mutableMapOf<String, Scale2VH>()
|
private val scale2VHMap = mutableMapOf<String, Scale2VH>()
|
||||||
private val scale22VHMap = mutableMapOf<String, Scale22VH>()
|
private val scale22VHMap = mutableMapOf<String, Scale22VH>()
|
||||||
private val scale18VHMap = mutableMapOf<String, Scale18VH>()
|
private val scale18VHMap = mutableMapOf<String, Scale18VH>()
|
||||||
|
|
||||||
@@ -155,101 +185,171 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addItemType(VIEW_TYPE_SCALE2, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
addItemType(
|
||||||
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
VIEW_TYPE_SCALE2,
|
||||||
QuickViewHolder(ListTypeScale2Binding.inflate(LayoutInflater.from(context), parent, false).root)
|
object :
|
||||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||||
item ?: return
|
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
||||||
val b = ListTypeScale2Binding.bind(holder.itemView)
|
QuickViewHolder(
|
||||||
val vh = Scale2VH(b)
|
ListTypeScale2Binding.inflate(
|
||||||
scale2VHMap[item.deviceId] = vh
|
LayoutInflater.from(context),
|
||||||
vh.bind(item)
|
parent,
|
||||||
|
false
|
||||||
|
).root
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBind(
|
||||||
|
holder: QuickViewHolder,
|
||||||
|
position: Int,
|
||||||
|
item: DeviceGroup?
|
||||||
|
) {
|
||||||
|
item ?: return
|
||||||
|
val b = ListTypeScale2Binding.bind(holder.itemView)
|
||||||
|
val vh = Scale2VH(b)
|
||||||
|
scale2VHMap[item.deviceId] = vh
|
||||||
|
vh.bind(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
addItemType(
|
||||||
|
VIEW_TYPE_SCALE22,
|
||||||
|
object :
|
||||||
|
BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||||
|
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
||||||
|
QuickViewHolder(
|
||||||
|
ListTypeScale22Binding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
).root
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBind(
|
||||||
|
holder: QuickViewHolder,
|
||||||
|
position: Int,
|
||||||
|
item: DeviceGroup?
|
||||||
|
) {
|
||||||
|
item ?: return
|
||||||
|
val b = ListTypeScale22Binding.bind(holder.itemView)
|
||||||
|
// 每次 onBind 时注册/更新 VH 缓存,后续数据更新直接走 VH,不再触发 notify
|
||||||
|
val vh = Scale22VH(b)
|
||||||
|
scale22VHMap[item.deviceId] = vh
|
||||||
|
vh.bind(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
addItemType(
|
||||||
|
VIEW_TYPE_SCALE18,
|
||||||
|
object :
|
||||||
|
BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||||
|
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
||||||
|
QuickViewHolder(
|
||||||
|
ListTypeScale18Binding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
).root
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBind(
|
||||||
|
holder: QuickViewHolder,
|
||||||
|
position: Int,
|
||||||
|
item: DeviceGroup?
|
||||||
|
) {
|
||||||
|
item ?: return
|
||||||
|
val b = ListTypeScale18Binding.bind(holder.itemView)
|
||||||
|
val vh = Scale18VH(b)
|
||||||
|
scale18VHMap[item.deviceId] = vh
|
||||||
|
vh.bind(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
addItemType(
|
||||||
|
VIEW_TYPE_SCALE1,
|
||||||
|
object :
|
||||||
|
BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
||||||
|
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
||||||
|
QuickViewHolder(
|
||||||
|
ListTypeScale1Binding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
).root
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBind(
|
||||||
|
holder: QuickViewHolder,
|
||||||
|
position: Int,
|
||||||
|
item: DeviceGroup?
|
||||||
|
) {
|
||||||
|
item ?: return
|
||||||
|
val b = ListTypeScale1Binding.bind(holder.itemView)
|
||||||
|
val vh = Scale1VH(b)
|
||||||
|
scale1VHMap[item.deviceId] = vh
|
||||||
|
vh.bind(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onItemViewType { position, list ->
|
||||||
|
when (list.getOrNull(position)?.deviceId) {
|
||||||
|
ScaleDeviceConfig.DEVICE_ID_2 -> VIEW_TYPE_SCALE2
|
||||||
|
ScaleDeviceConfig.DEVICE_ID_22 -> VIEW_TYPE_SCALE22
|
||||||
|
ScaleDeviceConfig.DEVICE_ID_18 -> VIEW_TYPE_SCALE18
|
||||||
|
else -> VIEW_TYPE_SCALE1
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
addItemType(VIEW_TYPE_SCALE22, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
|
||||||
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
|
||||||
QuickViewHolder(ListTypeScale22Binding.inflate(LayoutInflater.from(context), parent, false).root)
|
|
||||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
|
||||||
item ?: return
|
|
||||||
val b = ListTypeScale22Binding.bind(holder.itemView)
|
|
||||||
// 每次 onBind 时注册/更新 VH 缓存,后续数据更新直接走 VH,不再触发 notify
|
|
||||||
val vh = Scale22VH(b)
|
|
||||||
scale22VHMap[item.deviceId] = vh
|
|
||||||
vh.bind(item)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
addItemType(VIEW_TYPE_SCALE18, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
|
||||||
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
|
||||||
QuickViewHolder(ListTypeScale18Binding.inflate(LayoutInflater.from(context), parent, false).root)
|
|
||||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
|
||||||
item ?: return
|
|
||||||
val b = ListTypeScale18Binding.bind(holder.itemView)
|
|
||||||
val vh = Scale18VH(b)
|
|
||||||
scale18VHMap[item.deviceId] = vh
|
|
||||||
vh.bind(item)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
addItemType(VIEW_TYPE_SCALE1, object : BaseMultiItemAdapter.OnMultiItemAdapterListener<DeviceGroup, QuickViewHolder> {
|
|
||||||
override fun onCreate(context: Context, parent: ViewGroup, viewType: Int) =
|
|
||||||
QuickViewHolder(ListTypeScale1Binding.inflate(LayoutInflater.from(context), parent, false).root)
|
|
||||||
override fun onBind(holder: QuickViewHolder, position: Int, item: DeviceGroup?) {
|
|
||||||
item ?: return
|
|
||||||
val b = ListTypeScale1Binding.bind(holder.itemView)
|
|
||||||
val vh = Scale1VH(b)
|
|
||||||
scale1VHMap[item.deviceId] = vh
|
|
||||||
vh.bind(item)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
onItemViewType { position, list -> when (list.getOrNull(position)?.deviceId) {
|
|
||||||
ScaleDeviceConfig.DEVICE_ID_2 -> VIEW_TYPE_SCALE2
|
|
||||||
ScaleDeviceConfig.DEVICE_ID_22 -> VIEW_TYPE_SCALE22
|
|
||||||
ScaleDeviceConfig.DEVICE_ID_18 -> VIEW_TYPE_SCALE18
|
|
||||||
else -> VIEW_TYPE_SCALE1
|
|
||||||
}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ViewHolder: 2个秤 ──────────────────────────────────────────────────
|
// ── ViewHolder: 2个秤 ──────────────────────────────────────────────────
|
||||||
|
|
||||||
private inner class Scale2VH(val b: ListTypeScale2Binding) : RecyclerView.ViewHolder(b.root) {
|
private inner class Scale2VH(val b: ListTypeScale2Binding) : RecyclerView.ViewHolder(b.root) {
|
||||||
private lateinit var cells: List<LinearLayout>
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(group: DeviceGroup) {
|
fun bind(group: DeviceGroup) {
|
||||||
b.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
b.tvDeviceIp.text = "IP:${group.ip.ifEmpty { "未知" }}"
|
||||||
b.tvDeviceLabel.text = "设备:${group.deviceId}"
|
b.tvDeviceLabel.text = "设备:${group.deviceId}"
|
||||||
|
// 两个格子显示秤地址行,隐藏重量/状态(showAddress 通过 visibility 控制)
|
||||||
if (!::cells.isInitialized) {
|
listOf(b.cell1, b.cell2).forEach { cell ->
|
||||||
val cellSize = (resources.displayMetrics.widthPixels - 4 * 4.dp - 10.dp) / 2
|
cell.tvAddress.visibility = android.view.View.VISIBLE
|
||||||
cells = group.scales.take(2).map { scale ->
|
cell.tvWeight.visibility = android.view.View.VISIBLE
|
||||||
makeScaleCell(scale, cellSize).also { cell ->
|
cell.tvState.visibility = android.view.View.VISIBLE
|
||||||
cell.setOnClickListener {
|
cell.root.setOnClickListener {
|
||||||
val s = cell.tag as? ScaleData ?: return@setOnClickListener
|
val s = cell.root.tag as? ScaleData ?: return@setOnClickListener
|
||||||
showTareDialog(deviceId = s.deviceId, ip = s.ip, address = s.address)
|
showTareDialog(deviceId = s.deviceId, ip = s.ip, address = s.address)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
b.llScaleContainer.removeAllViews()
|
|
||||||
cells.forEach { b.llScaleContainer.addView(it) }
|
|
||||||
}
|
}
|
||||||
updateScales(group.scales)
|
updateScales(group.scales)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateScales(scales: List<ScaleData>) {
|
fun updateScales(scales: List<ScaleData>) {
|
||||||
if (!::cells.isInitialized) return
|
bindCell(b.cell1, scales.getOrNull(0))
|
||||||
scales.take(2).forEachIndexed { i, scale ->
|
bindCell(b.cell2, scales.getOrNull(1))
|
||||||
cells.getOrNull(i)?.let { cell ->
|
}
|
||||||
cell.tag = scale
|
|
||||||
updateScaleCell(cell, scale)
|
@SuppressLint("SetTextI18n")
|
||||||
}
|
private fun bindCell(cell: ListItemScale22RowBinding, scale: ScaleData?) {
|
||||||
|
cell.root.tag = scale
|
||||||
|
cell.tvAddress.run {
|
||||||
|
text = scale?.let { "秤${it.address}" } ?: ""
|
||||||
|
textSize = 18f
|
||||||
}
|
}
|
||||||
|
cell.tvName.run {
|
||||||
|
text = if (scale?.address == 1) "净材" else "熟食"
|
||||||
|
textSize = 22f
|
||||||
|
}
|
||||||
|
cell.tvWeight.run {
|
||||||
|
text = scale?.let { "${it.weight}g" } ?: ""
|
||||||
|
textSize = 22f
|
||||||
|
}
|
||||||
|
cell.tvState.run {
|
||||||
|
text = stateStr(scale?.state)
|
||||||
|
textSize = 22f
|
||||||
|
}
|
||||||
|
cell.root.updateLayoutParams { height = 400.dp }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ViewHolder: 22个秤 ─────────────────────────────────────────────────
|
// ── ViewHolder: 22个秤 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
private inner class Scale22VH(val b: ListTypeScale22Binding) : RecyclerView.ViewHolder(b.root) {
|
private inner class Scale22VH(val b: ListTypeScale22Binding) : RecyclerView.ViewHolder(b.root) {
|
||||||
private var innerAdapter: Scale22GridAdapter? = null
|
private var innerAdapter: Seasoning22GridAdapter? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(group: DeviceGroup) {
|
fun bind(group: DeviceGroup) {
|
||||||
@@ -269,9 +369,18 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
alignItems = AlignItems.FLEX_START
|
alignItems = AlignItems.FLEX_START
|
||||||
}
|
}
|
||||||
b.rvScales.itemAnimator = null
|
b.rvScales.itemAnimator = null
|
||||||
innerAdapter = Scale22GridAdapter(largeSize, smallSize).apply {
|
innerAdapter = Seasoning22GridAdapter(
|
||||||
onItemClick = { scale ->
|
largeSize,
|
||||||
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
smallSize,
|
||||||
|
showNameOnly = false,
|
||||||
|
showAddress = true
|
||||||
|
).apply {
|
||||||
|
onItemClick = { scale, _ ->
|
||||||
|
showTareDialog(
|
||||||
|
deviceId = scale.deviceId,
|
||||||
|
ip = scale.ip,
|
||||||
|
address = scale.address
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.rvScales.adapter = innerAdapter
|
b.rvScales.adapter = innerAdapter
|
||||||
@@ -288,7 +397,7 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
// ── ViewHolder: 18个秤 ─────────────────────────────────────────────────
|
// ── ViewHolder: 18个秤 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
private inner class Scale18VH(val b: ListTypeScale18Binding) : RecyclerView.ViewHolder(b.root) {
|
private inner class Scale18VH(val b: ListTypeScale18Binding) : RecyclerView.ViewHolder(b.root) {
|
||||||
private var innerAdapter: Scale18GridAdapter? = null
|
private var innerAdapter: Seasoning18GridAdapter? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(group: DeviceGroup) {
|
fun bind(group: DeviceGroup) {
|
||||||
@@ -303,11 +412,16 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
b.rvScales.layoutManager = gridLayoutManager
|
b.rvScales.layoutManager = gridLayoutManager
|
||||||
b.rvScales.itemAnimator = null
|
b.rvScales.itemAnimator = null
|
||||||
innerAdapter = Scale18GridAdapter().apply {
|
innerAdapter =
|
||||||
onItemClick = { scale ->
|
Seasoning18GridAdapter(showNameOnly = false, showAddress = true).apply {
|
||||||
showTareDialog(deviceId = scale.deviceId, ip = scale.ip, address = scale.address)
|
onItemClick = { scale, _ ->
|
||||||
|
showTareDialog(
|
||||||
|
deviceId = scale.deviceId,
|
||||||
|
ip = scale.ip,
|
||||||
|
address = scale.address
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
b.rvScales.adapter = innerAdapter
|
b.rvScales.adapter = innerAdapter
|
||||||
}
|
}
|
||||||
updateScales(group.scales)
|
updateScales(group.scales)
|
||||||
@@ -349,53 +463,13 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
// ── 工具方法 ───────────────────────────────────────────────────────────
|
// ── 工具方法 ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private fun stateStr(state: Int?) = when (state) {
|
private fun stateStr(state: Int?) = when (state) {
|
||||||
WeightUtil.STATE_STABLE -> "稳定"
|
WeightUtil.STATE_STABLE -> "稳定"
|
||||||
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
||||||
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
||||||
null -> ""
|
null -> ""
|
||||||
else -> "$state"
|
else -> "$state"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建正方形秤格子(用于 Scale2VH 初始化) */
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
private fun makeScaleCell(scale: ScaleData, cellSize: Int): LinearLayout {
|
|
||||||
val margin = 2.dp
|
|
||||||
return LinearLayout(this).apply {
|
|
||||||
orientation = LinearLayout.VERTICAL
|
|
||||||
gravity = Gravity.CENTER
|
|
||||||
layoutParams = LinearLayout.LayoutParams(0, cellSize - margin * 2, 1f)
|
|
||||||
.also { it.setMargins(margin, margin, margin, margin) }
|
|
||||||
setPadding(4.dp, 2.dp, 4.dp, 2.dp)
|
|
||||||
setBackgroundResource(R.drawable.shape_scale_cell)
|
|
||||||
val baseSize = ((cellSize - margin * 2) / resources.displayMetrics.density).toInt()
|
|
||||||
addView(makeCellText("秤${scale.address}", (baseSize * 0.16f).coerceIn(9f, 14f).toInt()))
|
|
||||||
addView(makeCellText("${scale.weight}g", (baseSize * 0.22f).coerceIn(12f, 20f).toInt(), bold = true))
|
|
||||||
addView(makeCellText(stateStr(scale.state), (baseSize * 0.14f).coerceIn(8f, 12f).toInt()))
|
|
||||||
tag = scale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 仅更新格子内文字,不重建 view(用于 Scale2VH 数据刷新) */
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
private fun updateScaleCell(cell: LinearLayout, scale: ScaleData) {
|
|
||||||
(cell.getChildAt(0) as? TextView)?.text = "秤${scale.address}"
|
|
||||||
(cell.getChildAt(1) as? TextView)?.text = "${scale.weight}g"
|
|
||||||
(cell.getChildAt(2) as? TextView)?.text = stateStr(scale.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeCellText(text: String, spSize: Int, bold: Boolean = false): TextView =
|
|
||||||
TextView(this).apply {
|
|
||||||
this.text = text
|
|
||||||
textSize = spSize.toFloat()
|
|
||||||
if (bold) setTypeface(null, android.graphics.Typeface.BOLD)
|
|
||||||
setTextColor(if (bold) getColor(R.color.home_title) else getColor(R.color.home_sub_title))
|
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
gravity = Gravity.CENTER
|
|
||||||
includeFontPadding = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 弹出清零确认对话框
|
* 弹出清零确认对话框
|
||||||
* @param deviceId 目标设备 ID
|
* @param deviceId 目标设备 ID
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import com.shuwei.dish.match.base.BaseActivity
|
|||||||
import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding
|
import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding
|
||||||
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
||||||
import com.shuwei.dish.match.dialog.CommonDialog
|
import com.shuwei.dish.match.dialog.CommonDialog
|
||||||
import com.shuwei.dish.match.dialog.FoodSearchDialog
|
|
||||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
import com.shuwei.dish.match.entity.FoodRecord
|
||||||
@@ -32,7 +31,6 @@ import com.shuwei.dish.match.utils.WeightUtil
|
|||||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||||
import com.shuwei.dish.match.utils.ext.gone
|
import com.shuwei.dish.match.utils.ext.gone
|
||||||
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
|
|
||||||
import com.shuwei.dish.match.utils.ext.startActivity
|
import com.shuwei.dish.match.utils.ext.startActivity
|
||||||
import com.shuwei.dish.match.utils.ext.toJsonString
|
import com.shuwei.dish.match.utils.ext.toJsonString
|
||||||
import com.shuwei.dish.match.utils.ext.toast
|
import com.shuwei.dish.match.utils.ext.toast
|
||||||
@@ -53,7 +51,7 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
const val FOOD_NAME = "foodName"
|
const val FOOD_NAME = "foodName"
|
||||||
const val PAGE_FROM = "pageFrom"
|
const val PAGE_FROM = "pageFrom"
|
||||||
const val HOME = "home"
|
const val HOME = "home"
|
||||||
const val WEIGHT_CHANGE_VALUE = 5
|
const val WEIGHT_CHANGE_VALUE = 15
|
||||||
const val WEIGHT_RECOGNIZE_VALUE = 10
|
const val WEIGHT_RECOGNIZE_VALUE = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,10 +171,13 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val isTakingPhoto = AtomicBoolean(false)
|
private val isTakingPhoto = AtomicBoolean(false)
|
||||||
private var isRecognizeOpen = false
|
// 识别成功并查询到数据打开识别页面
|
||||||
|
private var showRecognizePage = false
|
||||||
|
|
||||||
// 页面可见时允许识别,onPause 后禁止,onResume 后恢复
|
// 页面可见时允许识别,onPause 后禁止,onResume 后恢复
|
||||||
private var isRecognizeEnabled = true
|
private var pageVisible = true
|
||||||
|
// 手动取消标识
|
||||||
|
private var manualCancelFlag = false
|
||||||
private var lastPhotoUri: Uri? = null
|
private var lastPhotoUri: Uri? = null
|
||||||
private var lastWeight = 0.0
|
private var lastWeight = 0.0
|
||||||
private var currentWeight = 0.0
|
private var currentWeight = 0.0
|
||||||
@@ -188,11 +189,14 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
private fun recognizeFood(weight: Double) {
|
private fun recognizeFood(weight: Double) {
|
||||||
this.currentWeight = weight
|
this.currentWeight = weight
|
||||||
if (weight < WEIGHT_RECOGNIZE_VALUE) {
|
if (weight < WEIGHT_RECOGNIZE_VALUE) {
|
||||||
isRecognizeOpen = false
|
showRecognizePage = false
|
||||||
|
manualCancelFlag = false
|
||||||
FoodRecognizeActivity.close()
|
FoodRecognizeActivity.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!isRecognizeEnabled || isTakingPhoto.get() || isRecognizeOpen || abs(lastWeight - weight) <= WEIGHT_CHANGE_VALUE) return
|
if (!pageVisible || manualCancelFlag || isTakingPhoto.get() || showRecognizePage || abs(lastWeight - weight) <= WEIGHT_CHANGE_VALUE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
isTakingPhoto.set(true)
|
isTakingPhoto.set(true)
|
||||||
cameraUtils.takePhoto(
|
cameraUtils.takePhoto(
|
||||||
succCallback = cameraSuccessCallback,
|
succCallback = cameraSuccessCallback,
|
||||||
@@ -205,14 +209,14 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
* - list 中不含该 goodsId:直接新增,isNewDishType = true
|
* - list 中不含该 goodsId:直接新增,isNewDishType = true
|
||||||
* - list 中已含该 goodsId:累加 useWeight
|
* - list 中已含该 goodsId:累加 useWeight
|
||||||
*/
|
*/
|
||||||
private val foodSelectCallback: (CookFoodGoodsEntity) -> Unit = { item ->
|
private val foodSelectCallback: (CookFoodGoodsEntity) -> Unit = { entity ->
|
||||||
val existing = list.firstOrNull { it.goodsId == item.goodsId }
|
val existing = list.firstOrNull { it.goodsId == entity.goodsId }
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.useWeight = (existing.useWeight ?: 0.0) + (item.useWeight ?: 0.0)
|
existing.useWeight = (existing.useWeight ?: 0.0) + (entity.useWeight ?: 0.0)
|
||||||
existing.isSetFinished = true
|
existing.isSetFinished = true
|
||||||
dishPartAdapter.notifyItemChanged(list.indexOf(existing))
|
dishPartAdapter.notifyItemChanged(list.indexOf(existing))
|
||||||
} else {
|
} else {
|
||||||
list.add(item.also {
|
list.add(entity.also {
|
||||||
it.isNewDishType = true
|
it.isNewDishType = true
|
||||||
it.isSetFinished = true
|
it.isSetFinished = true
|
||||||
})
|
})
|
||||||
@@ -363,14 +367,14 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
isRecognizeEnabled = true
|
pageVisible = true
|
||||||
isRecognizeOpen = false
|
showRecognizePage = false
|
||||||
cameraUtils.bind()
|
cameraUtils.bind()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
isRecognizeEnabled = false
|
pageVisible = false
|
||||||
cameraUtils.unbind()
|
cameraUtils.unbind()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,10 +477,19 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
//调用接口成功,返回食材列表设置isTakingPhoto.set(false),暂时写死数据
|
//调用接口成功,返回食材列表设置isTakingPhoto.set(false),暂时写死数据
|
||||||
val foodList = mutableListOf<CookFoodGoodsEntity>()
|
val foodList = mutableListOf<CookFoodGoodsEntity>()
|
||||||
list.forEachIndexed { index, food ->
|
list.forEachIndexed { index, food ->
|
||||||
val foodName = food.name.split("WP").first()
|
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(
|
foodList.add(
|
||||||
CookFoodGoodsEntity(
|
CookFoodGoodsEntity(
|
||||||
goodsId = "${food.id}",
|
goodsId = foodId,
|
||||||
goodsName = foodName,
|
goodsName = foodName,
|
||||||
foodId = index.toString()
|
foodId = index.toString()
|
||||||
).also {
|
).also {
|
||||||
@@ -489,9 +502,18 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
activity = this,
|
activity = this,
|
||||||
imageUri = lastPhotoUri?.toString(),
|
imageUri = lastPhotoUri?.toString(),
|
||||||
goodsList = ArrayList(foodList),
|
goodsList = ArrayList(foodList),
|
||||||
onResult = { item -> item?.let { foodSelectCallback(it) } }
|
finishCallback = { isManualCancel, entity ->
|
||||||
|
Log.d(TAG, "queryFood: isManualCancel=$isManualCancel, entity=${entity.toJsonString()}")
|
||||||
|
manualCancelFlag = isManualCancel
|
||||||
|
if (isManualCancel) {
|
||||||
|
return@start
|
||||||
|
}
|
||||||
|
entity?.let {
|
||||||
|
foodSelectCallback(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
isRecognizeOpen = true
|
showRecognizePage = true
|
||||||
isTakingPhoto.set(false)
|
isTakingPhoto.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
package com.shuwei.dish.match.ui
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.google.android.flexbox.AlignItems
|
|
||||||
import com.google.android.flexbox.FlexDirection
|
|
||||||
import com.google.android.flexbox.FlexWrap
|
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
|
||||||
import com.shuwei.dish.match.base.BaseActivity
|
|
||||||
import com.shuwei.dish.match.databinding.ActivityScale22GridBinding
|
|
||||||
import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
|
|
||||||
import com.shuwei.dish.match.scale.ScaleData
|
|
||||||
import com.shuwei.dish.match.scale.ScaleServiceManager
|
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/** 22格设备的 deviceId */
|
|
||||||
private const val DEVICE_ID_22 = "a46fa55c-113c-3511-bb1f-41e5eff77c4b"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 列优先顺序:大列1(20,22) → 大列2(19,21) → 小列1~6
|
|
||||||
* FlexboxLayoutManager COLUMN 方向按列填充,顺序天然对应
|
|
||||||
*/
|
|
||||||
private val SCALE_ORDER_22 = listOf(20, 22, 19, 21, 6, 12, 18, 5, 11, 17, 4, 10, 16, 3, 9, 15, 2, 8, 14, 1, 7, 13)
|
|
||||||
|
|
||||||
/** 前4个 item(大列1+大列2,各2行)为大格子 */
|
|
||||||
private fun isLargeCell(position: Int) = position < 4
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 22格秤混合网格布局页
|
|
||||||
* FlexboxLayoutManager(COLUMN) 竖向排列:
|
|
||||||
* - 大格子 flexBasisPercent=1.5/9,每列2个,自动换列
|
|
||||||
* - 小格子 flexBasisPercent=1.0/9,每列3个,自动换列
|
|
||||||
*/
|
|
||||||
class Scale22GridActivity : BaseActivity() {
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityScale22GridBinding
|
|
||||||
private val scaleList = mutableListOf<ScaleData?>()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
binding = ActivityScale22GridBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
setHeaderBackground()
|
|
||||||
|
|
||||||
binding.ivBack.setOnClickListener { finish() }
|
|
||||||
|
|
||||||
// 格子尺寸计算:
|
|
||||||
// 屏幕宽 - 8列×左右margin(2dp×2) = 可用宽,共9份(大1.5+小1.0×6)
|
|
||||||
val screenWidth = resources.displayMetrics.widthPixels
|
|
||||||
val usable = screenWidth - 8 * 4.dp - 4.dp // 减去8列格子margin + RecyclerView左右margin各2dp
|
|
||||||
val smallSize = usable / 9
|
|
||||||
val largeSize = (usable - smallSize * 6) / 2 // 剩余宽度平分给2列大格子
|
|
||||||
// 高度以小格子3行为准(3行×格子高 + 3行×上下margin各2dp)
|
|
||||||
val rvHeight = smallSize * 3 + 3 * 4.dp
|
|
||||||
binding.rvScale22.layoutParams = binding.rvScale22.layoutParams.also {
|
|
||||||
it.height = rvHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.rvScale22.layoutManager = FlexboxLayoutManager(this).apply {
|
|
||||||
flexDirection = FlexDirection.COLUMN
|
|
||||||
flexWrap = FlexWrap.WRAP
|
|
||||||
alignItems = AlignItems.FLEX_START
|
|
||||||
}
|
|
||||||
binding.rvScale22.itemAnimator = null
|
|
||||||
binding.rvScale22.adapter = Scale22Adapter(scaleList, largeSize, smallSize)
|
|
||||||
|
|
||||||
observeScaleData()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeScaleData() {
|
|
||||||
val flow = ScaleServiceManager.allScales ?: return
|
|
||||||
lifecycleScope.launch {
|
|
||||||
flow.collectLatest { scaleMap ->
|
|
||||||
val scaleByAddress = scaleMap.values
|
|
||||||
.filter { it.deviceId == DEVICE_ID_22 }
|
|
||||||
.associateBy { it.address }
|
|
||||||
val newList = SCALE_ORDER_22.map { scaleByAddress[it] }
|
|
||||||
if (scaleList.isEmpty()) {
|
|
||||||
scaleList.addAll(newList)
|
|
||||||
binding.rvScale22.adapter?.notifyItemRangeInserted(0, scaleList.size)
|
|
||||||
} else {
|
|
||||||
newList.forEachIndexed { i, scale ->
|
|
||||||
if (scaleList[i] != scale) {
|
|
||||||
scaleList[i] = scale
|
|
||||||
binding.rvScale22.adapter?.notifyItemChanged(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class Scale22Adapter(
|
|
||||||
private val data: List<ScaleData?>,
|
|
||||||
private val largeSize: Int, // 大格子宽高(px)
|
|
||||||
private val smallSize: Int // 小格子宽高(px)
|
|
||||||
) : RecyclerView.Adapter<Scale22Adapter.VH>() {
|
|
||||||
|
|
||||||
inner class VH(val b: ListItemScale22RowBinding) : RecyclerView.ViewHolder(b.root)
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = if (isLargeCell(position)) 1 else 0
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
|
||||||
val b = ListItemScale22RowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
|
||||||
val cellSize = if (viewType == 1) largeSize else smallSize
|
|
||||||
// 固定宽高实现正方形,FlexboxLayoutManager COLUMN 方向按高度装满后自动换列
|
|
||||||
(b.root.layoutParams as? FlexboxLayoutManager.LayoutParams)?.apply {
|
|
||||||
width = cellSize
|
|
||||||
height = cellSize
|
|
||||||
setMargins(2.dp, 2.dp, 2.dp, 2.dp)
|
|
||||||
}
|
|
||||||
val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt()
|
|
||||||
b.tvAddr.textSize = (baseSp * 0.16f).coerceIn(9f, 14f)
|
|
||||||
b.tvWeight.textSize = (baseSp * 0.22f).coerceIn(12f, 20f)
|
|
||||||
b.tvState.textSize = (baseSp * 0.14f).coerceIn(8f, 12f)
|
|
||||||
return VH(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = data.size
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
|
||||||
val scale = data[position]
|
|
||||||
holder.b.tvAddr.text = scale?.let { "秤${it.address}" } ?: ""
|
|
||||||
holder.b.tvWeight.text = scale?.let { "${it.weight}g" } ?: ""
|
|
||||||
holder.b.tvState.text = when (scale?.state) {
|
|
||||||
WeightUtil.STATE_STABLE -> "稳定"
|
|
||||||
WeightUtil.STATE_UNSTABLE -> "不稳定"
|
|
||||||
WeightUtil.STATE_OVER_WEIGHT -> "超量"
|
|
||||||
null -> ""
|
|
||||||
else -> "${scale.state}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,23 +9,29 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.flexbox.AlignItems
|
import com.google.android.flexbox.AlignItems
|
||||||
import com.google.android.flexbox.FlexDirection
|
import com.google.android.flexbox.FlexDirection
|
||||||
import com.google.android.flexbox.FlexWrap
|
import com.google.android.flexbox.FlexWrap
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import com.shuwei.dish.match.adapter.Scale18GridAdapter
|
import com.shuwei.dish.match.adapter.Seasoning18GridAdapter
|
||||||
import com.shuwei.dish.match.adapter.Scale22GridAdapter
|
import com.shuwei.dish.match.adapter.Seasoning22GridAdapter
|
||||||
import com.shuwei.dish.match.adapter.ScaleRowAdapter
|
import com.shuwei.dish.match.adapter.ScaleRowAdapter
|
||||||
import com.shuwei.dish.match.base.BaseActivity
|
import com.shuwei.dish.match.base.BaseActivity
|
||||||
|
import com.shuwei.dish.match.base.BaseApp
|
||||||
import com.shuwei.dish.match.base.GlobalData
|
import com.shuwei.dish.match.base.GlobalData
|
||||||
import com.shuwei.dish.match.databinding.ActivitySlaveBinding
|
import com.shuwei.dish.match.databinding.ActivitySlaveBinding
|
||||||
import com.shuwei.dish.match.dialog.CommonDialog
|
import com.shuwei.dish.match.dialog.CommonDialog
|
||||||
|
import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
||||||
import com.shuwei.dish.match.scale.ScaleData
|
import com.shuwei.dish.match.scale.ScaleData
|
||||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
||||||
import com.shuwei.dish.match.scale.ScaleServiceManager
|
import com.shuwei.dish.match.scale.ScaleServiceManager
|
||||||
import com.shuwei.dish.match.utils.NetworkUtil
|
import com.shuwei.dish.match.utils.NetworkUtil
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 子设备专属页面(小屏幕设备使用)
|
* 子设备专属页面(小屏幕设备使用)
|
||||||
@@ -40,15 +46,17 @@ class SlaveActivity : BaseActivity() {
|
|||||||
private val linearAdapter = ScaleRowAdapter()
|
private val linearAdapter = ScaleRowAdapter()
|
||||||
|
|
||||||
/** 22个秤网格 adapter */
|
/** 22个秤网格 adapter */
|
||||||
private var scale22Adapter: Scale22GridAdapter? = null
|
private var scale22Adapter: Seasoning22GridAdapter? = null
|
||||||
/** 18个秤网格 adapter */
|
/** 18个秤网格 adapter */
|
||||||
private var scale18Adapter: Scale18GridAdapter? = null
|
private var scale18Adapter: Seasoning18GridAdapter? = null
|
||||||
|
|
||||||
/** 已连接的主设备数量(有连接即代表主设备在线) */
|
/** 已连接的主设备数量(有连接即代表主设备在线) */
|
||||||
private var masterConnected = false
|
private var masterConnected = false
|
||||||
|
|
||||||
/** 各秤地址的上一次重量快照,用于计算 delta */
|
/** 各秤地址的上一次重量快照,用于计算 delta */
|
||||||
private val weightSnapshot = mutableMapOf<Int, Double>()
|
private val weightSnapshot = mutableMapOf<Int, Double>()
|
||||||
|
/** key = address,value = 调料名称,用于注入 ScaleData.name,避免重量更新时覆盖名称 */
|
||||||
|
private val slotNameMap = mutableMapOf<Int, String>()
|
||||||
/** 主线程 Handler,用于延迟清除高亮 */
|
/** 主线程 Handler,用于延迟清除高亮 */
|
||||||
private val mainHandler = Handler(Looper.getMainLooper())
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
/** 当前高亮清除任务,新高亮触发时取消旧任务 */
|
/** 当前高亮清除任务,新高亮触发时取消旧任务 */
|
||||||
@@ -65,8 +73,10 @@ class SlaveActivity : BaseActivity() {
|
|||||||
|
|
||||||
setupScaleList()
|
setupScaleList()
|
||||||
updateMasterStatus(ScaleServiceManager.isMasterConnected)
|
updateMasterStatus(ScaleServiceManager.isMasterConnected)
|
||||||
|
loadSlotsFromDb()
|
||||||
listenLocalScales()
|
listenLocalScales()
|
||||||
listenMasterConnection()
|
listenMasterConnection()
|
||||||
|
listenSeasoningConfig()
|
||||||
addBackKeyListener()
|
addBackKeyListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,8 +105,8 @@ class SlaveActivity : BaseActivity() {
|
|||||||
flexWrap = FlexWrap.WRAP
|
flexWrap = FlexWrap.WRAP
|
||||||
alignItems = AlignItems.FLEX_START
|
alignItems = AlignItems.FLEX_START
|
||||||
}
|
}
|
||||||
scale22Adapter = Scale22GridAdapter(largeSize, smallSize).also {
|
scale22Adapter = Seasoning22GridAdapter(largeSize, smallSize, showNameOnly = false).also {
|
||||||
it.onItemClick = { scale -> showTareDialog(scale.address) }
|
it.onItemClick = { scale, _ -> showTareDialog(scale.address) }
|
||||||
}
|
}
|
||||||
binding.rvScaleList.adapter = scale22Adapter
|
binding.rvScaleList.adapter = scale22Adapter
|
||||||
}
|
}
|
||||||
@@ -107,8 +117,8 @@ class SlaveActivity : BaseActivity() {
|
|||||||
override fun getSpanSize(position: Int) = 1
|
override fun getSpanSize(position: Int) = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scale18Adapter = Scale18GridAdapter().also {
|
scale18Adapter = Seasoning18GridAdapter(showNameOnly = false).also {
|
||||||
it.onItemClick = { scale -> showTareDialog(scale.address) }
|
it.onItemClick = { scale, _ -> showTareDialog(scale.address) }
|
||||||
}
|
}
|
||||||
binding.rvScaleList.adapter = scale18Adapter
|
binding.rvScaleList.adapter = scale18Adapter
|
||||||
}
|
}
|
||||||
@@ -140,6 +150,64 @@ class SlaveActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从本地 Room 加载本机设备的槽位配置并更新 adapter
|
||||||
|
* 主设备未开机时也能正常展示上次配置
|
||||||
|
*/
|
||||||
|
private fun loadSlotsFromDb() {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val dao = BaseApp.instance!!.database.seasoningSlotDao()
|
||||||
|
val slots = dao.queryByDeviceId(GlobalData.deviceId)
|
||||||
|
withContext(Dispatchers.Main) { applySlots(slots) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听主设备下发的调料配置同步事件
|
||||||
|
* 收到后更新 adapter 并写入本地 Room
|
||||||
|
*/
|
||||||
|
private fun listenSeasoningConfig() {
|
||||||
|
ScaleServiceManager.onSeasoningConfig = onSeasoningConfig@{ event ->
|
||||||
|
val mySlots = event.slots
|
||||||
|
?.filter { it.deviceId == GlobalData.deviceId }
|
||||||
|
?.map { SeasoningSlotEntity(it.deviceId, it.address, it.goodsId, it.goodsName) }
|
||||||
|
?: return@onSeasoningConfig
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
BaseApp.instance!!.database.seasoningSlotDao().upsertAll(mySlots)
|
||||||
|
withContext(Dispatchers.Main) { applySlots(mySlots) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将槽位配置应用到对应 adapter(按地址匹配格子位置)
|
||||||
|
* 同时更新 slotNameMap,确保后续重量回调构造 ScaleData 时携带正确名称
|
||||||
|
*/
|
||||||
|
private fun applySlots(slots: List<SeasoningSlotEntity>) {
|
||||||
|
slots.forEach { slot ->
|
||||||
|
slotNameMap[slot.address] = slot.goodsName
|
||||||
|
when {
|
||||||
|
scale22Adapter != null -> {
|
||||||
|
val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address)
|
||||||
|
if (pos >= 0) scale22Adapter!!.updateItemName(pos, slot.goodsName)
|
||||||
|
}
|
||||||
|
scale18Adapter != null -> {
|
||||||
|
val pos = ScaleDeviceConfig.SCALE_ORDER_18.indexOf(slot.address)
|
||||||
|
if (pos >= 0) scale18Adapter!!.updateItemName(pos, slot.goodsName)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// 线性列表模式(子设备1):刷新已显示条目的名称
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听本机 WeightUtil 回调,实时刷新秤列表
|
* 监听本机 WeightUtil 回调,实时刷新秤列表
|
||||||
* 22/18个秤走网格 adapter,其余走线性列表
|
* 22/18个秤走网格 adapter,其余走线性列表
|
||||||
@@ -162,19 +230,19 @@ class SlaveActivity : BaseActivity() {
|
|||||||
// 用最新数据更新缓存后整体刷新网格
|
// 用最新数据更新缓存后整体刷新网格
|
||||||
val existing = scale22Adapter!!.items.toMutableList()
|
val existing = scale22Adapter!!.items.toMutableList()
|
||||||
val idx = existing.indexOfFirst { it.address == address }
|
val idx = existing.indexOfFirst { it.address == address }
|
||||||
val newData = ScaleData(GlobalData.deviceId, address, weight, state, System.currentTimeMillis())
|
val newData = ScaleData(GlobalData.deviceId, address, weight, state, System.currentTimeMillis(), name = slotNameMap[address])
|
||||||
if (idx >= 0) existing[idx] = newData else existing.add(newData)
|
if (idx >= 0) existing[idx] = newData else existing.add(newData)
|
||||||
scale22Adapter!!.updateByAddress(existing)
|
scale22Adapter!!.updateByAddress(existing)
|
||||||
}
|
}
|
||||||
scale18Adapter != null -> {
|
scale18Adapter != null -> {
|
||||||
val existing = scale18Adapter!!.items.toMutableList()
|
val existing = scale18Adapter!!.items.toMutableList()
|
||||||
val idx = existing.indexOfFirst { it.address == address }
|
val idx = existing.indexOfFirst { it.address == address }
|
||||||
val newData = ScaleData(GlobalData.deviceId, address, weight, state, System.currentTimeMillis())
|
val newData = ScaleData(GlobalData.deviceId, address, weight, state, System.currentTimeMillis(), name = slotNameMap[address])
|
||||||
if (idx >= 0) existing[idx] = newData else existing.add(newData)
|
if (idx >= 0) existing[idx] = newData else existing.add(newData)
|
||||||
scale18Adapter!!.updateByAddress(existing)
|
scale18Adapter!!.updateByAddress(existing)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val item = ScaleRowAdapter.ScaleItem(address, weight, state)
|
val item = ScaleRowAdapter.ScaleItem(address, weight, state, slotNameMap[address] ?: "")
|
||||||
val list = linearAdapter.items.toMutableList()
|
val list = linearAdapter.items.toMutableList()
|
||||||
val idx = list.indexOfFirst { it.address == address }
|
val idx = list.indexOfFirst { it.address == address }
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
@@ -217,6 +285,7 @@ class SlaveActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
ScaleServiceManager.onMasterConnectionChanged = null
|
ScaleServiceManager.onMasterConnectionChanged = null
|
||||||
|
ScaleServiceManager.onSeasoningConfig = null
|
||||||
WeightUtil.removeWeightListener(TAG)
|
WeightUtil.removeWeightListener(TAG)
|
||||||
clearHighlightRunnable?.let { mainHandler.removeCallbacks(it) }
|
clearHighlightRunnable?.let { mainHandler.removeCallbacks(it) }
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|||||||
@@ -3,25 +3,22 @@ package com.shuwei.dish.match.ui
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.SparseArray
|
|
||||||
import android.util.SparseIntArray
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.core.util.forEach
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
import com.shuwei.dish.match.adapter.TextCellAdapter
|
import com.shuwei.dish.match.adapter.SeasoningWeightAdapter
|
||||||
import com.shuwei.dish.match.base.BaseActivity
|
import com.shuwei.dish.match.base.BaseActivity
|
||||||
import com.shuwei.dish.match.base.BaseApp
|
import com.shuwei.dish.match.base.BaseApp
|
||||||
import com.shuwei.dish.match.databinding.ActivitySubmitFoodBinding
|
import com.shuwei.dish.match.databinding.ActivitySubmitFoodBinding
|
||||||
import com.shuwei.dish.match.db.AppRepository
|
import com.shuwei.dish.match.db.AppRepository
|
||||||
|
|
||||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
import com.shuwei.dish.match.entity.FoodRecord
|
||||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
|
||||||
import com.shuwei.dish.match.net.NetViewModel
|
import com.shuwei.dish.match.net.NetViewModel
|
||||||
|
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
||||||
|
import com.shuwei.dish.match.scale.ScaleServiceManager
|
||||||
import com.shuwei.dish.match.utils.AddressUtil
|
import com.shuwei.dish.match.utils.AddressUtil
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||||
@@ -34,7 +31,10 @@ import com.shuwei.dish.match.utils.ext.toast
|
|||||||
import com.shuwei.dish.match.utils.ext.visible
|
import com.shuwei.dish.match.utils.ext.visible
|
||||||
import com.shuwei.dish.match.viewmodel.AppViewModel
|
import com.shuwei.dish.match.viewmodel.AppViewModel
|
||||||
import com.shuwei.dish.match.viewmodel.factory.AppFactory
|
import com.shuwei.dish.match.viewmodel.factory.AppFactory
|
||||||
import kotlin.getValue
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@SuppressLint("UseSparseArrays")
|
@SuppressLint("UseSparseArrays")
|
||||||
class SubmitFoodActivity : BaseActivity() {
|
class SubmitFoodActivity : BaseActivity() {
|
||||||
@@ -50,8 +50,6 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
private var food: FoodRecord? = null
|
private var food: FoodRecord? = null
|
||||||
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
|
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
|
||||||
|
|
||||||
private var isCooking = false
|
|
||||||
|
|
||||||
private val cookFoodEntity by lazy {
|
private val cookFoodEntity by lazy {
|
||||||
CookFoodEntity().apply {
|
CookFoodEntity().apply {
|
||||||
food?.let {
|
food?.let {
|
||||||
@@ -64,6 +62,27 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 调料列表 adapter */
|
||||||
|
private val seasoningAdapter = SeasoningWeightAdapter()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 各秤的初始重量快照,key = "deviceId#address"
|
||||||
|
* 首次收到稳定数据时记录,之后用于计算用量差值
|
||||||
|
*/
|
||||||
|
private val baselineMap = mutableMapOf<String, Double>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 槽位配置:key = "deviceId#address",value = goodsId to goodsName
|
||||||
|
* 从 Room dm_seasoning_slot 加载
|
||||||
|
*/
|
||||||
|
private val slotMap = mutableMapOf<String, Pair<String, String>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 各秤原始用量,key = "deviceId#address",value = Triple(goodsId, goodsName, useWeight)
|
||||||
|
* 每次秤数据变化后更新,再按 goodsName 聚合推给 adapter
|
||||||
|
*/
|
||||||
|
private val rawWeightMap = mutableMapOf<String, Triple<String, String, Double>>()
|
||||||
|
|
||||||
@Suppress("unchecked_cast", "DEPRECATION")
|
@Suppress("unchecked_cast", "DEPRECATION")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -74,14 +93,8 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
intent.extras?.apply {
|
intent.extras?.apply {
|
||||||
food = getSerializable(FOOD_ITEM) as FoodRecord?
|
food = getSerializable(FOOD_ITEM) as FoodRecord?
|
||||||
Log.d(TAG, "onCreate: cookMode=${food?.cookMode}")
|
Log.d(TAG, "onCreate: cookMode=${food?.cookMode}")
|
||||||
isCooking = food?.isCooking ?: false
|
// 非烹饪中,从菜品主辅材配置页面带入数据
|
||||||
if (isCooking) {
|
goodsList = getSerializable(GOODS_LIST) as MutableList<CookFoodGoodsEntity>?
|
||||||
//烹饪中,从本地查询数据
|
|
||||||
loadSeasoningFromLocal()
|
|
||||||
} else {
|
|
||||||
//非烹饪中,从菜品主辅材配置页面带入数据
|
|
||||||
goodsList = getSerializable(GOODS_LIST) as MutableList<CookFoodGoodsEntity>?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setTitleBar(titleBarAction = {
|
setTitleBar(titleBarAction = {
|
||||||
it.visible()
|
it.visible()
|
||||||
@@ -90,80 +103,103 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
}, rightIconActon = {
|
}, rightIconActon = {
|
||||||
it.gone()
|
it.gone()
|
||||||
it.setImageResource(R.drawable.ic_setting)
|
it.setImageResource(R.drawable.ic_setting)
|
||||||
it.setOnClickListener {
|
|
||||||
//startActivity<DeviceSettingActivity>()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
setupRecyclerView()
|
||||||
addViewListener()
|
addViewListener()
|
||||||
|
loadSlotsThenObserveScales()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadSeasoningFromLocal() {
|
/** 初始化 RecyclerView,2列 GridLayoutManager */
|
||||||
appViewModel.getCookFoodGoodsList(
|
private fun setupRecyclerView() {
|
||||||
foodId = food!!.foodId!!,
|
binding.rvSeasoning.itemAnimator = null
|
||||||
cookMode = food!!.cookMode
|
binding.rvSeasoning.layoutManager = GridLayoutManager(this, 2)
|
||||||
) { it ->
|
binding.rvSeasoning.adapter = seasoningAdapter
|
||||||
goodsList = mutableListOf()
|
}
|
||||||
val goodsIdList = mutableListOf<String>()
|
|
||||||
//设置主辅材数据
|
|
||||||
val tempList = it.filter { goods -> goods.materialType != 3 }
|
|
||||||
tempList.forEach {
|
|
||||||
if (!goodsIdList.contains(it.goodsId)) {
|
|
||||||
goodsList?.add(it)
|
|
||||||
goodsIdList.add(it.goodsId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//获取调料数据并展示
|
/**
|
||||||
val filterList: List<SeasoningEntity>? = it.filter { goods -> goods.materialType == 3 }
|
* 从 Room 加载槽位配置后,开始订阅子设备秤数据
|
||||||
.map { entity ->
|
* 确保 slotMap 就绪后再处理秤数据,避免名称为空
|
||||||
SeasoningEntity().also { se ->
|
*/
|
||||||
se.goodsId = entity.goodsId
|
private fun loadSlotsThenObserveScales() {
|
||||||
se.goodsName = entity.goodsName
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
se.goodsOrRelationCode = entity.goodsOrRelationCode
|
val slots = BaseApp.instance!!.database.seasoningSlotDao().queryAll()
|
||||||
se.relateionType = entity.relateionType
|
withContext(Dispatchers.Main) {
|
||||||
se.materialType = entity.materialType
|
slots.forEach { slot ->
|
||||||
se.allEdible = entity.allEdible
|
slotMap["${slot.deviceId}#${slot.address}"] = slot.goodsId to slot.goodsName
|
||||||
se.useWeight = entity.useWeight
|
|
||||||
se.popularName = entity.popularName
|
|
||||||
se.canteenId = entity.canteenId
|
|
||||||
se.relateionType_dictText = entity.relateionType_dictText
|
|
||||||
se.foodId = entity.foodId
|
|
||||||
se.sort = entity.sort
|
|
||||||
se.pageType = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
filterList?.toMutableList()?.let { cellData ->
|
observeScaleData()
|
||||||
loadTextCell(cellData)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅 ScaleServiceManager.allScales,过滤子设备数据并更新 adapter
|
||||||
|
* 排除主设备(DEVICE_ID_2),只处理 22格、18格、1格子设备
|
||||||
|
*/
|
||||||
|
private fun observeScaleData() {
|
||||||
|
val flow = ScaleServiceManager.allScales ?: return
|
||||||
|
lifecycleScope.launch {
|
||||||
|
flow.collectLatest { scaleMap ->
|
||||||
|
scaleMap.forEach { (key, data) ->
|
||||||
|
// 排除主设备 2格秤
|
||||||
|
if (data.deviceId == ScaleDeviceConfig.DEVICE_ID_2) return@forEach
|
||||||
|
|
||||||
// private lateinit var seasoningList: List<SeasoningEntity>
|
// 从槽位配置读取调料名,未配置则跳过
|
||||||
// private var isFirstSave = true
|
val (goodsId, goodsName) = slotMap[key] ?: return@forEach
|
||||||
// fun updateSeasoningList(list: MutableList<SeasoningEntity>) {
|
|
||||||
// if (isFirstSave) {
|
|
||||||
// val json = list.toJsonString()
|
|
||||||
// val typeToken = object : TypeToken<List<SeasoningEntity>>() {}
|
|
||||||
// seasoningList = json.toType(typeToken = typeToken)
|
|
||||||
// isFirstSave = false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
private val seasoningArray by lazy {
|
// 首次收到稳定数据时记录基准重量
|
||||||
SparseArray<Double>()
|
val baseline = baselineMap[key]
|
||||||
|
if (baseline == null) {
|
||||||
|
if (data.state == WeightUtil.STATE_STABLE) {
|
||||||
|
baselineMap[key] = data.weight
|
||||||
|
}
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用量 = 初始重量 - 当前重量,负值归零
|
||||||
|
val useWeight = (baseline - data.weight)
|
||||||
|
.coerceAtLeast(0.0)
|
||||||
|
.roundedOneDecimalPlace()
|
||||||
|
|
||||||
|
// 更新原始用量 map,再按 goodsName 聚合刷新 adapter
|
||||||
|
if (useWeight < 0.5) {
|
||||||
|
rawWeightMap.remove(key)
|
||||||
|
} else {
|
||||||
|
rawWeightMap[key] = Triple(goodsId, goodsName, useWeight)
|
||||||
|
}
|
||||||
|
refreshAdapterByName(goodsName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
private val firstGoodsArray = SparseArray<Double>()
|
* 按 goodsName 聚合 rawWeightMap,将同名调料的用量累加后推给 adapter
|
||||||
|
* 聚合后总量 < 0.5g 则从 adapter 移除
|
||||||
|
*/
|
||||||
|
private fun refreshAdapterByName(goodsName: String) {
|
||||||
|
val grouped = rawWeightMap.values.filter { it.second == goodsName }
|
||||||
|
if (grouped.isEmpty()) {
|
||||||
|
seasoningAdapter.removeItem(goodsName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val totalWeight = grouped.sumOf { it.third }.roundedOneDecimalPlace()
|
||||||
|
if (totalWeight < 0.5) {
|
||||||
|
seasoningAdapter.removeItem(goodsName)
|
||||||
|
} else {
|
||||||
|
seasoningAdapter.updateItem(
|
||||||
|
SeasoningWeightAdapter.Item(
|
||||||
|
goodsId = grouped.first().first,
|
||||||
|
goodsName = goodsName,
|
||||||
|
useWeight = totalWeight
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addViewListener() {
|
private fun addViewListener() {
|
||||||
addWeightListener()
|
|
||||||
binding.btnWeightClear.setOnClickListener { WeightUtil.tareTwo(AddressUtil.TWO) }
|
|
||||||
|
|
||||||
binding.btnCook.clickWithDebounce {
|
binding.btnCook.clickWithDebounce { cook() }
|
||||||
cook()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.btnSubmit.clickWithDebounce {
|
binding.btnSubmit.clickWithDebounce {
|
||||||
if (cookFoodEntity.foodWeight <= 0.toDouble()) {
|
if (cookFoodEntity.foodWeight <= 0.toDouble()) {
|
||||||
@@ -172,114 +208,40 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
submit()
|
submit()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun addWeightListener() {
|
// 监听主设备 2格秤的熟重
|
||||||
WeightUtil.addWeightListener(
|
WeightUtil.addWeightListener(TAG) { address, _, weight ->
|
||||||
weightKey = TAG,
|
if (address == AddressUtil.TWO) {
|
||||||
getWeight = { address, state, weight ->
|
cookFoodEntity.foodWeight = weight.toDouble()
|
||||||
if (address == AddressUtil.TWO) {
|
binding.tvTotalWeight.text = "${weight / 1000f}"
|
||||||
cookFoodEntity.foodWeight = weight.toDouble()
|
|
||||||
binding.tvTotalWeight.text = "${weight / 1000f}"
|
|
||||||
}
|
|
||||||
val firstWeight = firstGoodsArray.get(address, null)
|
|
||||||
if (firstWeight == null) {
|
|
||||||
//未设置数据
|
|
||||||
if (state != WeightUtil.STATE_STABLE) {
|
|
||||||
//首次记录数据需要稳定数据
|
|
||||||
return@addWeightListener
|
|
||||||
}
|
|
||||||
firstGoodsArray.put(address, weight)
|
|
||||||
return@addWeightListener
|
|
||||||
}
|
|
||||||
var realUseWeight = firstWeight - weight
|
|
||||||
realUseWeight = if (realUseWeight > 0) realUseWeight else 0.0
|
|
||||||
seasoningArray.put(address, realUseWeight.toDouble())
|
|
||||||
|
|
||||||
// refreshSeasoningWeight()
|
|
||||||
|
|
||||||
val item = seasoningItems.firstOrNull { address == addressArray[it.sort] }
|
|
||||||
item?.let {
|
|
||||||
val lastWeight = seasoningArray.get(address) ?: 0.toDouble()
|
|
||||||
it.useWeight =
|
|
||||||
(lastWeight + getCookingSeasoning(address)).roundedOneDecimalPlace()
|
|
||||||
updateGridData(it)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// private fun refreshSeasoningWeight() {
|
|
||||||
// if (seasoningItems.isEmpty()) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// seasoningItems.let { items ->
|
|
||||||
// addressArray.forEach { key, value ->
|
|
||||||
// items[key].useWeight =
|
|
||||||
// (seasoningArray.get(value) ?: 0.toDouble()) + getCookingSeasoning(value)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// setGridData(seasoningItems)
|
|
||||||
// }
|
|
||||||
|
|
||||||
private fun updateGridData(entity: SeasoningEntity) {
|
|
||||||
val gridLayout = binding.include.root
|
|
||||||
val tag = entity.sort.toString()
|
|
||||||
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
|
|
||||||
TextCellAdapter.loadLayout(frameLayout, entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCookingSeasoning(address: Int): Double {
|
|
||||||
if (isCooking.not()) return 0.toDouble()
|
|
||||||
if (seasoningItems.isEmpty()) return 0.toDouble()
|
|
||||||
val weight =
|
|
||||||
seasoningCookingItems?.get(weightRelateArray2.get(address))?.useWeight ?: 0.toDouble()
|
|
||||||
return weight
|
|
||||||
}
|
|
||||||
|
|
||||||
private val weightRelateArray2 by lazy {
|
|
||||||
SparseIntArray().apply {
|
|
||||||
addressArray.forEach { key, value ->
|
|
||||||
put(value, key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val addressArray by lazy {
|
|
||||||
AddressUtil.getWeighAddressArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
// private fun prepareData(acton: () -> Unit) {
|
|
||||||
//
|
|
||||||
// //只有制作和采样列表页面才能进入设置页面,提交页面无法进行设置,不存在调料数据被修改的问题
|
|
||||||
// val seasoningJson = realSeasoningData.toJsonString()
|
|
||||||
// val typeToken = object : TypeToken<List<CookFoodGoodsEntity>>() {}
|
|
||||||
// val tempSeasoningList = seasoningJson.toType(typeToken = typeToken)
|
|
||||||
// goodsList?.addAll(tempSeasoningList)
|
|
||||||
// acton()
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
private fun cook() {
|
private fun cook() {
|
||||||
if (food == null || goodsList.isNullOrEmpty()) {
|
if (food == null || goodsList.isNullOrEmpty()) {
|
||||||
toast("未获取到菜品或构成信息")
|
toast("未获取到菜品或构成信息")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val realSeasoningData = seasoningItems
|
val seasoningData = seasoningAdapter.items
|
||||||
//(binding.rvSeasoning.adapter as TextCellAdapter).list
|
if (seasoningData.isEmpty()) {
|
||||||
if (realSeasoningData.isEmpty() && !isCooking) {
|
|
||||||
toast("未获取到调料信息")
|
toast("未获取到调料信息")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (realSeasoningData.isNotEmpty() && realSeasoningData[0].goodsId.isNullOrBlank()) {
|
|
||||||
toast("还未设置调料信息,请去设置页面操作")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
showLoading()
|
showLoading()
|
||||||
//只有制作和采样列表页面才能进入设置页面,提交页面无法进行设置,不存在调料数据被修改的问题
|
|
||||||
val seasoningJson = realSeasoningData.toJsonString()
|
// 将调料数据转为 CookFoodGoodsEntity 追加到 goodsList
|
||||||
val typeToken = object : TypeToken<List<CookFoodGoodsEntity>>() {}
|
val typeToken = object : TypeToken<List<CookFoodGoodsEntity>>() {}
|
||||||
val tempSeasoningList = seasoningJson.toType(typeToken = typeToken)
|
val tempSeasoningList = seasoningData.map { item ->
|
||||||
|
CookFoodGoodsEntity().also { entity ->
|
||||||
|
entity.goodsId = item.goodsId
|
||||||
|
entity.goodsName = item.goodsName
|
||||||
|
entity.useWeight = item.useWeight
|
||||||
|
entity.materialType = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
goodsList?.addAll(tempSeasoningList)
|
goodsList?.addAll(tempSeasoningList)
|
||||||
|
|
||||||
cookFoodEntity.let {
|
cookFoodEntity.let {
|
||||||
it.dinnerType = when (it.dinnerType) {
|
it.dinnerType = when (it.dinnerType) {
|
||||||
"早餐" -> "1"
|
"早餐" -> "1"
|
||||||
@@ -288,17 +250,14 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
else -> it.dinnerType
|
else -> it.dinnerType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isSamplingData = food!!.cookMode == 1
|
val isSamplingData = food!!.cookMode == 1
|
||||||
if (isSamplingData) {
|
if (isSamplingData) {
|
||||||
//生成临时foodId,解决采样模式没有foodId,无法关联数据的问题
|
|
||||||
val tempFoodId = cookFoodEntity.foodId.ifBlank { "${System.currentTimeMillis()}" }
|
val tempFoodId = cookFoodEntity.foodId.ifBlank { "${System.currentTimeMillis()}" }
|
||||||
cookFoodEntity.foodId = tempFoodId
|
cookFoodEntity.foodId = tempFoodId
|
||||||
}
|
}
|
||||||
//全部设置同一foodId
|
goodsList?.forEach { it.foodId = cookFoodEntity.foodId }
|
||||||
goodsList?.forEach {
|
|
||||||
it.foodId = cookFoodEntity.foodId
|
|
||||||
}
|
|
||||||
//保存菜品-保存主辅料调料
|
|
||||||
appViewModel.saveCookFoodAndGoods(
|
appViewModel.saveCookFoodAndGoods(
|
||||||
cookMode = food!!.cookMode,
|
cookMode = food!!.cookMode,
|
||||||
entity = cookFoodEntity,
|
entity = cookFoodEntity,
|
||||||
@@ -316,31 +275,22 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
toast("未获取到菜品或构成信息")
|
toast("未获取到菜品或构成信息")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val realSeasoningData = seasoningItems
|
val seasoningData = seasoningAdapter.items
|
||||||
if (realSeasoningData.isEmpty() && !isCooking) {
|
|
||||||
toast("未获取到调料信息")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (realSeasoningData.isNotEmpty() && realSeasoningData[0].goodsId.isNullOrBlank()) {
|
|
||||||
toast("还未设置调料信息,请去设置页面操作")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
showLoading()
|
showLoading()
|
||||||
//只有制作和采样列表页面才能进入设置页面,提交页面无法进行设置,不存在调料数据被修改的问题
|
|
||||||
val seasoningJson = realSeasoningData.toJsonString()
|
// 将重量大于0且不重复的调料追加到 goodsList
|
||||||
val typeToken = object : TypeToken<List<CookFoodGoodsEntity>>() {}
|
seasoningData.forEach { item ->
|
||||||
val tempSeasoningList = seasoningJson.toType(typeToken = typeToken)
|
if (item.useWeight > 0.0 && goodsList?.none { it.goodsId == item.goodsId } == true) {
|
||||||
tempSeasoningList.forEach { entity ->
|
goodsList?.add(CookFoodGoodsEntity().also { entity ->
|
||||||
val realWeight = entity.useWeight ?: 0.toDouble()
|
entity.goodsId = item.goodsId
|
||||||
val filterData = goodsList?.firstOrNull { it.goodsId == entity.goodsId }
|
entity.goodsName = item.goodsName
|
||||||
//确保重量大于0,且无重复数据
|
entity.useWeight = item.useWeight
|
||||||
if (realWeight > 0.toDouble() && filterData == null) {
|
entity.materialType = 3
|
||||||
goodsList?.add(entity)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cookFoodEntity.let {
|
cookFoodEntity.let {
|
||||||
// it.stFoodInfoConstituteList = this@SubmitFoodActivity.goodsList
|
|
||||||
it.matchingConstituteInfoList = this@SubmitFoodActivity.goodsList
|
it.matchingConstituteInfoList = this@SubmitFoodActivity.goodsList
|
||||||
it.dinnerType = when (it.dinnerType) {
|
it.dinnerType = when (it.dinnerType) {
|
||||||
"1" -> "早餐"
|
"1" -> "早餐"
|
||||||
@@ -352,30 +302,15 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
|
|
||||||
val isSamplingData = food!!.cookMode == 1
|
val isSamplingData = food!!.cookMode == 1
|
||||||
if (isSamplingData) {
|
if (isSamplingData) {
|
||||||
//采样模式没有foodId
|
|
||||||
cookFoodEntity.foodId = ""
|
cookFoodEntity.foodId = ""
|
||||||
goodsList?.forEach { it.foodId = "" }
|
goodsList?.forEach { it.foodId = "" }
|
||||||
}
|
}
|
||||||
val json = cookFoodEntity.toJsonString()
|
|
||||||
Log.d(TAG, "submit: json=$json")
|
|
||||||
// HttpUtil.postJson(
|
|
||||||
// url = UrlConfig.SUBMIT_DISH,
|
|
||||||
// json = json,
|
|
||||||
// doSuccess = {
|
|
||||||
// Log.d(TAG, "submit: postJson=$it")
|
|
||||||
// submitSuccess(isSamplingData)
|
|
||||||
// }, doFailure = { code, msg ->
|
|
||||||
// Log.d(TAG, "submit: postJson:code=$code,msg=$msg")
|
|
||||||
// toast(msg)
|
|
||||||
// dismissLoading()
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
Log.d(TAG, "submit: json=${cookFoodEntity.toJsonString()}")
|
||||||
netViewModel.submitCookFood(
|
netViewModel.submitCookFood(
|
||||||
entity = cookFoodEntity,
|
entity = cookFoodEntity,
|
||||||
onSuccess = {
|
onSuccess = { submitSuccess(isSamplingData) },
|
||||||
submitSuccess(isSamplingData)
|
onFailure = { _, msg ->
|
||||||
},
|
|
||||||
onFailure = { code, msg ->
|
|
||||||
toast(msg)
|
toast(msg)
|
||||||
dismissLoading()
|
dismissLoading()
|
||||||
})
|
})
|
||||||
@@ -383,12 +318,10 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
|
|
||||||
private fun submitSuccess(isSamplingData: Boolean) {
|
private fun submitSuccess(isSamplingData: Boolean) {
|
||||||
if (food?.foodId.isNullOrBlank()) {
|
if (food?.foodId.isNullOrBlank()) {
|
||||||
//本地无删除数据直接跳转页面
|
|
||||||
dismissLoading()
|
dismissLoading()
|
||||||
jumpPage(isSamplingData, false)
|
jumpPage(isSamplingData, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//删除本地数据
|
|
||||||
appViewModel.deleteCookFoodAndGoods(
|
appViewModel.deleteCookFoodAndGoods(
|
||||||
cookMode = food!!.cookMode,
|
cookMode = food!!.cookMode,
|
||||||
foodId = food!!.foodId!!
|
foodId = food!!.foodId!!
|
||||||
@@ -415,57 +348,12 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
|
|
||||||
private fun initViewModel() {
|
private fun initViewModel() {
|
||||||
val db = BaseApp.instance!!.database
|
val db = BaseApp.instance!!.database
|
||||||
val factory =
|
val factory = AppFactory(AppRepository(db.appDao()))
|
||||||
AppFactory(AppRepository(db.appDao()))
|
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
|
||||||
appViewModel =
|
|
||||||
ViewModelProvider(this, factory)[AppViewModel::class.java]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onDestroy() {
|
||||||
super.onResume()
|
WeightUtil.removeWeightListener(TAG)
|
||||||
if (isCooking.not()) {
|
super.onDestroy()
|
||||||
//烹饪中使用本地数据,未烹饪则使用默认的调料配置数据
|
|
||||||
appViewModel.loadSeasoning {
|
|
||||||
Log.d(TAG, "onResume: loadSeasoning:${it.toJsonString()}")
|
|
||||||
loadTextCell(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var seasoningItems = mutableListOf<SeasoningEntity>()
|
|
||||||
private var seasoningCookingItems: List<SeasoningEntity>? = null
|
|
||||||
private fun loadTextCell(list: MutableList<SeasoningEntity>) {
|
|
||||||
seasoningItems.clear()
|
|
||||||
seasoningItems.addAll(list)
|
|
||||||
initConfigData()
|
|
||||||
// setGridData(list)
|
|
||||||
list.forEach { entity ->
|
|
||||||
//烹饪中重量数据从本地获取,否则显示0
|
|
||||||
if (isCooking.not()) {
|
|
||||||
entity.useWeight = 0.0
|
|
||||||
}
|
|
||||||
updateGridData(entity)
|
|
||||||
}
|
|
||||||
seasoningCookingItems = seasoningItems.map { it.copy() }.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initConfigData() {
|
|
||||||
repeat(12) { num ->
|
|
||||||
val firstOne = seasoningItems.firstOrNull { it.sort == num }
|
|
||||||
if (firstOne == null) {
|
|
||||||
seasoningItems.add(SeasoningEntity().also { it.sort = num })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setGridData(list: MutableList<SeasoningEntity>) {
|
|
||||||
val gridLayout = binding.include.root
|
|
||||||
list.forEach { entity ->
|
|
||||||
val tag = entity.sort.toString()
|
|
||||||
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
|
|
||||||
TextCellAdapter.loadLayout(frameLayout, entity)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,23 @@ import com.google.android.flexbox.FlexWrap
|
|||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import com.shuwei.dish.match.adapter.Seasoning18GridAdapter
|
import com.shuwei.dish.match.adapter.Seasoning18GridAdapter
|
||||||
import com.shuwei.dish.match.adapter.Seasoning22GridAdapter
|
import com.shuwei.dish.match.adapter.Seasoning22GridAdapter
|
||||||
|
import com.shuwei.dish.match.base.BaseApp
|
||||||
import com.shuwei.dish.match.base.BaseFragment
|
import com.shuwei.dish.match.base.BaseFragment
|
||||||
import com.shuwei.dish.match.base.DeviceRole
|
import com.shuwei.dish.match.base.DeviceRole
|
||||||
import com.shuwei.dish.match.base.GlobalData
|
import com.shuwei.dish.match.base.GlobalData
|
||||||
import com.shuwei.dish.match.databinding.FragmentSeasoningConfigBinding
|
import com.shuwei.dish.match.databinding.FragmentSeasoningConfigBinding
|
||||||
import com.shuwei.dish.match.dialog.SeasoningSelectDialog
|
import com.shuwei.dish.match.dialog.SeasoningSelectDialog
|
||||||
|
import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
||||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
||||||
import com.shuwei.dish.match.scale.ScaleEvent
|
import com.shuwei.dish.match.scale.ScaleEvent
|
||||||
import com.shuwei.dish.match.scale.ScaleServiceManager
|
import com.shuwei.dish.match.scale.ScaleServiceManager
|
||||||
|
import com.shuwei.dish.match.scale.ScaleEvent.SlotConfig
|
||||||
import com.shuwei.dish.match.ui.SettingActivity
|
import com.shuwei.dish.match.ui.SettingActivity
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
||||||
|
|
||||||
@@ -45,7 +50,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
|||||||
// private lateinit var appViewModel: AppViewModel
|
// private lateinit var appViewModel: AppViewModel
|
||||||
|
|
||||||
private val adapter22 by lazy { buildAdapter22() }
|
private val adapter22 by lazy { buildAdapter22() }
|
||||||
private val adapter18 by lazy { Seasoning18GridAdapter() }
|
private val adapter18 by lazy { Seasoning18GridAdapter(showNameOnly = true) }
|
||||||
|
|
||||||
/** 各秤的上一次重量快照,key 格式 "{deviceId}#{address}",用于计算 delta */
|
/** 各秤的上一次重量快照,key 格式 "{deviceId}#{address}",用于计算 delta */
|
||||||
private val weightSnapshot = mutableMapOf<String, Double>()
|
private val weightSnapshot = mutableMapOf<String, Double>()
|
||||||
@@ -59,32 +64,86 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
|||||||
// initViewModel()
|
// initViewModel()
|
||||||
setupRecyclerViews()
|
setupRecyclerViews()
|
||||||
bindAdapterClicks()
|
bindAdapterClicks()
|
||||||
|
bindOilSlotClick()
|
||||||
addWeightListener()
|
addWeightListener()
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 绑定油盆格子点击,弹出调料选择弹窗并保存到 DEVICE_ID_1 */
|
||||||
|
private fun bindOilSlotClick() {
|
||||||
|
binding.tvOilSlot.setOnClickListener {
|
||||||
|
SeasoningSelectDialog(
|
||||||
|
activity = settingActivity,
|
||||||
|
clickIndex = 0,
|
||||||
|
onItemSelected = { item ->
|
||||||
|
binding.tvOilSlot.text = item.goodsName ?: "-"
|
||||||
|
saveSlotAndBroadcast(
|
||||||
|
SeasoningSlotEntity(
|
||||||
|
deviceId = ScaleDeviceConfig.DEVICE_ID_1,
|
||||||
|
address = 1,
|
||||||
|
goodsId = item.goodsId,
|
||||||
|
goodsName = item.goodsName ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 绑定两个 Adapter 的格子点击,弹出调料选择弹窗 */
|
/** 绑定两个 Adapter 的格子点击,弹出调料选择弹窗 */
|
||||||
private fun bindAdapterClicks() {
|
private fun bindAdapterClicks() {
|
||||||
adapter22.onItemClick = { _, position ->
|
adapter22.onItemClick = { scaleData, position ->
|
||||||
SeasoningSelectDialog(
|
SeasoningSelectDialog(
|
||||||
activity = settingActivity,
|
activity = settingActivity,
|
||||||
clickIndex = position,
|
clickIndex = position,
|
||||||
onItemSelected = { item ->
|
onItemSelected = { item ->
|
||||||
adapter22.updateItemName(position, item.goodsName ?: "")
|
adapter22.updateItemName(position, item.goodsName ?: "")
|
||||||
|
saveSlotAndBroadcast(
|
||||||
|
SeasoningSlotEntity(
|
||||||
|
deviceId = ScaleDeviceConfig.DEVICE_ID_22,
|
||||||
|
address = scaleData.address,
|
||||||
|
goodsId = item.goodsId,
|
||||||
|
goodsName = item.goodsName ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
adapter18.onItemClick = { _, position ->
|
adapter18.onItemClick = { scaleData, position ->
|
||||||
SeasoningSelectDialog(
|
SeasoningSelectDialog(
|
||||||
activity = settingActivity,
|
activity = settingActivity,
|
||||||
clickIndex = position,
|
clickIndex = position,
|
||||||
onItemSelected = { item ->
|
onItemSelected = { item ->
|
||||||
adapter18.updateItemName(position, item.goodsName ?: "")
|
adapter18.updateItemName(position, item.goodsName ?: "")
|
||||||
|
saveSlotAndBroadcast(
|
||||||
|
SeasoningSlotEntity(
|
||||||
|
deviceId = ScaleDeviceConfig.DEVICE_ID_18,
|
||||||
|
address = scaleData.address,
|
||||||
|
goodsId = item.goodsId,
|
||||||
|
goodsName = item.goodsName ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将单个槽位写入 Room,再读取全量配置广播给子设备
|
||||||
|
* 仅主设备执行广播,子设备不调用此方法
|
||||||
|
*/
|
||||||
|
private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val dao = BaseApp.instance!!.database.seasoningSlotDao()
|
||||||
|
dao.upsert(slot)
|
||||||
|
// 读取全量配置,通过 wsClient 推送给所有已连接子设备
|
||||||
|
val allSlots = dao.queryAll().map {
|
||||||
|
SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName)
|
||||||
|
}
|
||||||
|
ScaleServiceManager.sendSeasoningConfig(allSlots)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// /** 初始化 ViewModel */
|
// /** 初始化 ViewModel */
|
||||||
// private fun initViewModel() {
|
// private fun initViewModel() {
|
||||||
// val factory = AppFactory(AppRepository(BaseApp.instance!!.database.appDao()))
|
// val factory = AppFactory(AppRepository(BaseApp.instance!!.database.appDao()))
|
||||||
@@ -96,7 +155,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
|||||||
val usable = resources.displayMetrics.widthPixels - 8 * 4.dp - 10.dp
|
val usable = resources.displayMetrics.widthPixels - 8 * 4.dp - 10.dp
|
||||||
val smallSize = usable / 9
|
val smallSize = usable / 9
|
||||||
val largeSize = (usable - smallSize * 6) / 2
|
val largeSize = (usable - smallSize * 6) / 2
|
||||||
return Seasoning22GridAdapter(largeSize, smallSize)
|
return Seasoning22GridAdapter(largeSize, smallSize, showNameOnly = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 配置两个 RecyclerView */
|
/** 配置两个 RecyclerView */
|
||||||
@@ -126,14 +185,29 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载默认空数据,使两个列表完整展示所有格子 */
|
/** 从 Room 加载已配置的槽位名称,填充到对应格子 */
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
adapter22.update(emptyList())
|
adapter22.update(emptyList())
|
||||||
adapter18.update(emptyList())
|
adapter18.update(emptyList())
|
||||||
// appViewModel.loadSeasoning { list ->
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
// adapter22.update(list ?: emptyList())
|
val dao = BaseApp.instance!!.database.seasoningSlotDao()
|
||||||
// adapter18.update(list ?: emptyList())
|
val slots22 = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_22)
|
||||||
// }
|
val slots18 = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_18)
|
||||||
|
val slotsOil = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_1)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
slots22.forEach { slot ->
|
||||||
|
val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address)
|
||||||
|
if (pos >= 0) adapter22.updateItemName(pos, slot.goodsName)
|
||||||
|
}
|
||||||
|
slots18.forEach { slot ->
|
||||||
|
val pos = ScaleDeviceConfig.SCALE_ORDER_18.indexOf(slot.address)
|
||||||
|
if (pos >= 0) adapter18.updateItemName(pos, slot.goodsName)
|
||||||
|
}
|
||||||
|
// 加载油盆格子名称(address=1)
|
||||||
|
val oilSlot = slotsOil.firstOrNull { it.address == 1 }
|
||||||
|
binding.tvOilSlot.text = if (oilSlot != null && oilSlot.goodsName.isNotEmpty()) oilSlot.goodsName else "-"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,10 +296,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
|||||||
* @param delta 重量变化量(克)
|
* @param delta 重量变化量(克)
|
||||||
*/
|
*/
|
||||||
private fun triggerHighlight(sourceDeviceId: String, address: Int, delta: Double) {
|
private fun triggerHighlight(sourceDeviceId: String, address: Int, delta: Double) {
|
||||||
// 根据来源设备决定查哪个列表,而非查本机设备ID
|
// 只有对应设备的秤才查对应列表,避免主设备(DEVICE_ID_2)等其他设备误触发
|
||||||
val pos22 = if (sourceDeviceId != ScaleDeviceConfig.DEVICE_ID_18)
|
val pos22 = if (sourceDeviceId == ScaleDeviceConfig.DEVICE_ID_22)
|
||||||
ScaleDeviceConfig.SCALE_ORDER_22.indexOf(address) else -1
|
ScaleDeviceConfig.SCALE_ORDER_22.indexOf(address) else -1
|
||||||
val pos18 = if (sourceDeviceId != ScaleDeviceConfig.DEVICE_ID_22)
|
val pos18 = if (sourceDeviceId == ScaleDeviceConfig.DEVICE_ID_18)
|
||||||
ScaleDeviceConfig.SCALE_ORDER_18.indexOf(address) else -1
|
ScaleDeviceConfig.SCALE_ORDER_18.indexOf(address) else -1
|
||||||
|
|
||||||
Log.d(TAG, "triggerHighlight: sourceDeviceId=$sourceDeviceId address=$address pos22=$pos22 pos18=$pos18")
|
Log.d(TAG, "triggerHighlight: sourceDeviceId=$sourceDeviceId address=$address pos22=$pos22 pos18=$pos18")
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:background="@color/bg_color">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:text="22格秤数据"
|
|
||||||
android:textColor="@color/home_title"
|
|
||||||
android:textSize="36sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/ivBack"
|
|
||||||
android:layout_width="70dp"
|
|
||||||
android:layout_height="60dp"
|
|
||||||
android:paddingStart="30dp"
|
|
||||||
android:paddingEnd="10dp"
|
|
||||||
android:src="@drawable/ic_back"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/tvTitle"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/tvTitle"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/rvScale22"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:layout_marginStart="2dp"
|
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:overScrollMode="never"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvTitle"
|
|
||||||
tools:listitem="@layout/list_item_scale22_row" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -27,11 +27,12 @@
|
|||||||
android:textColor="@color/black666"
|
android:textColor="@color/black666"
|
||||||
android:textSize="28sp" />
|
android:textSize="28sp" />
|
||||||
|
|
||||||
<include
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/include"
|
android:id="@+id/rvSeasoning"
|
||||||
layout="@layout/layout_grid"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="660dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:overScrollMode="never" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -2,41 +2,100 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:background="@color/bg_color"
|
||||||
android:background="@color/bg_color">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="上层"
|
android:layout_marginStart="0dp"
|
||||||
android:textSize="18sp"
|
android:layout_marginEnd="0dp"
|
||||||
android:textStyle="bold"
|
android:paddingBottom="20dp"
|
||||||
android:textColor="@color/home_title"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginBottom="6dp" />
|
android:background="@drawable/shape_white_30_corners"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<FrameLayout
|
||||||
android:id="@+id/rvTop"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="106dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_marginStart="6dp"
|
||||||
android:overScrollMode="never" />
|
android:layout_marginEnd="6dp"
|
||||||
|
android:background="@drawable/bg_bottom_line">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="下层"
|
android:layout_gravity="start|center_vertical"
|
||||||
android:textSize="18sp"
|
android:text="调料区设置"
|
||||||
android:textStyle="bold"
|
android:textColor="@color/black"
|
||||||
android:textColor="@color/home_title"
|
android:textSize="28sp"
|
||||||
android:layout_marginStart="10dp"
|
android:textStyle="bold" />
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginBottom="6dp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<TextView
|
||||||
android:id="@+id/rvBottom"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_gravity="end|center_vertical"
|
||||||
android:overScrollMode="never" />
|
android:text="点击分格配置调料"
|
||||||
|
android:textColor="@color/gray_b4"
|
||||||
|
android:textSize="28sp" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:text="上层调料区"
|
||||||
|
android:textColor="@color/home_title"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvTop"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:overScrollMode="never" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:text="下层调料区"
|
||||||
|
android:textColor="@color/home_title"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvBottom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:overScrollMode="never" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:text="油盆配置区"
|
||||||
|
android:textColor="@color/home_title"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvOilSlot"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="400dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:background="@drawable/shape_scale_cell"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="-"
|
||||||
|
android:textColor="@color/home_title"
|
||||||
|
android:textSize="28sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSeasoningName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/black666"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSeasoningWeight"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- 单个秤格子:地址 + 重量 + 状态 -->
|
<!-- 单个秤格子:秤地址 + 调料名 + 重量 + 状态 -->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -9,7 +9,16 @@
|
|||||||
android:background="@drawable/shape_scale_cell">
|
android:background="@drawable/shape_scale_cell">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvAddr"
|
android:id="@+id/tvAddress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/home_sub_title"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvName"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
|||||||
@@ -1,48 +1,52 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!-- 单行秤数据:秤编号 + 重量 + 状态 -->
|
||||||
<!-- 单行秤数据:秤编号 + 重量 + 状态 -->
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="120dp"
|
android:layout_height="340dp"
|
||||||
android:orientation="horizontal"
|
android:background="@drawable/shape_scale_cell"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingEnd="16dp"
|
||||||
android:background="@drawable/shape_scale_cell">
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
<!-- 秤编号 -->
|
<!-- 秤编号 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvScaleLabel"
|
android:id="@+id/tvScaleLabel"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:textSize="22sp"
|
|
||||||
android:textColor="@color/home_sub_title"
|
android:textColor="@color/home_sub_title"
|
||||||
|
android:textSize="18sp"
|
||||||
tools:text="秤 1" />
|
tools:text="秤 1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/home_sub_title"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:text="-" />
|
||||||
|
|
||||||
<!-- 重量 -->
|
<!-- 重量 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvWeight"
|
android:id="@+id/tvWeight"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="2"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:textColor="@color/home_title"
|
||||||
android:textSize="26sp"
|
android:textSize="26sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="@color/home_title"
|
|
||||||
tools:text="235.5 g" />
|
tools:text="235.5 g" />
|
||||||
|
|
||||||
<!-- 状态 -->
|
<!-- 状态 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvState"
|
android:id="@+id/tvState"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:textSize="18sp"
|
|
||||||
android:textColor="@color/home_sub_title"
|
android:textColor="@color/home_sub_title"
|
||||||
|
android:textSize="18sp"
|
||||||
tools:text="稳定" />
|
tools:text="稳定" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -29,48 +29,57 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/llScaleContainer"
|
android:id="@+id/llScaleContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="120dp"
|
android:layout_height="260dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center"
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:layout_marginBottom="2dp"
|
android:layout_marginBottom="2dp"
|
||||||
android:paddingStart="30dp"
|
|
||||||
android:background="@drawable/shape_scale_cell">
|
android:background="@drawable/shape_scale_cell">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvAddr"
|
android:id="@+id/tvAddr"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:textSize="18sp"
|
||||||
android:textSize="22sp"
|
android:includeFontPadding="false"
|
||||||
android:textColor="@color/home_sub_title"
|
android:textColor="@color/home_sub_title"
|
||||||
tools:text="秤 1" />
|
tools:text="秤 1" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tvName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/home_sub_title"
|
||||||
|
android:text="-" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
android:id="@+id/tvWeight"
|
android:id="@+id/tvWeight"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="2"
|
|
||||||
android:textSize="26sp"
|
android:textSize="26sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
android:textColor="@color/home_title"
|
android:textColor="@color/home_title"
|
||||||
tools:text="100.0 g" />
|
tools:text="100.0 g" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvState"
|
android:id="@+id/tvState"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
android:textColor="@color/home_sub_title"
|
android:textColor="@color/home_sub_title"
|
||||||
tools:text="稳定" />
|
tools:text="稳定" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.divider.MaterialDivider
|
<!-- <com.google.android.material.divider.MaterialDivider-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_height="1dp"
|
<!-- android:layout_height="1dp"-->
|
||||||
android:background="@color/gray_eb"
|
<!-- android:background="@color/gray_eb"-->
|
||||||
android:layout_marginTop="10dp" />
|
<!-- android:layout_marginTop="10dp" />-->
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -32,10 +32,10 @@
|
|||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:overScrollMode="never" />
|
android:overScrollMode="never" />
|
||||||
|
|
||||||
<com.google.android.material.divider.MaterialDivider
|
<!-- <com.google.android.material.divider.MaterialDivider-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_height="1dp"
|
<!-- android:layout_height="1dp"-->
|
||||||
android:background="@color/gray_eb"
|
<!-- android:background="@color/gray_eb"-->
|
||||||
android:layout_marginTop="10dp" />
|
<!-- android:layout_marginTop="10dp" />-->
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -25,18 +25,29 @@
|
|||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
tools:text="设备:device_01" />
|
tools:text="设备:device_01" />
|
||||||
|
|
||||||
<!-- 水平排列2个秤格子,高度固定 400dp -->
|
<!-- 水平排列2个秤格子 -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/llScaleContainer"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="400dp"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_marginTop="5dp" />
|
android:layout_marginTop="5dp">
|
||||||
|
|
||||||
<com.google.android.material.divider.MaterialDivider
|
<include
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/cell1"
|
||||||
android:layout_height="1dp"
|
layout="@layout/list_item_scale22_row"
|
||||||
android:background="@color/gray_eb"
|
android:layout_width="0dp"
|
||||||
android:layout_marginTop="10dp" />
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_margin="2dp" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/cell2"
|
||||||
|
layout="@layout/list_item_scale22_row"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_margin="2dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -32,10 +32,10 @@
|
|||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:overScrollMode="never" />
|
android:overScrollMode="never" />
|
||||||
|
|
||||||
<com.google.android.material.divider.MaterialDivider
|
<!-- <com.google.android.material.divider.MaterialDivider-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_height="1dp"
|
<!-- android:layout_height="1dp"-->
|
||||||
android:background="@color/gray_eb"
|
<!-- android:background="@color/gray_eb"-->
|
||||||
android:layout_marginTop="10dp" />
|
<!-- android:layout_marginTop="10dp" />-->
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user