fix(activity): 修复调料配置删除后子设备未同步问题
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -137,6 +137,23 @@ class DbRepository {
|
||||
suspend fun upsertAllSeasoningSlots(slots: List<SeasoningSlotEntity>) = withContext(Dispatchers.IO) {
|
||||
db.seasoningSlotDao().upsertAll(slots)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定设备的指定槽位配置
|
||||
* @param deviceId 目标设备 ID
|
||||
* @param address 秤硬件地址
|
||||
*/
|
||||
suspend fun deleteSeasoningSlot(deviceId: String, address: Int) = withContext(Dispatchers.IO) {
|
||||
db.seasoningSlotDao().deleteByDeviceIdAndAddress(deviceId, address)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定设备的全部槽位配置
|
||||
* @param deviceId 目标设备 ID
|
||||
*/
|
||||
suspend fun deleteAllSlotsByDeviceId(deviceId: String) = withContext(Dispatchers.IO) {
|
||||
db.seasoningSlotDao().deleteAllByDeviceId(deviceId)
|
||||
}
|
||||
}
|
||||
//
|
||||
//class SeasoningRepository(val seasoningDao: SeasoningDao) {
|
||||
|
||||
@@ -222,6 +222,21 @@ class DbViewModel : ViewModel() {
|
||||
suspend fun upsertAllSeasoningSlots(slots: List<SeasoningSlotEntity>) =
|
||||
rep.upsertAllSeasoningSlots(slots)
|
||||
|
||||
/**
|
||||
* 删除指定设备的指定槽位配置
|
||||
* @param deviceId 目标设备 ID
|
||||
* @param address 秤硬件地址
|
||||
*/
|
||||
suspend fun deleteSeasoningSlot(deviceId: String, address: Int) =
|
||||
rep.deleteSeasoningSlot(deviceId, address)
|
||||
|
||||
/**
|
||||
* 删除指定设备的全部槽位配置(子设备接收全量广播时先清空再写入)
|
||||
* @param deviceId 目标设备 ID
|
||||
*/
|
||||
suspend fun deleteAllSlotsByDeviceId(deviceId: String) =
|
||||
rep.deleteAllSlotsByDeviceId(deviceId)
|
||||
|
||||
// fun updateAll(list: MutableList<SeasoningEntity>) {
|
||||
// viewModelScope.launch {
|
||||
// list.forEach {
|
||||
|
||||
@@ -37,4 +37,19 @@ interface SeasoningSlotDao {
|
||||
*/
|
||||
@Query("SELECT * FROM dm_seasoning_slot")
|
||||
suspend fun queryAll(): List<SeasoningSlotEntity>
|
||||
|
||||
/**
|
||||
* 删除指定设备的指定槽位配置
|
||||
* @param deviceId 目标设备 ID
|
||||
* @param address 秤硬件地址
|
||||
*/
|
||||
@Query("DELETE FROM dm_seasoning_slot WHERE deviceId = :deviceId AND address = :address")
|
||||
suspend fun deleteByDeviceIdAndAddress(deviceId: String, address: Int)
|
||||
|
||||
/**
|
||||
* 删除指定设备的全部槽位配置(子设备接收全量广播时先清空再写入)
|
||||
* @param deviceId 目标设备 ID
|
||||
*/
|
||||
@Query("DELETE FROM dm_seasoning_slot WHERE deviceId = :deviceId")
|
||||
suspend fun deleteAllByDeviceId(deviceId: String)
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class SlaveActivity : BaseActivity() {
|
||||
|
||||
/**
|
||||
* 监听主设备下发的调料配置同步事件
|
||||
* 收到后更新 adapter 并写入本地 Room
|
||||
* 收到后以全量替换方式更新本机 DB(先删后插),再刷新 UI
|
||||
*/
|
||||
private fun listenSeasoningConfig() {
|
||||
ScaleServiceManager.onSeasoningConfig = onSeasoningConfig@{ event ->
|
||||
@@ -167,6 +167,8 @@ class SlaveActivity : BaseActivity() {
|
||||
?.map { SeasoningSlotEntity(it.deviceId, it.address, it.goodsId, it.goodsName) }
|
||||
?: return@onSeasoningConfig
|
||||
lifecycleScope.launch {
|
||||
// 全量替换:先删除本机所有旧槽位,再写入最新配置
|
||||
appViewModel.deleteAllSlotsByDeviceId(GlobalData.deviceId)
|
||||
appViewModel.upsertAllSeasoningSlots(mySlots)
|
||||
applySlots(mySlots)
|
||||
}
|
||||
@@ -175,9 +177,38 @@ class SlaveActivity : BaseActivity() {
|
||||
|
||||
/**
|
||||
* 将槽位配置应用到对应 adapter(按地址匹配格子位置)
|
||||
* 同时更新 slotNameMap,确保后续重量回调构造 ScaleData 时携带正确名称
|
||||
* 同时以传入列表为准全量刷新 slotNameMap:
|
||||
* - 新列表中存在的地址:更新名称
|
||||
* - 新列表中不存在的地址:从 slotNameMap 移除,并清空对应格子名称
|
||||
*/
|
||||
private fun applySlots(slots: List<SeasoningSlotEntity>) {
|
||||
val newAddresses = slots.map { it.address }.toSet()
|
||||
|
||||
// 清除已被删除的槽位:从 slotNameMap 移除并清空 adapter 对应格子
|
||||
val removedAddresses = slotNameMap.keys.filter { it !in newAddresses }
|
||||
removedAddresses.forEach { addr ->
|
||||
slotNameMap.remove(addr)
|
||||
when {
|
||||
scale22Adapter != null -> {
|
||||
val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(addr)
|
||||
if (pos >= 0) scale22Adapter!!.updateItemName(pos, "")
|
||||
}
|
||||
scale18Adapter != null -> {
|
||||
val pos = ScaleDeviceConfig.SCALE_ORDER_18.indexOf(addr)
|
||||
if (pos >= 0) scale18Adapter!!.updateItemName(pos, "")
|
||||
}
|
||||
else -> {
|
||||
val list = linearAdapter.items.toMutableList()
|
||||
val idx = list.indexOfFirst { it.address == addr }
|
||||
if (idx >= 0) {
|
||||
list[idx] = list[idx].copy(name = "")
|
||||
linearAdapter.submitList(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新新列表中存在的槽位
|
||||
slots.forEach { slot ->
|
||||
slotNameMap[slot.address] = slot.goodsName
|
||||
when {
|
||||
@@ -190,7 +221,6 @@ class SlaveActivity : BaseActivity() {
|
||||
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) {
|
||||
@@ -290,7 +320,7 @@ class SlaveActivity : BaseActivity() {
|
||||
/** 重量增加阈值(克),超过此值才触发高亮 */
|
||||
private const val WEIGHT_DELTA_THRESHOLD = 10.0
|
||||
/** 高亮自动恢复时间(毫秒) */
|
||||
private const val HIGHLIGHT_DURATION_MS = 15_000L
|
||||
private const val HIGHLIGHT_DURATION_MS = 8_000L
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,8 +17,8 @@ import com.shuwei.dish.match.base.BaseFragment
|
||||
import com.shuwei.dish.match.base.DeviceRole
|
||||
import com.shuwei.dish.match.base.GlobalData
|
||||
import com.shuwei.dish.match.databinding.FragmentSeasoningConfigBinding
|
||||
import com.shuwei.dish.match.dialog.CommonDialog
|
||||
import com.shuwei.dish.match.dialog.SeasoningSelectDialog
|
||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||
import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
||||
import com.shuwei.dish.match.scale.ScaleDeviceConfig
|
||||
import com.shuwei.dish.match.scale.ScaleEvent
|
||||
@@ -39,7 +39,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
||||
private const val WEIGHT_DELTA_THRESHOLD = 10.0
|
||||
|
||||
/** 高亮自动恢复时间(毫秒) */
|
||||
private const val HIGHLIGHT_DURATION_MS = 15_000L
|
||||
private const val HIGHLIGHT_DURATION_MS = 8_000L
|
||||
}
|
||||
|
||||
override fun inflateBinding(
|
||||
@@ -80,81 +80,164 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
|
||||
/** 绑定油盆格子点击,弹出调料选择弹窗并保存到 DEVICE_ID_1 */
|
||||
private fun bindOilSlotClick() {
|
||||
binding.tvOilSlot.setOnClickListener {
|
||||
openSeasoningSelectDialog(0) { item ->
|
||||
binding.tvOilSlot.text = item.goodsName ?: "-"
|
||||
saveSlotAndBroadcast(
|
||||
SeasoningSlotEntity(
|
||||
deviceId = ScaleDeviceConfig.DEVICE_ID_1,
|
||||
address = 1,
|
||||
goodsId = item.goodsId,
|
||||
goodsName = item.goodsName ?: ""
|
||||
)
|
||||
)
|
||||
}
|
||||
handleSlotClick(
|
||||
currentName = binding.tvOilSlot.text.toString().takeIf { it != "-" },
|
||||
onSelect = {
|
||||
selectAndSave(ScaleDeviceConfig.DEVICE_ID_1, 1) { name ->
|
||||
binding.tvOilSlot.text = name.ifBlank { "-" }
|
||||
}
|
||||
},
|
||||
onClear = {
|
||||
binding.tvOilSlot.text = "-"
|
||||
deleteSlotAndBroadcast(ScaleDeviceConfig.DEVICE_ID_1, 1)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 绑定两个 Adapter 的格子点击,弹出调料选择弹窗 */
|
||||
private fun bindAdapterClicks() {
|
||||
adapter22.onItemClick = { scaleData, position ->
|
||||
openSeasoningSelectDialog(position) { item ->
|
||||
adapter22.updateItemName(position, item.goodsName ?: "")
|
||||
saveSlotAndBroadcast(
|
||||
SeasoningSlotEntity(
|
||||
deviceId = ScaleDeviceConfig.DEVICE_ID_22,
|
||||
address = scaleData.address,
|
||||
goodsId = item.goodsId,
|
||||
goodsName = item.goodsName ?: ""
|
||||
)
|
||||
bindSlotAdapterClick(
|
||||
adapter = adapter22,
|
||||
deviceId = ScaleDeviceConfig.DEVICE_ID_22
|
||||
)
|
||||
bindSlotAdapterClick(
|
||||
adapter = adapter18,
|
||||
deviceId = ScaleDeviceConfig.DEVICE_ID_18
|
||||
)
|
||||
}
|
||||
|
||||
/** 为指定 Adapter 绑定格子点击逻辑(已配置弹操作弹窗,未配置直接选择) */
|
||||
private fun bindSlotAdapterClick(
|
||||
adapter: Any,
|
||||
deviceId: String
|
||||
) {
|
||||
when (adapter) {
|
||||
is Seasoning22GridAdapter -> adapter.onItemClick = { scaleData, position ->
|
||||
handleSlotClick(
|
||||
currentName = scaleData.name,
|
||||
onSelect = {
|
||||
selectAndSave(deviceId, scaleData.address) { name ->
|
||||
adapter.updateItemName(position, name)
|
||||
}
|
||||
},
|
||||
onClear = {
|
||||
adapter.updateItemName(position, "")
|
||||
deleteSlotAndBroadcast(deviceId, scaleData.address)
|
||||
}
|
||||
)
|
||||
}
|
||||
is Seasoning18GridAdapter -> adapter.onItemClick = { scaleData, position ->
|
||||
handleSlotClick(
|
||||
currentName = scaleData.name,
|
||||
onSelect = {
|
||||
selectAndSave(deviceId, scaleData.address) { name ->
|
||||
adapter.updateItemName(position, name)
|
||||
}
|
||||
},
|
||||
onClear = {
|
||||
adapter.updateItemName(position, "")
|
||||
deleteSlotAndBroadcast(deviceId, scaleData.address)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
adapter18.onItemClick = { scaleData, position ->
|
||||
openSeasoningSelectDialog(position) { item ->
|
||||
adapter18.updateItemName(position, item.goodsName ?: "")
|
||||
saveSlotAndBroadcast(
|
||||
SeasoningSlotEntity(
|
||||
deviceId = ScaleDeviceConfig.DEVICE_ID_18,
|
||||
address = scaleData.address,
|
||||
goodsId = item.goodsId,
|
||||
goodsName = item.goodsName ?: ""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格子点击分发:已配置则弹操作弹窗,未配置直接执行选择
|
||||
* @param currentName 格子当前调料名称
|
||||
* @param onSelect 执行选择(新增或重新选择)
|
||||
* @param onClear 执行清除
|
||||
*/
|
||||
private fun handleSlotClick(
|
||||
currentName: String?,
|
||||
onSelect: () -> Unit,
|
||||
onClear: () -> Unit
|
||||
) {
|
||||
if (!currentName.isNullOrBlank()) {
|
||||
showSlotActionDialog(goodsName = currentName, onReselect = onSelect, onClear = onClear)
|
||||
} else {
|
||||
onSelect()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开调料选择弹窗
|
||||
*
|
||||
* @param position 格子位置
|
||||
* @param callback 调料选择回调
|
||||
*/
|
||||
private fun openSeasoningSelectDialog(
|
||||
position: Int,
|
||||
callback: (item: SeasoningEntity) -> Unit
|
||||
/**
|
||||
* 打开选择弹窗,选中后更新 UI 并保存槽位到 Room 广播
|
||||
* @param deviceId 目标设备 ID
|
||||
* @param address 秤硬件地址
|
||||
* @param onUpdateName 选中后更新格子显示名称的回调
|
||||
*/
|
||||
private fun selectAndSave(
|
||||
deviceId: String,
|
||||
address: Int,
|
||||
onUpdateName: (String) -> Unit
|
||||
) {
|
||||
SeasoningSelectDialog(
|
||||
activity = currentActivity,
|
||||
onItemSelected = callback
|
||||
onItemSelected = { item ->
|
||||
val name = item.goodsName ?: ""
|
||||
onUpdateName(name)
|
||||
saveSlotAndBroadcast(
|
||||
SeasoningSlotEntity(
|
||||
deviceId = deviceId,
|
||||
address = address,
|
||||
goodsId = item.goodsId,
|
||||
goodsName = name
|
||||
)
|
||||
)
|
||||
}
|
||||
).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个槽位写入 Room,再读取全量配置广播给子设备
|
||||
* 仅主设备执行广播,子设备不调用此方法
|
||||
*/
|
||||
/** 将单个槽位写入 Room,再广播全量配置给子设备 */
|
||||
private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) {
|
||||
lifecycleScope.launch {
|
||||
currentActivity.appViewModel.upsertSeasoningSlot(slot)
|
||||
// 读取全量配置,通过 wsClient 推送给所有已连接子设备
|
||||
val allSlots = currentActivity.appViewModel.loadSeasoningSlot().map {
|
||||
SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName)
|
||||
}
|
||||
ScaleServiceManager.sendSeasoningConfig(allSlots)
|
||||
broadcastAllSlots()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出已配置格子的操作选择弹窗(3按钮)
|
||||
* @param goodsName 当前格子已配置的调料名称
|
||||
* @param onReselect 用户点击「重新选择」后的回调
|
||||
* @param onClear 用户点击「清除配置」后的回调
|
||||
*/
|
||||
private fun showSlotActionDialog(
|
||||
goodsName: String,
|
||||
onReselect: () -> Unit,
|
||||
onClear: () -> Unit
|
||||
) {
|
||||
CommonDialog(currentActivity)
|
||||
.setTitle("调料操作")
|
||||
.setContent("当前配置:$goodsName")
|
||||
.setNegativeButton("取消")
|
||||
.setNeutralButton("重新选择") { onReselect() }
|
||||
.setPositiveButton("清除配置") { onClear() }
|
||||
.show()
|
||||
}
|
||||
|
||||
/** 从 Room 删除指定槽位,再广播全量配置给子设备 */
|
||||
private fun deleteSlotAndBroadcast(deviceId: String, address: Int) {
|
||||
lifecycleScope.launch {
|
||||
currentActivity.appViewModel.deleteSeasoningSlot(deviceId, address)
|
||||
broadcastAllSlots()
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取全量槽位配置并通过 wsClient 推送给所有已连接子设备 */
|
||||
private suspend fun broadcastAllSlots() {
|
||||
val allSlots = currentActivity.appViewModel.loadSeasoningSlot().map {
|
||||
SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName)
|
||||
}
|
||||
ScaleServiceManager.sendSeasoningConfig(allSlots)
|
||||
}
|
||||
|
||||
// /** 初始化 ViewModel */
|
||||
// private fun initViewModel() {
|
||||
// val factory = AppFactory(DbRepository(BaseApp.instance!!.database.appDao()))
|
||||
|
||||
Reference in New Issue
Block a user