refactor(home): 移除旧版首页和货架相关界面及代码

- 删除 activity_home.xml 布局文件
- 删除 activity_shelf.xml 布局文件
- 删除 activity_shelf_v2.xml 布局文件
- 从 AndroidManifest.xml 中移除 HomeActivity 和 ShelfActivity 等旧页面声明
- 删除 ApiService.kt 网络接口文件
- 删除 ApiServiceV2.kt 网络接口文件
- 删除 CabinetInitResult.kt 数据模型文件
- 删除 CleanPackageAdapter.kt 适配器文件
- 删除 CleanPackageOption.kt 数据模型文件
- 删除 Constant.kt 常量定义文件
- 删除 DeviceConfigInfo.kt 数据模型文件
- 删除 DrawableExt.kt 扩展工具文件
- 删除 F2C.kt 温度转换工具文件
- 删除 GoodsModel.kt 数据模型文件
- 删除 GoodsRecord.kt 数据模型文件
- 删除 GridLayoutTool.kt 布局工具文件
- 删除 HexUtils.java 十六进制工具类
This commit is contained in:
2026-06-30 11:27:29 +08:00
parent 947afd06e8
commit 3757b851be
51 changed files with 4 additions and 4402 deletions
-10
View File
@@ -36,21 +36,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.HomeActivity"
android:screenOrientation="portrait"/>
<activity
android:name=".activity.ShelfActivity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<!-- <activity android:name="com.shuwei.intelligent.shelves.activity.SettingActivity" />-->
<activity android:name="com.shuwei.intelligent.shelves.activity.LogActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.HomeV2Activity" />
<activity android:name="com.sw.scalefusion.shelf.HomeV3Activity" />
<activity android:name="com.sw.scalefusion.shelf.ShelfV3Activity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.ShelfV2Activity" />
<receiver
android:name=".utils.BootReceiver"
@@ -1,463 +0,0 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.addCallback
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.adapter.ShelfAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityHomeBinding
import com.shuwei.intelligent.shelves.dialog.CommonDialog
import com.shuwei.intelligent.shelves.model.ClearShelfEvent
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.model.ShelfResult
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.serial.ProtocolConstants
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.C_TEMP_CMD
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.START_TEMP_CTRL_CMD
import com.shuwei.intelligent.shelves.serial.ScaleManager
import com.shuwei.intelligent.shelves.utils.GridLayoutTool
import com.shuwei.intelligent.shelves.utils.IntervalExecutor
import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class HomeActivity : BaseActivity() {
companion object {}
override val enableRightStatusMenu: Boolean = true
// 货架列表数据
private val list: MutableList<ShelfModel> = mutableListOf()
// 当前选中货架下标
private var shelfIndex = 0
// 当前温度(摄氏)
private var showTemperatureC = "0"
// 当前湿度
private var showHumidity = "0"
// 仅特定 deviceId 的部署点需要发送开锁指令
private fun isNeedOpenLock() = App.deviceId == "4787e213-90ab-3e32-88e0-ac271a937751"
private var deviceName = ""
private lateinit var binding: ActivityHomeBinding
private val viewModel: NetViewModel by viewModels()
// 串口协议解析器,通过回调通知本 Activity
private fun initScaleManager() {
lifecycleScope.launch {
val ok = ScaleManager
.init(activateCode = ProtocolConstants.ACTIVE_MAP[App.deviceId] ?: "")
.onHeartbeat { _, data ->
data.weights.forEach { weight ->
val shelfNo = weight.index
if (shelfNo in 1..list.size) {
list.firstOrNull { it.deviceNo == shelfNo }?.let { model ->
model.weight = weight.grams.toDouble()
val pos = list.indexOf(model)
shelfAdapter.notifyItemChanged(pos)
log("getWeightInfo: deviceNo=${model.deviceNo},realWeight=${weight.grams}")
EventBus.getDefault().post(SendWeightEvent(model.deviceNo, weight.grams))
}
}
}
}
.onTempReport { _ ->
updateLeftStatus(deviceName)
}
.onLog { message -> log(message) }
.start { data ->
lastDataReceivedTime = System.currentTimeMillis()
noDataWarningDialog?.takeIf { it.isShowing }?.dismiss()
log("receiveSerialPortData: $data")
}
log("onCreate: openState=$ok")
}
}
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
EventBus.getDefault().register(this)
setBackground()
setContentView(binding.root)
onBackPressedDispatcher.addCallback(this) {
}
updateLeftStatus("")
initRecyclerView()
// 初始化 ScaleManager:注册回调、打开串口、启动接收
initScaleManager()
// 网络状态监听,独立协程,与串口初始化并行执行
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.getAccessTokenUiState.collect { state ->
when (state) {
is UiState.Loading -> {}
is UiState.Success<*> -> getTokenSuccess(state.data)
is UiState.Error -> toast(state.msg)
else -> {}
}
}
}
launch {
viewModel.getShelfListUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateUI(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
launch {
viewModel.updateOverdueStateUiState.collect { state ->
if (state is UiState.Success<*>) {
val respData = state.data as? RespData<*>
if (respData == null) {
log("updateOverdueState: 数据类型错误,期望 RespData")
return@collect
}
val shelfResult = respData.data as? ShelfResult
if (shelfResult == null) {
log("updateOverdueState: 数据类型错误,期望 ShelfResult")
return@collect
}
val items = shelfResult.containerGoodsList
if (items.isNullOrEmpty()) {
log("updateOverdueState: 货架列表为空")
return@collect
}
applyOverdueState(items)
}
}
}
}
}
binding.root.postDelayed({
sendCmd(START_TEMP_CTRL_CMD)
sendCmd(C_TEMP_CMD)
}, 5000)
saveGoodsTask()
overdueTask()
viewModel.getShelfList(deviceId = App.deviceId)
}
private val defDeviceList = listOf(
//原410货柜
"7a991439-3a12-3ef7-809b-c0258b839473",
//原1楼餐厅货柜
"4787e213-90ab-3e32-88e0-ac271a937751"
)
private fun getTokenSuccess(data: RespData<*>) {
log("getTokenSuccess: $data")
data.data?.let {
App.accessToken = it.toString()
viewModel.getShelfList(deviceId = App.deviceId)
}
}
private fun showProgress() {
Loading.show(this)
}
@SuppressLint("NotifyDataSetChanged")
private fun updateUI(data: RespData<*>) {
binding.include?.root?.gone()
window?.decorView?.postDelayed({ Loading.dismiss() }, 500)
val shelfResult = data.data as? ShelfResult
if (shelfResult == null) {
log("updateUI: 数据类型错误,期望 ShelfResult,实际类型:${data.data?.javaClass?.simpleName}")
loadEmptyView()
return
}
deviceName = shelfResult.deviceName
updateLeftStatus(deviceName)
App.canteenId = shelfResult.placeId
var tempList = shelfResult.containerGoodsList
if (tempList.isNullOrEmpty()) {
loadEmptyView()
return
}
list.clear()
tempList = GridLayoutTool.initListSort(tempList)
list.addAll(tempList)
shelfAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
Loading.dismiss()
toast(message)
loadEmptyView()
}
private var tipDialog: CommonDialog? = null
/** 最后一次成功收到串口数据的时间戳(毫秒) */
private var lastDataReceivedTime = System.currentTimeMillis()
/** 无数据超时提示弹窗 */
private var noDataWarningDialog: CommonDialog? = null
/**
* 无数据看门狗:每30秒检查一次,若超过5分钟未收到数据则弹出提示。
* 收到数据后会自动重置计时并关闭弹窗。
*/
private val noDataWatchdogTask = object : Runnable {
override fun run() {
// 生命周期检查,防止 Activity 销毁后继续执行
if (isFinishing || isDestroyed) return
val elapsed = System.currentTimeMillis() - lastDataReceivedTime
if (elapsed >= 5 * 60 * 1000L) {
if (noDataWarningDialog == null) {
noDataWarningDialog = CommonDialog(this@HomeActivity).apply {
dialogTitle = "设备提示"
dialogContent =
"长时间未收到数据,若无法开门或秤重量不更新情况,请考虑断电重启设备"
}
}
if (noDataWarningDialog?.isShowing == false) {
noDataWarningDialog?.show()
}
}
handler.postDelayed(this, 30 * 1000L)
}
}
/**
* 用户超时无操作时关灯
*/
override fun onUserInactive() {
switchLight(false)
}
override fun onDestroy() {
// 清理 Handler 所有回调,防止内存泄漏
handler.removeCallbacksAndMessages(null)
// 关闭并释放 Dialog 引用,防止内存泄漏
tipDialog?.dismiss()
tipDialog = null
noDataWarningDialog?.dismiss()
noDataWarningDialog = null
runBlocking { ScaleManager.release() }
saveTaskJob?.cancel()
overdueTaskJob?.cancel()
EventBus.getDefault().unregister(this)
super.onDestroy()
}
private val shelfAdapter by lazy {
ShelfAdapter(list).apply {
setOnItemClickListener { _, _, position ->
onShelfItemClicked(position)
}
}
}
/**
* 处理货架格子点击事件:按需开锁,然后跳转 ShelfActivity
*/
private fun onShelfItemClicked(position: Int) {
shelfIndex = position
lifecycleScope.launch {
if (isNeedOpenLock()) {
val openCmd = if (list[position].deviceNo in 1..5)
ScaleManager.buildOpenLock1Cmd()
else
ScaleManager.buildOpenLock2Cmd()
ScaleManager.sendCmd(openCmd)
}
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
}) { result ->
result?.let { onShelfActivityResult(position, it) }
}
}
}
/**
* 处理 ShelfActivity 返回结果:更新本地列表数据并同步到服务端
*/
private fun onShelfActivityResult(position: Int, intent: Intent) {
@Suppress("DEPRECATION")
val model = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra(ShelfActivity.SHELF_MODEL, ShelfModel::class.java)
} else {
intent.getSerializableExtra(ShelfActivity.SHELF_MODEL) as? ShelfModel
} ?: return
list[position].apply {
goodsId = model.goodsId
goodsName = model.goodsName
weight = model.weight
if (model.putCabinetDay.isNullOrBlank().not()) {
putCabinetDay = model.putCabinetDay
}
}
shelfAdapter.notifyItemChanged(position)
syncShelfGoodsToServer()
}
private fun initRecyclerView() {
val layoutManager = GridLayoutTool.getLayoutManager(this@HomeActivity) ?: return
binding.rvShelf.let {
it.layoutManager = layoutManager
it.adapter = shelfAdapter
it.itemAnimator.let {
if (it is DefaultItemAnimator) {
it.supportsChangeAnimations = false
}
}
}
}
private val handler = Handler(Looper.getMainLooper())
override fun onResume() {
super.onResume()
hideStatusBar()
// 启动无数据看门狗(每30秒检查一次)
handler.postDelayed(noDataWatchdogTask, 30 * 1000L)
}
override fun onPause() {
super.onPause()
// 停止无数据看门狗
handler.removeCallbacks(noDataWatchdogTask)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
shelfAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
viewModel.getShelfList(deviceId = App.deviceId)
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_white)
it.tvEmptyContent.setTextColor(Color.WHITE)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun clearEmptyShelf(event: ClearShelfEvent) {
val item = list.firstOrNull { it.deviceNo == event.shelfNo }
item?.apply {
goodsId = ""
goodsName = null
weight = 0.0
weightBak = if (event.weight <= 0.0) 0.0 else -1 * event.weight.toDouble()
createTime = null
overdueDay = null
}
shelfAdapter.notifyItemChanged(list.indexOf(item))
}
private val taskExecutor by lazy { IntervalExecutor() }
private var saveTaskJob: Job? = null
/**
* 保存定时任务
*/
private fun saveGoodsTask() {
saveTaskJob =
taskExecutor.startIntervalTaskWithInitialDelay(1 * 60 * 1000L, 5 * 60 * 1000L) {
syncShelfGoodsToServer()
}
}
private var overdueTaskJob: Job? = null
/**
* 过期时间定时任务
*/
private fun overdueTask() {
overdueTaskJob = taskExecutor.startIntervalTaskWithInitialDelay(5 * 1000L, 5 * 60 * 1000L) {
updateOverdueState()
}
}
private fun updateOverdueState() {
viewModel.updateOverdueState()
}
/**
* 将接口返回的过期状态应用到本地列表,仅在状态变化时刷新对应 item
*/
private fun applyOverdueState(items: List<ShelfModel>) {
if (items.size != list.size) return
list.forEachIndexed { index, model ->
// 接口过期状态变化后更新状态
if (items[index].overdue != model.overdue) {
model.overdue = items[index].overdue
shelfAdapter.notifyItemChanged(index)
log("更新编号为${model.deviceNo}物品:${model.goodsName}的过期状态:${model.overdue}")
}
}
}
/**
* 构建 ShelfBody 并提交到服务端(被定时任务与 ShelfActivity 返回共用)
*/
private fun syncShelfGoodsToServer() {
log("performSync显示list:${list.toJsonString()}")
val submitList = GridLayoutTool.submitListSort(list)
submitList.forEach {
if (it.goodsId.isNullOrBlank()) {
it.weight = it.weightBak
}
}
val body = ShelfBody().also {
it.deviceId = App.deviceId ?: ""
it.canteenId = App.canteenId
it.temperature = showTemperatureC
it.humidity = showHumidity
it.goodsList = submitList
}
log("performSync提交body:${body.toJsonString()}")
viewModel.saveShelfGoodsList(body)
}
}
@@ -1,455 +0,0 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.addCallback
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.adapter.ShelfV2Adapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityHomeBinding
import com.shuwei.intelligent.shelves.dialog.CommonDialog
import com.shuwei.intelligent.shelves.model.CabinetInitResult
import com.shuwei.intelligent.shelves.model.ClearShelfEvent
import com.shuwei.intelligent.shelves.model.ShelfModelV2
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.model.SyncBody
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.NetViewModelV2
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.serial.ProtocolConstants
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.C_TEMP_CMD
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.START_TEMP_CTRL_CMD
import com.shuwei.intelligent.shelves.serial.ScaleManager
import com.shuwei.intelligent.shelves.utils.GridLayoutTool
import com.shuwei.intelligent.shelves.utils.IntervalExecutor
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class HomeV2Activity : BaseActivity() {
companion object {}
override val enableRightStatusMenu: Boolean = true
// 货架列表数据
private val list: MutableList<ShelfModelV2> = mutableListOf()
// 当前选中货架下标
private var shelfIndex = 0
// 当前温度(摄氏)
private var showTemperatureC = "0"
// 当前湿度
private var showHumidity = "0"
// 仅特定 deviceId 的部署点需要发送开锁指令
private fun isNeedOpenLock() = App.deviceId == "4787e213-90ab-3e32-88e0-ac271a937751"
private var deviceName = ""
private lateinit var binding: ActivityHomeBinding
private val viewModel: NetViewModel by viewModels()
private val viewModelV2: NetViewModelV2 by viewModels()
// 串口协议解析器,通过回调通知本 Activity
private fun initScaleManager() {
lifecycleScope.launch {
val ok = ScaleManager
.init(activateCode = ProtocolConstants.ACTIVE_MAP[App.deviceId] ?: "")
.onHeartbeat { _, data ->
data.weights.forEach { weight ->
val shelfNo = weight.index
// if (shelfNo in 1..list.size) {
// 用 slotNo 末尾数字与串口编号匹配
val pos = list.indexOfFirst {
it.slotNo?.filter { c -> c.isDigit() }?.toIntOrNull() == shelfNo
}
val model = list.getOrNull(pos)?:return@forEach
model.weight = (1.0 * weight.grams / 1000.0).toBigDecimal()
// if (model.itemType == 1) {
// model.actualQty = (weight.grams / 1000.0).toBigDecimal()
// } else {
// model.totalWeightG = weight.grams.toBigDecimal()
// }
shelfAdapter.notifyItemChanged(pos)
log("getWeightInfo: slotNo=${model.slotNo}, realWeight=${weight.grams}, temp=${data.temperature}, item:${model.toJsonString()}")
EventBus.getDefault().post(SendWeightEvent(shelfNo, weight.grams))
// }
}
}
.onTempReport { frame ->
log("onTempReport: $frame")
updateLeftStatus(deviceName)
}
.onLog { message -> log(message) }
.start { data ->
lastDataReceivedTime = System.currentTimeMillis()
noDataWarningDialog?.takeIf { it.isShowing }?.dismiss()
log("receiveSerialPortData: $data")
}
log("onCreate: openState=$ok")
}
}
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
EventBus.getDefault().register(this)
setBackground()
setContentView(binding.root)
onBackPressedDispatcher.addCallback(this) {
}
updateLeftStatus("")
// 初始化 ScaleManager:注册回调、打开串口、启动接收
initScaleManager()
// 网络状态监听,独立协程,与串口初始化并行执行
lifecycleScope.launch {
viewModelV2.initCabinetUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateUI(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
binding.root.postDelayed({
sendCmd(START_TEMP_CTRL_CMD)
sendCmd(C_TEMP_CMD)
}, 5000)
saveGoodsTask()
overdueTask()
viewModelV2.initCabinet(cabinetId = App.deviceId)
}
private val defDeviceList = listOf(
//原410货柜
"7a991439-3a12-3ef7-809b-c0258b839473",
//原1楼餐厅货柜
"4787e213-90ab-3e32-88e0-ac271a937751"
)
private fun getTokenSuccess(data: RespData<*>) {
log("getTokenSuccess: $data")
data.data?.let {
App.accessToken = it.toString()
viewModelV2.initCabinet(cabinetId = App.deviceId)
}
}
private fun showProgress() {
Loading.show(this)
}
private var cabinetInitResult: CabinetInitResult? = null
@SuppressLint("NotifyDataSetChanged")
private fun updateUI(data: RespData<*>) {
binding.include.root.gone()
window?.decorView?.postDelayed({ Loading.dismiss() }, 500)
cabinetInitResult = data.data as? CabinetInitResult
if (cabinetInitResult == null) {
log("updateUI: 数据类型错误,期望 CabinetInitResult,实际类型:${data.data?.javaClass?.simpleName}")
loadEmptyView()
return
}
val cabinet = cabinetInitResult!!
//行列数量后端返回是反着的
val columns = cabinet.horizontalRows
val rows = cabinet.verticalCount
log("updateUI: rows=$rows, columns=$columns")
deviceName = cabinet.deviceName
updateLeftStatus(deviceName)
App.canteenId = cabinet.canteenId.toString()
var tempList = cabinet.containerGoodsList
if (tempList.isNullOrEmpty()) {
loadEmptyView()
return
}
list.clear()
tempList = GridLayoutTool.initListSortV2(rows = rows, columns = columns, source = tempList)
list.addAll(tempList)
if (binding.rvShelf.adapter == null) {
initRecyclerView(columns)
} else {
shelfAdapter.notifyDataSetChanged()
}
}
private fun showError(message: String) {
Loading.dismiss()
toast(message)
loadEmptyView()
}
private var tipDialog: CommonDialog? = null
/** 最后一次成功收到串口数据的时间戳(毫秒) */
private var lastDataReceivedTime = System.currentTimeMillis()
/** 无数据超时提示弹窗 */
private var noDataWarningDialog: CommonDialog? = null
/**
* 无数据看门狗:每30秒检查一次,若超过5分钟未收到数据则弹出提示。
* 收到数据后会自动重置计时并关闭弹窗。
*/
private val noDataWatchdogTask = object : Runnable {
override fun run() {
// 生命周期检查,防止 Activity 销毁后继续执行
if (isFinishing || isDestroyed) return
val elapsed = System.currentTimeMillis() - lastDataReceivedTime
if (elapsed >= 5 * 60 * 1000L) {
if (noDataWarningDialog == null) {
noDataWarningDialog = CommonDialog(this@HomeV2Activity).apply {
dialogTitle = "设备提示"
dialogContent =
"长时间未收到数据,若无法开门或秤重量不更新情况,请考虑断电重启设备"
}
}
if (noDataWarningDialog?.isShowing == false) {
noDataWarningDialog?.show()
}
}
handler.postDelayed(this, 30 * 1000L)
}
}
/**
* 用户超时无操作时关灯
*/
override fun onUserInactive() {
switchLight(false)
}
override fun onDestroy() {
// 清理 Handler 所有回调,防止内存泄漏
handler.removeCallbacksAndMessages(null)
// 关闭并释放 Dialog 引用,防止内存泄漏
tipDialog?.dismiss()
tipDialog = null
noDataWarningDialog?.dismiss()
noDataWarningDialog = null
runBlocking { ScaleManager.release() }
saveTaskJob?.cancel()
overdueTaskJob?.cancel()
EventBus.getDefault().unregister(this)
super.onDestroy()
}
private val shelfAdapter by lazy {
ShelfV2Adapter(list).apply {
setOnItemClickListener { _, _, position ->
onShelfItemClicked(position)
}
}
}
/**
* 处理货架格子点击事件:按需开锁,然后跳转 ShelfV2Activity
*/
private fun onShelfItemClicked(position: Int) {
shelfIndex = position
lifecycleScope.launch {
if (isNeedOpenLock()) {
// 解析 slotNo 末尾数字判断锁区(1-5 号格口走锁1,其余走锁2)
val slotNum = list[position].slotNo?.filter { it.isDigit() }?.toIntOrNull() ?: 0
val openCmd = if (slotNum in 1..5)
ScaleManager.buildOpenLock1Cmd()
else
ScaleManager.buildOpenLock2Cmd()
ScaleManager.sendCmd(openCmd)
}
launch(Intent(this@HomeV2Activity, ShelfV2Activity::class.java).also {
it.putExtra(ShelfV2Activity.SHELF_MODEL, list[position])
}) { result ->
result?.let { onShelfActivityResult(position, it) }
}
}
}
/**
* 处理 ShelfV2Activity 返回结果
* TODO: 待 ShelfV2Activity 迁移到 RecordItem 后重新实现字段更新逻辑
*/
private fun onShelfActivityResult(position: Int, intent: Intent) {
@Suppress("DEPRECATION")
val model = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra(ShelfV2Activity.SHELF_MODEL, ShelfModelV2::class.java)
} else {
intent.getSerializableExtra(ShelfV2Activity.SHELF_MODEL) as? ShelfModelV2
} ?: return
list[position].let {
it.itemType = model.itemType
it.itemName = model.itemName
it.traceCode = model.traceCode
it.cleanOrderNo = model.cleanOrderNo
it.spec = model.spec
it.foodId = model.foodId
it.storeTime = model.storeTime
it.slotStatus = model.slotStatus
}
shelfAdapter.notifyItemChanged(position)
// syncShelfGoodsToServer()
}
private fun initRecyclerView(columns: Int) {
val layoutManager = GridLayoutTool.getLayoutManagerV2(this, columns = columns)
binding.rvShelf.let {
it.layoutManager = layoutManager
it.adapter = shelfAdapter
it.itemAnimator.let { animator ->
if (animator is DefaultItemAnimator) {
animator.supportsChangeAnimations = false
}
}
}
}
private val handler = Handler(Looper.getMainLooper())
override fun onResume() {
super.onResume()
hideStatusBar()
// 启动无数据看门狗(每30秒检查一次)
handler.postDelayed(noDataWatchdogTask, 30 * 1000L)
}
override fun onPause() {
super.onPause()
// 停止无数据看门狗
handler.removeCallbacks(noDataWatchdogTask)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
shelfAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
viewModelV2.initCabinet(cabinetId = App.deviceId)
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_white)
it.tvEmptyContent.setTextColor(Color.WHITE)
}
// loadTestData()
}
// @SuppressLint("NotifyDataSetChanged")
// private fun loadTestData() {
// binding.rvShelf.layoutManager = GridLayoutManager(this, 2)
// binding.rvShelf.adapter = shelfAdapter
// binding.include.root.gone()
// list.clear()
// var tempList = (1..10).map { i -> ShelfModelV2(slotNo = i.toString()) }
// tempList = GridLayoutTool.initListSortV2(tempList)
// list.addAll(tempList)
// shelfAdapter.notifyDataSetChanged()
// }
@Subscribe(threadMode = ThreadMode.MAIN)
fun clearEmptyShelf(event: ClearShelfEvent) {
// 用 slotNo 末尾数字与串口编号匹配
val index = list.indexOfFirst {
it.slotNo?.filter { c -> c.isDigit() }?.toIntOrNull() == event.shelfNo
}
if (index < 0) return
list[index] = list[index].copy(itemName = "")
shelfAdapter.notifyItemChanged(index)
}
private val taskExecutor by lazy { IntervalExecutor() }
private var saveTaskJob: Job? = null
/**
* 保存定时任务
*/
private fun saveGoodsTask() {
saveTaskJob =
// taskExecutor.startIntervalTaskWithInitialDelay(1 * 60 * 1000L, 5 * 60 * 1000L) {
taskExecutor.startIntervalTaskWithInitialDelay(10 * 1000L, 2 * 60 * 1000L) {
syncShelfGoodsToServer()
}
}
private var overdueTaskJob: Job? = null
/**
* 过期时间定时任务
*/
private fun overdueTask() {
overdueTaskJob = taskExecutor.startIntervalTaskWithInitialDelay(5 * 1000L, 5 * 60 * 1000L) {
viewModelV2.initCabinet(cabinetId = App.deviceId)
}
}
/**
* 将接口返回的临期状态应用到本地列表,仅在状态变化时刷新对应 item
*/
private fun applyOverdueState(items: List<ShelfModelV2>) {
if (items.size != list.size) return
items.forEachIndexed { index, newItem ->
val oldItem = list[index]
// slotStatus=2 表示临期预警
if (oldItem.slotStatus != newItem.slotStatus) {
list[index] = newItem
shelfAdapter.notifyItemChanged(index)
log("更新格口 ${newItem.slotNo} 物品:${newItem.itemName} 的状态:${newItem.slotStatus}")
}
}
}
/**
* 构建同步请求并提交到服务端
*/
private fun syncShelfGoodsToServer() {
if (cabinetInitResult == null) {
return
}
val submitList = GridLayoutTool.submitListSortV2(list)
viewModelV2.syncCabinetData(SyncBody().apply {
// cabinetId = list[0].cabinetId
cabinetId = App.deviceId
temperature = null
humidity = null
goodsList = submitList
})
}
}
@@ -1,66 +0,0 @@
//package com.shuwei.intelligent.shelves.activity
//
//import android.os.Bundle
//import android.os.Handler
//import android.os.Looper
//import com.shuwei.intelligent.shelves.base.BaseActivity
//import com.shuwei.intelligent.shelves.databinding.ActivitySettingBinding
//import com.shuwei.intelligent.shelves.utils.KeyboardUtil
//import java.text.SimpleDateFormat
//import java.util.Date
//import java.util.Locale
//
//class SettingActivity: BaseActivity() {
//
// private lateinit var binding: ActivitySettingBinding
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// binding = ActivitySettingBinding.inflate(layoutInflater)
// setBackground()
// setContentView(binding.root)
// binding.btnSendCmd.setOnClickListener{
// val cmd = binding.etInputCmd.text.toString().trim()
// if (cmd.isBlank()) {
// return@setOnClickListener
// }
// var record = binding.tvCmdRecord.text.toString().trim()
// record = cmd+"\n"+record
// binding.tvCmdRecord.text = record
// val realCmd = "${HomeActivity.HEADER}0F01${cmd}${HomeActivity.FOOTER}"
// sendCmd(realCmd)
// }
// binding.ivBack.setOnClickListener { finish() }
// binding.root.setOnClickListener { v ->
// KeyboardUtil.hideKeyboard(v)
// }
// }
//
//
// private fun updateDateTime() {
// val sdf = SimpleDateFormat(ShelfActivity.YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA)
// val dateTime = sdf.format(Date())
// val arr = dateTime.split("***")
// updateLeftStatus(arr[0])
// updateRightStatus(arr[1])
// }
//
// private val handler = Handler(Looper.getMainLooper())
// private val updateTask = object : Runnable {
// override fun run() {
// updateDateTime()
// handler.postDelayed(this, 1000)
// }
// }
//
// override fun onResume() {
// super.onResume()
// hideStatusBar()
// handler.post(updateTask)
// }
//
// override fun onPause() {
// super.onPause()
// handler.removeCallbacks(updateTask)
// }
//
//}
@@ -1,380 +0,0 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.LinearLayout
import androidx.activity.viewModels
import androidx.core.view.updateLayoutParams
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.scwang.smart.refresh.layout.constant.RefreshState
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.serial.ScaleManager
import com.shuwei.intelligent.shelves.adapter.SearchAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding
import com.shuwei.intelligent.shelves.model.ClearShelfEvent
import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.KeyboardUtil
import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
import com.shuwei.intelligent.shelves.utils.ext.dp
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@SuppressLint("NotifyDataSetChanged")
class ShelfActivity : BaseActivity() {
companion object {
private const val TAG = "ShelfActivity"
const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
const val SHELF_MODEL = "shelfModel"
const val RECEIVER_DEVICE_INFO = "receiverDeviceInfo"
const val SHELF_WEIGHT = "shelfWeight"
const val SHELF_INDEX = "shelfIndex"
const val PAGE_SIZE = 100
}
private lateinit var binding: ActivityShelfBinding
private var shelfModel: ShelfModel? = null
private var clickIndex: Int = -1
private var realWeight: Int = 0
private var pageNo: Int = 1
private val viewModel: NetViewModel by viewModels()
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShelfBinding.inflate(layoutInflater)
setBackground()
setContentView(binding.root)
EventBus.getDefault().register(this)
// tvFoodWeight =binding.tvFoodWeight
shelfModel = intent.getSerializableExtra(SHELF_MODEL) as ShelfModel?
shelfModel?.let {
binding.tvShelfName.text = it.shelfName
binding.tvFoodName.text = if (it.goodsName.isNullOrBlank()) "-" else it.goodsName
realWeight = it.weight?.toInt()?:0
binding.tvFoodWeight.text = if(realWeight < 1000) "${realWeight}" else "%.3f千克".format(realWeight/1000.0)
startTime = System.currentTimeMillis()
}
initRecyclerView()
binding.etInputFood.run {
addOnActionSearchListener {
if (text.isNullOrBlank()) {
toast(hint.toString())
return@addOnActionSearchListener
}
pageNo = 1
getGoodsList()
KeyboardUtil.hideKeyboard(context, this)
}
addTextChangedListener(afterTextChanged = { editable ->
if (editable.isNullOrBlank()) {
pageNo = 1
getGoodsList()
}
})
}
binding.btnConfirm.clickWithDebounce {
if (clickIndex < 0) {
toast("请选择食材")
return@clickWithDebounce
}
val currentGoodsId = list[clickIndex].goodsId ?: ""
val data = ShelfModel(
shelfName = shelfModel?.shelfName,
goodsId = currentGoodsId,
goodsName = list[clickIndex].goodsName,
weight = realWeight.toDouble(),
overdueDay = list[clickIndex].overdueDay,
// createTime = if (shelfModel?.goodsId == currentGoodsId) null else DateTimeUtil.formatDateTime(dateTime = Date())
putCabinetDay = DateTimeUtil.formatDateTime(dateTime = Date())
)
val intent = Intent().apply {
putExtra(SHELF_MODEL, data)
}
setResult(RESULT_OK, intent)
finish()
}
binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v)
}
binding.btnClearZero.setOnClickListener {
clearZero()
}
binding.btnClearEmpty.setOnClickListener {
val tempWeight = shelfModel?.weight?.toInt()?:0
clearZero()
binding.tvFoodName.text = "-"
//binding.tvFoodWeight.text = "0克"
EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo, tempWeight))
}
sendCmd(ScaleManager.buildGetStatusCmd())
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.getGoodsListUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateUI(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
}
// registerReceiver(receiver, IntentFilter(RECEIVER_DEVICE_INFO + shelfModel?.deviceNo))
binding.ivBack?.setOnClickListener { finish() }
binding.refreshLayout.let {
it.setEnableRefresh(true)
it.setEnableLoadMore(false)
it.setOnRefreshListener {
pageNo = 1
getGoodsList()
}
it.setOnLoadMoreListener { getGoodsList() }
}
getGoodsList()
// TaskManager.startDeviceTask()
}
private fun finishRefresh() {
binding.refreshLayout.let {
if (it.state == RefreshState.Refreshing) {
it.finishRefresh(500)
} else if (binding.refreshLayout.state == RefreshState.Loading) {
it.finishLoadMore(500)
} else {
}
}
}
private fun getGoodsList() {
viewModel.getGoodsList(
canteenId = App.canteenId,
goodsName = binding.etInputFood.text.trim().toString(),
pageNo = pageNo,
pageSize = PAGE_SIZE
)
}
private fun showProgress() {
Loading.show(this)
}
@SuppressLint("NotifyDataSetChanged")
private fun updateUI(data: RespData<*>) {
binding.include?.root?.gone()
finishRefresh()
binding.root.postDelayed({ Loading.dismiss() }, 200)
Log.d(TAG, "updateUI: ${data.toJsonString()}")
val goodsList = data.data as MutableList<GoodsModel>
if (data.data.isNullOrEmpty()) {
if (pageNo == 1) {
loadEmptyView()
}
return
}
if(pageNo == 1){
list.clear()
}
// var firstOne:GoodsModel?=null
// if (pageNo == 1) {
// if (shelfModel?.goodsId.isNullOrBlank().not()) {
// firstOne = list.firstOrNull { it.goodsId == shelfModel?.goodsId }
// if (firstOne == null) {
// firstOne = GoodsModel(
// goodsId = shelfModel?.goodsId,
// goodsName = shelfModel?.goodsName,
// isClicked = true
// )
// }
// }
// list.clear()
// }
// val records = data.data.records
// if (pageNo == 1) {
// firstOne?.let { one ->
// val filterResult = records.firstOrNull{one.goodsId == it.goodsId }
// if (filterResult != null) {
// records.remove(filterResult)
// }
// clickIndex = 0
// list.add(one)
// }
// }
list.addAll(goodsList)
val enableLoadMore = goodsList.size >= PAGE_SIZE
binding.refreshLayout.setEnableLoadMore(enableLoadMore)
if (enableLoadMore) {
pageNo++
}
searchAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
finishRefresh()
Loading.dismiss()
toast(message)
if (pageNo == 1) {
loadEmptyView()
}
}
// private val receiver = object : BroadcastReceiver() {
// override fun onReceive(context: Context?, intent: Intent?) {
// intent?.let {
// if (it.action == RECEIVER_DEVICE_INFO + shelfModel?.deviceNo) {
// val weight = it.getDoubleExtra(SHELF_WEIGHT, 0.0)
// Log.d(TAG, "getDeviceInfo:onReceive: weight=$weight")
// realWeight = (weight * 1000).toInt()
// binding.tvFoodWeight.text = "${realWeight}克"
// }
// }
// }
// }
override fun onDestroy() {
// unregisterReceiver(receiver)
EventBus.getDefault().unregister(this)
// TaskManager.cancelDeviceTask()
super.onDestroy()
}
private val list: MutableList<GoodsModel> = mutableListOf()
private val searchAdapter by lazy {
SearchAdapter(list).apply {
setOnDebouncedItemClick { adapter, v, position ->
this@ShelfActivity.clickIndex = position
list.forEach { it.isClicked = false }
list[position].isClicked = true
notifyDataSetChanged()
binding.tvFoodName.text = list[position].goodsName
KeyboardUtil.hideKeyboard(v.context, v)
}
}
}
private fun initRecyclerView() {
binding.rvSearch.run {
layoutManager = GridLayoutManager(this@ShelfActivity, 2)
adapter = searchAdapter
}
}
/**
* 覆写时钟回调:左侧状态栏显示年月日星期,右侧状态栏显示时分秒
*/
override fun onClockTick() {
val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA)
val arr = sdf.format(Date()).split("***")
updateLeftStatus(arr[0])
updateRightStatus(arr[1])
}
override fun onResume() {
super.onResume()
hideStatusBar()
}
override fun onPause() {
super.onPause()
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
searchAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
pageNo = 1
getGoodsList()
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_gray)
it.ivEmptyIcon.updateLayoutParams<LinearLayout.LayoutParams> {
val swDpValue = resources?.configuration?.smallestScreenWidthDp ?: 0
width = if (swDpValue > 600) 200.dp else 120.dp
}
}
binding.refreshLayout.run {
setEnableRefresh(false)
setEnableLoadMore(false)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun receiveWeightEvent(event: SendWeightEvent) {
if (event.shelfNo == (shelfModel?.deviceNo ?: 0)) {
val intervalTime = System.currentTimeMillis() - startTime
val weight = event.weight
log("编号${event.shelfNo}货架获取重量成功:${weight}克,数据获取间隔时间为:${intervalTime}")
binding.tvFoodWeight.text = if(weight < 1000) "${weight}" else "%.3f千克".format(weight/1000.0)
startTime = System.currentTimeMillis()
window.decorView.postDelayed({ Loading.dismiss() }, 1000)
}
}
private fun clearZero() {
lifecycleScope.launch {
val shelfNo = shelfModel?.deviceNo ?: 0
val zeroClearingCmd = ScaleManager.buildScaleZeroCmd(shelfNo)
log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}")
sendCmd(zeroClearingCmd)
// sendCmd(
// if (shelfNo in 1..5) ProtocolConstants.LEFT_SHELF_OPEN_CMD else ProtocolConstants.RIGHT_SHELF_OPEN_CMD
// )
//清零
realWeight = 0
// binding.tvFoodWeight.text = "${realWeight}克"
sendCmd(ScaleManager.buildGetStatusCmd())
startTime = System.currentTimeMillis()
Loading.show(this@ShelfActivity)
window.decorView.postDelayed({ Loading.dismiss() }, 10000)
}
KeyboardUtil.hideKeyboard(window.decorView)
}
private var startTime = 0L
}
@@ -1,384 +0,0 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.widget.LinearLayout
import androidx.activity.viewModels
import androidx.core.view.updateLayoutParams
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.scwang.smart.refresh.layout.constant.RefreshState
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.adapter.CleanPackageAdapter
import com.shuwei.intelligent.shelves.adapter.MealPackageAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityShelfV2Binding
import com.shuwei.intelligent.shelves.model.CleanPackageOption
import com.shuwei.intelligent.shelves.model.MealPackageOption
import com.shuwei.intelligent.shelves.model.ShelfModelV2
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModelV2
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.serial.ScaleManager
import com.shuwei.intelligent.shelves.utils.KeyboardUtil
import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
import com.shuwei.intelligent.shelves.utils.ext.dp
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@SuppressLint("NotifyDataSetChanged")
class ShelfV2Activity : BaseActivity() {
companion object {
const val SHELF_MODEL = "shelfModel"
const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
/** itemType=1:净菜包 */
private const val TYPE_CLEAN = 1
/** itemType=2:餐品净菜包 */
private const val TYPE_MEAL = 2
}
private lateinit var binding: ActivityShelfV2Binding
private var recordItem: ShelfModelV2? = null
private var realWeight: Int = 0
private var startTime = 0L
private val viewModel: NetViewModelV2 by viewModels()
// 当前选中类型,默认净菜包
private var currentType = TYPE_CLEAN
// 净菜包列表与 Adapter
private val cleanList: MutableList<CleanPackageOption> = mutableListOf()
private val cleanAdapter by lazy {
CleanPackageAdapter(cleanList).apply {
setOnDebouncedItemClick { _, _, position ->
cleanList.forEachIndexed { index, option -> option.isSelected = index == position }
notifyDataSetChanged()
binding.tvFoodName.text = cleanList[position].itemName
KeyboardUtil.hideKeyboard(this@ShelfV2Activity.window.decorView)
}
}
}
// 餐品净菜包列表与 Adapter
private val mealList: MutableList<MealPackageOption> = mutableListOf()
private val mealAdapter by lazy {
MealPackageAdapter(mealList).apply {
setOnDebouncedItemClick { _, _, position ->
mealList.forEachIndexed { index, option -> option.isSelected = index == position }
notifyDataSetChanged()
binding.tvFoodName.text = mealList[position].itemName
KeyboardUtil.hideKeyboard(this@ShelfV2Activity.window.decorView)
}
}
}
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShelfV2Binding.inflate(layoutInflater)
setBackground()
setContentView(binding.root)
EventBus.getDefault().register(this)
recordItem = intent.getSerializableExtra(SHELF_MODEL) as? ShelfModelV2
recordItem?.let {
binding.tvShelfName.text = it.slotNo
binding.tvFoodName.text = if (it.itemName.isNullOrBlank()) "-" else it.itemName
val weightG = it.weight?.toDouble() ?: 0.0
realWeight = (weightG * 1000.0).toInt()
binding.tvFoodWeight.text = if (weightG >= 1.0) "%.3f千克".format(weightG)
else "${realWeight}"
startTime = System.currentTimeMillis()
}
initChipGroup()
initRecyclerView()
initSearchBox()
initButtons()
observeViewModel()
sendCmd(ScaleManager.buildGetStatusCmd())
loadList()
}
private fun initChipGroup() {
binding.chipGroupType.setOnCheckedStateChangeListener { _, checkedIds ->
currentType = when {
checkedIds.contains(R.id.chipClean) -> TYPE_CLEAN
checkedIds.contains(R.id.chipMeal) -> TYPE_MEAL
else -> currentType
}
// 切换时清空搜索框并重新加载列表
binding.etInputFood.setText("")
switchAdapter()
loadList()
}
}
private fun switchAdapter() {
val adapter: BaseQuickAdapter<*, *> =
if (currentType == TYPE_CLEAN) cleanAdapter else mealAdapter
binding.rvSearch.adapter = adapter
}
private fun initRecyclerView() {
binding.rvSearch.run {
layoutManager = GridLayoutManager(this@ShelfV2Activity, 2)
adapter = cleanAdapter
}
binding.refreshLayout.run {
setEnableRefresh(true)
setEnableLoadMore(false)
setOnRefreshListener { loadList() }
}
}
private fun initSearchBox() {
binding.etInputFood.run {
addOnActionSearchListener {
loadList()
KeyboardUtil.hideKeyboard(context, this)
}
addTextChangedListener(afterTextChanged = { editable ->
if (editable.isNullOrBlank()) loadList()
})
}
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
}
private fun initButtons() {
binding.ivBack.setOnClickListener { finish() }
binding.btnClearZero.setOnClickListener {
Loading.show(this@ShelfV2Activity)
clearZero()
window.decorView.postDelayed({ Loading.dismiss() }, 10000)
}
binding.btnClearEmpty.setOnClickListener { v ->
clearZero()
binding.tvFoodName.text = "-"
Loading.show(this@ShelfV2Activity)
v.postDelayed({
Loading.dismiss()
// 3 秒后自动关闭 Activity 并回传空值
val emptyRecord = recordItem?.also {
it.itemName = ""
//4-空置
it.slotStatus = 4
// it.totalWeightG = null
// it.actualQty = null
it.weight = null
it.itemType = 0
it.traceCode = null
it.storeTime = null
it.spec = null
it.specLabel = null
it.cleanOrderNo = null
}
val result = Intent().apply {
putExtra(SHELF_MODEL, emptyRecord)
}
setResult(RESULT_OK, result)
finish()
}, 3000)
}
binding.btnConfirm.clickWithDebounce {
var shelfModelV2: ShelfModelV2? = null
if (currentType == TYPE_CLEAN) {
val item = cleanList.firstOrNull { it.isSelected }
if (item == null) {
toast("请选择净菜包"); return@clickWithDebounce
}
shelfModelV2 = item.toShelfModelV2(recordItem).also {
//1-存放中
it.slotStatus = 1
}
} else {
val item = mealList.firstOrNull { it.isSelected }
if (item == null) {
toast("请选择餐品净菜包"); return@clickWithDebounce
}
shelfModelV2 = item.toShelfModelV2(recordItem).also {
//1-存放中
it.slotStatus = 1
}
}
// 将选中结果回传给上层 Activity
val result = Intent().apply {
putExtra(SHELF_MODEL, shelfModelV2)
}
setResult(RESULT_OK, result)
finish()
}
}
private fun observeViewModel() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.cleanPackageOptionsUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateCleanList(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
launch {
viewModel.mealPackageOptionsUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateMealList(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
}
}
}
private fun loadList() {
val keyword = binding.etInputFood.text.trim().toString().ifBlank { null }
if (currentType == TYPE_CLEAN) {
viewModel.getCleanPackageOptions(keyword = keyword)
} else {
viewModel.getMealPackageOptions(keyword = keyword)
}
}
private fun showProgress() {
Loading.show(this)
}
@SuppressLint("NotifyDataSetChanged")
private fun updateCleanList(data: RespData<*>) {
finishRefresh()
binding.include?.root?.gone()
binding.root.postDelayed({ Loading.dismiss() }, 200)
@Suppress("UNCHECKED_CAST")
val items = data.data as? List<CleanPackageOption>
if (items.isNullOrEmpty()) {
loadEmptyView(); return
}
cleanList.clear()
cleanList.addAll(items)
cleanAdapter.notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
private fun updateMealList(data: RespData<*>) {
finishRefresh()
binding.include?.root?.gone()
binding.root.postDelayed({ Loading.dismiss() }, 200)
@Suppress("UNCHECKED_CAST")
val items = data.data as? List<MealPackageOption>
if (items.isNullOrEmpty()) {
loadEmptyView(); return
}
mealList.clear()
mealList.addAll(items)
mealAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
finishRefresh()
Loading.dismiss()
toast(message)
loadEmptyView()
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
cleanList.clear()
mealList.clear()
cleanAdapter.notifyDataSetChanged()
mealAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener { loadList() }
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_gray)
it.ivEmptyIcon.updateLayoutParams<LinearLayout.LayoutParams> {
val swDpValue = resources?.configuration?.smallestScreenWidthDp ?: 0
width = if (swDpValue > 600) 200.dp else 120.dp
}
}
binding.refreshLayout.run {
setEnableRefresh(false)
setEnableLoadMore(false)
}
}
private fun finishRefresh() {
binding.refreshLayout.let {
if (it.state == RefreshState.Refreshing) it.finishRefresh(500)
else if (it.state == RefreshState.Loading) it.finishLoadMore(500)
}
}
private fun clearZero() {
lifecycleScope.launch {
val slotNum = recordItem?.slotNo?.filter { it.isDigit() }?.toIntOrNull() ?: 0
val zeroClearingCmd = ScaleManager.buildScaleZeroCmd(slotNum)
log("格口${recordItem?.slotNo}执行清零指令:$zeroClearingCmd")
sendCmd(zeroClearingCmd)
realWeight = 0
sendCmd(ScaleManager.buildGetStatusCmd())
startTime = System.currentTimeMillis()
}
KeyboardUtil.hideKeyboard(window.decorView)
}
override fun onClockTick() {
val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA)
val arr = sdf.format(Date()).split("***")
updateLeftStatus(arr[0])
updateRightStatus(arr[1])
}
override fun onResume() {
super.onResume()
hideStatusBar()
}
override fun onDestroy() {
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun receiveWeightEvent(event: SendWeightEvent) {
val slotNum = recordItem?.slotNo?.filter { it.isDigit() }?.toIntOrNull() ?: return
if (event.shelfNo != slotNum) return
val intervalTime = System.currentTimeMillis() - startTime
val weight = event.weight
log("格口${recordItem?.slotNo}获取重量:${weight}克,间隔:${intervalTime}ms")
binding.tvFoodWeight.text = if (weight < 1000) "${weight}"
else "%.3f千克".format(weight / 1000.0)
startTime = System.currentTimeMillis()
window.decorView.postDelayed({ Loading.dismiss() }, 1000)
}
}
@@ -1,39 +0,0 @@
package com.shuwei.intelligent.shelves.adapter
import android.content.Context
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ListItemSearchBinding
import com.shuwei.intelligent.shelves.model.CleanPackageOption
class CleanPackageAdapter(list: MutableList<CleanPackageOption>) :
BaseQuickAdapter<CleanPackageOption, CleanPackageAdapter.VH>(list) {
inner class VH(val binding: ListItemSearchBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val binding = ListItemSearchBinding.inflate(LayoutInflater.from(context), parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: CleanPackageOption?) {
item ?: return
val isSelected = item.isSelected
holder.binding.btnFoodName.run {
text = " ${item.itemName}"
setTextColor(getColor(if (isSelected) R.color.shelf_name_blue else R.color.black999))
strokeColor = ColorStateList.valueOf(
(if (isSelected) "#4969F5" else "#E6E6E6").toColorInt()
)
}
}
private fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
}
@@ -1,41 +0,0 @@
package com.shuwei.intelligent.shelves.adapter
import android.content.Context
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ListItemSearchBinding
import com.shuwei.intelligent.shelves.model.MealPackageOption
class MealPackageAdapter(list: MutableList<MealPackageOption>) :
BaseQuickAdapter<MealPackageOption, MealPackageAdapter.VH>(list) {
inner class VH(val binding: ListItemSearchBinding) : QuickViewHolder(binding.root)
var selectedId: Long = -1L
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val binding = ListItemSearchBinding.inflate(LayoutInflater.from(context), parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: MealPackageOption?) {
item ?: return
val isSelected = item.isSelected
holder.binding.btnFoodName.run {
text = " ${item.itemName}"
setTextColor(getColor(if (isSelected) R.color.shelf_name_blue else R.color.black999))
strokeColor = ColorStateList.valueOf(
(if (isSelected) "#4969F5" else "#E6E6E6").toColorInt()
)
}
}
private fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
}
@@ -1,50 +0,0 @@
package com.shuwei.intelligent.shelves.adapter
import android.content.Context
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ListItemSearchBinding
import com.shuwei.intelligent.shelves.model.GoodsModel
class SearchAdapter(list: MutableList<GoodsModel>) :
BaseQuickAdapter<GoodsModel, SearchAdapter.VH>(list) {
inner class VH(var binding: ListItemSearchBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemSearchBinding.inflate(inflater, parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: GoodsModel?) {
val binding = holder.binding
binding.btnFoodName.run {
text = " ${item!!.goodsName}"
setTextColor(
getColor(if (item.isClicked) R.color.shelf_name_blue else R.color.black999)
)
val btnStrokeColor = if (item.isClicked) "#4969F5" else "#E6E6E6"
strokeColor = ColorStateList.valueOf(btnStrokeColor.toColorInt())
}
// val bgDrawable = createCustomDrawable(
// solidColor = "#FFFFFF",
// strokeWidth = 2.dp,
// strokeColor = if (item!!.isClicked) "#4969F5" else "#E6E6E6",
// radius = 12.dp
// )
// binding.root.background = bgDrawable
}
private fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
}
@@ -1,117 +0,0 @@
package com.shuwei.intelligent.shelves.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ListItemShelfBinding
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.visible
import java.util.Date
import kotlin.math.abs
class ShelfAdapter(list: MutableList<ShelfModel>) :
BaseQuickAdapter<ShelfModel, ShelfAdapter.VH>(list) {
inner class VH(var binding: ListItemShelfBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemShelfBinding.inflate(inflater, parent, false)
return VH(binding)
}
private fun stringToDate(createTime: String?): Date? {
if (createTime.isNullOrBlank()) {
return null
}
return DateTimeUtil.stringToDate(createTime)
}
override fun onBindViewHolder(holder: VH, position: Int, item: ShelfModel?) {
val shelfNo = item!!.deviceNo
//货架-
item.shelfName = "${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo"}"
val weight = item.weight ?: 0.0
val binding = holder.binding
if (item.overdueDay.isNullOrBlank()) {
binding.ivStaleFood.gone()
} else {
runCatching {
// val shelfDate = stringToDate(item.putCabinetDay)
binding.ivStaleFood.run {
if (item.overdue == true) visible() else gone()
}
// if (shelfDate == null) {
// binding.ivStaleFood.gone()
// } else {
// val days = item.overdueDay!!.toInt()
// val resultDate = DateTimeUtil.addDays(shelfDate, days)
// if (resultDate.before(Date())) {
// binding.ivStaleFood.visible()
// } else {
// binding.ivStaleFood.gone()
// }
// }
}.onFailure {
binding.ivStaleFood.gone()
}
}
//?.setImageResource()
binding.tvFoodWeight.run {
setTextColor(getColor(R.color.food_weight_orange))
//val showWeight = if (weight == 0.toDouble()) "0克" else "%.3f千克".format(weight)
text = if(abs(weight) < 1000) "${weight.toInt()}" else "%.3f千克".format(weight/1000.0)
//"${(weight*1000).roundToInt()}克"
}
//val isBlankShelf = weight <= 0
val isBlankShelf = item.goodsName.isNullOrBlank()
if (isBlankShelf) {
binding.layoutCard.setCardBackgroundColor(getColor(R.color.bg_card_blue))
binding.tvShelfName.run {
setTextColor(getColor(R.color.shelf_name_white))
text = item.shelfName
}
binding.tvFoodName.run {
setTextColor(getColor(R.color.white))
text = ""
}
// binding.tvFoodWeight.run {
// setTextColor(getColor(R.color.food_weight_blue))
// text = "0千克"
// }
binding.tvStoreDate.run {
setTextColor(getColor(R.color.food_weight_blue))
text = "-"
}
return
}
binding.layoutCard.setCardBackgroundColor(getColor(R.color.bg_card_white))
binding.tvShelfName.run {
setTextColor(getColor(R.color.shelf_name_blue))
text = item.shelfName
}
binding.tvFoodName.run {
setTextColor(getColor(R.color.food_name_black))
text = item.goodsName
}
// binding.tvFoodWeight.run {
// setTextColor(getColor(R.color.food_weight_orange))
//// text = "${weight/1000F}千克"
// text = "${weight}克"
// }
binding.tvStoreDate.run {
setTextColor(getColor(R.color.black999))
text = item.putCabinetDay
}
}
private fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
}
@@ -1,77 +0,0 @@
package com.shuwei.intelligent.shelves.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ListItemShelfBinding
import com.shuwei.intelligent.shelves.model.ShelfModelV2
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlin.math.abs
class ShelfV2Adapter(list: MutableList<ShelfModelV2>) :
BaseQuickAdapter<ShelfModelV2, ShelfV2Adapter.VH>(list) {
inner class VH(val binding: ListItemShelfBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val binding = ListItemShelfBinding.inflate(LayoutInflater.from(context), parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: ShelfModelV2?) {
item ?: return
val binding = holder.binding
// 格口编号
binding.tvShelfName.text = item.slotNo?.padStart(2, '0')
// 临期预警(slotStatus=2)显示过期图标
if (item.slotStatus.toInt() == 2) binding.ivStaleFood.visible()
else binding.ivStaleFood.gone()
// // 重量:itemType=2 用 totalWeightG(g)itemType=1 用 actualQty×1000(g)
// val weightG = if (item.itemType == 1) (item.actualQty?.toDouble() ?: 0.0) * 1000.0
// else item.totalWeightG?.toDouble() ?: 0.0
val weightG = item.weight?.toDouble() ?: 0.0
binding.tvFoodWeight.run {
setTextColor(getColor(R.color.food_weight_orange))
text = if (abs(weightG) >= 1.0) "%.3f千克".format(weightG)
else "${(weightG * 1000.0).toInt()}"
}
val isBlank = item.itemName.isNullOrBlank()
if (isBlank) {
binding.layoutCard.setCardBackgroundColor(getColor(R.color.bg_card_blue))
binding.tvShelfName.setTextColor(getColor(R.color.shelf_name_white))
binding.tvFoodName.run {
setTextColor(getColor(R.color.white))
text = ""
}
binding.tvStoreDate.run {
setTextColor(getColor(R.color.food_weight_blue))
text = "-"
}
return
}
binding.layoutCard.setCardBackgroundColor(getColor(R.color.bg_card_white))
binding.tvShelfName.setTextColor(getColor(R.color.shelf_name_blue))
binding.tvFoodName.run {
setTextColor(getColor(R.color.food_name_black))
text = item.itemName
}
binding.tvStoreDate.run {
setTextColor(getColor(R.color.black999))
text = item.storeTime
}
}
private fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
}
@@ -1,14 +0,0 @@
package com.shuwei.intelligent.shelves.model
/**
* 货柜初始化接口(4.1)响应体
*/
data class CabinetInitResult(
val canteenId: Long = 0,
val canteenName: String = "",
val deviceName: String = "",
val area: String = "",
val horizontalRows:Int = 0,
val verticalCount:Int = 0,
val containerGoodsList: List<ShelfModelV2>? = null,
)
@@ -1,35 +0,0 @@
package com.shuwei.intelligent.shelves.model
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import java.math.BigDecimal
import java.util.Date
/**
* 净菜包下拉选项,对应接口 4.2.1itemType=1
*/
data class CleanPackageOption(
val id: Long = 0,
val itemName: String = "",
/** 净菜订单号,sync 时作为 cleanOrderNo 回传 */
val orderNo: String = "",
val packageSpec: String? = null,
/** 关联菜品 id */
val foodId: Long? = null,
/** 需求份数 */
val needCount: Int? = null,
/** 需求计量(kg */
val needQtyKg: BigDecimal? = null,
/** 保质期至,格式 yyyy-MM-dd */
val expiryDate: String? = null,
var isSelected: Boolean = false
) {
/** 将净菜包选项转换为格口记录 */
fun toShelfModelV2(model: ShelfModelV2? = null) = (model ?: ShelfModelV2()).also {
it.itemType = 1
it.itemName = itemName
it.cleanOrderNo = orderNo
it.spec = packageSpec
it.foodId = foodId
it.storeTime = DateTimeUtil.formatDateTime(dateTime = Date())
}
}
@@ -1,6 +0,0 @@
package com.shuwei.intelligent.shelves.model
class DeviceConfigInfo {
var appPackageUrl:String? = null
var canteenId:String? = null
}
@@ -1,10 +0,0 @@
package com.shuwei.intelligent.shelves.model
import java.io.Serializable
data class GoodsModel(
var goodsId: String? = null,
var goodsName: String? = null,
var overdueDay: String? = null,
var isClicked: Boolean = false
) : Serializable
@@ -1,8 +0,0 @@
package com.shuwei.intelligent.shelves.model
data class GoodsRecord(
val total: Int = 0,
val size: Int = 0,
val page: Int = 0,
val records: MutableList<GoodsModel>? = null
)
@@ -1,35 +0,0 @@
package com.shuwei.intelligent.shelves.model
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import java.math.BigDecimal
import java.util.Date
/**
* 餐品净菜包下拉选项,对应接口 4.2.2(itemType=2
*/
data class MealPackageOption(
val id: Long = 0,
val itemName: String = "",
/** 净菜订单号,sync 时作为 cleanOrderNo 回传 */
val orderNo: String = "",
val packageSpec: String? = null,
/** 关联菜品 id */
val foodId: Long? = null,
/** 需求份数 */
val needCount: Int? = null,
/** 需求计量(kg */
val needQtyKg: BigDecimal? = null,
/** 保质期至,格式 yyyy-MM-dd(包装完成后有值) */
val expiryDate: String? = null,
var isSelected: Boolean = false
) {
/** 将餐品净菜包选项转换为格口记录 */
fun toShelfModelV2(model: ShelfModelV2? = null) = (model ?: ShelfModelV2()).also {
it.itemType = 2
it.itemName = itemName
it.cleanOrderNo = orderNo
it.spec = packageSpec
it.foodId = foodId
it.storeTime = DateTimeUtil.formatDateTime(dateTime = Date())
}
}
@@ -1,11 +0,0 @@
package com.shuwei.intelligent.shelves.model
class ShelfBody {
var deviceId: String = ""
var canteenId: String = ""
var temperature: String? = null
var humidity: String = "0"
var goodsList: List<ShelfModel>? = null
}
@@ -1,21 +0,0 @@
package com.shuwei.intelligent.shelves.model
import java.io.Serializable
data class ShelfModel(
var id: String? = null,
var deviceNo: Int = 0,
var shelfName: String? = null,
var goodsId: String? = "",
var goodsName: String? = null,
var deviceId: String? = null,
var weight: Double = 0.0,
var weightBak: Double = 0.0,
var createTime: String? = null,
var putCabinetDay: String? = null,
var updateTime: String? = null,
var overdueDay: String? = null,
var temperature: String? = null,
var humidity: String = "0",
var overdue:Boolean ?= false
) : Serializable
@@ -1,58 +0,0 @@
package com.shuwei.intelligent.shelves.model
import java.io.Serializable
import java.math.BigDecimal
/**
* 格口存放记录,对应接口 4.1 货柜初始化返回的 containerGoodsList 元素
*/
data class ShelfModelV2(
var id: Long = 0,
/** 类型:1-食材净菜 / 2-餐品净菜包 */
var itemType: Int = 0,
/** 格口编号 */
var slotNo: String? = "",
/** 格口所在柜子编号 */
var cabinetId: String? = "",
/** 智能柜名称 */
var cabinetName: String? = "",
var area: String = "",
/** 溯源码 */
var traceCode: String? = "",
/** 关联净菜订单号 */
var cleanOrderNo: String? = "",
/** 物料名称 */
var itemName: String? = "",
/** 订单量 kgitemType=1 */
var orderQty: BigDecimal? = null,
/** 实收量 kgitemType=1 */
// var actualQty: BigDecimal? = null,
/** 差异量 kgitemType=1,可为负) */
var diffQty: BigDecimal? = null,
/** 规格(itemType=2,如:标准份) */
var spec: String? = null,
var specLabel: String? = null,
/** 包数(itemType=2 */
var pkgCount: Int? = null,
/** 总重量 gitemType=2 */
// var totalWeightG: BigDecimal? = null,
/** 统一重量字段,单位千克 */
var weight: BigDecimal? = null,
/** 验收结果:1-合格 / 2-轻微差异 / 3-不合格 / 4-待验 */
var checkResult: Short? = null,
/** 存储温度(℃) */
var temperature: BigDecimal? = null,
/** 存储湿度(% */
var humidity: BigDecimal? = null,
/** 剩余保质期(小时) */
var shelfLifeRemain: Int? = null,
var storeTime: String? = null,
/** 格口状态:1-存放中 / 2-临期预警 / 3-温度超标 / 4-空置 / 5-异常 */
var slotStatus: Short = 0,
/** 绑定菜品 iditemType=2 */
var foodId: Long? = null,
var unit: String? = null,
var remark: String? = null,
var canteenId: Long? = null,
var isClicked: Boolean = false
) : Serializable
@@ -1,8 +0,0 @@
package com.shuwei.intelligent.shelves.model
data class ShelfResult(
val placeId: String = "",
val canteenName: String = "",
val deviceName: String = "",
val containerGoodsList: List<ShelfModel>? = null
)
@@ -1,14 +0,0 @@
package com.shuwei.intelligent.shelves.model
import java.math.BigDecimal
/**
* 全量同步整柜数据请求体,对应接口 4.3
* 注意:goodsList 为空或不传时,后端会软删除整柜所有格口数据
*/
data class SyncBody(
var cabinetId: String?=null,
var temperature: BigDecimal? = null,
var humidity: BigDecimal? = null,
var goodsList: List<ShelfModelV2>? = null,
)
@@ -1,35 +0,0 @@
package com.shuwei.intelligent.shelves.model
import java.math.BigDecimal
/**
* 全量同步单格口数据,对应接口 4.3 goodsList 元素
*/
data class SyncItem(
val slotNo: String,
/** 类型:1-食材净菜 / 2-餐品净菜包 */
val itemType: Short? = null,
val itemName: String? = null,
/** 关联净菜订单号(从下拉选项 orderNo 取值回传) */
val cleanOrderNo: String? = null,
/** 绑定菜品 iditemType=2 */
val foodId: Long? = null,
/** 订单量 kgitemType=1 */
val orderQty: BigDecimal? = null,
/** 实收量 kgitemType=1,与 totalWeightG 二选一) */
val actualQty: BigDecimal? = null,
/** 总重量 gitemType=2,后端自动换算为 kg 存储) */
val totalWeightG: BigDecimal? = null,
/** 规格:1-标准份 / 2-大份 / 3-小份(itemType=2 */
val spec: Int? = null,
val pkgCount: Int? = null,
/** 验收结果:1-合格 / 2-轻微差异 / 3-不合格 / 4-待验 */
val checkResult: Short? = null,
/** 格口状态:1-存放中 / 2-临期预警 / 3-温度超标 / 4-空置 / 5-异常 */
val slotStatus: Short? = null,
/** 剩余保质期(小时) */
val shelfLifeRemain: Int? = null,
/** 存入时间,仅新增时生效,不传则取当前时间 */
val storeTime: String? = null,
val remark: String? = null,
)
@@ -1,61 +0,0 @@
package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.DeviceConfigInfo
import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfResult
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Query
import retrofit2.http.Url
interface ApiService {
// /**
// * device获取token
// */
// @GET("/sys/getEquipmentToken")
// suspend fun getDeviceToken(
// @Query("qrcodeId") qrcodeId: String,
// @Query("appVersion") appVersion: String = App.appVersion
// ): RespData<String>
//
// /**
// *获取配置信息
// */
// @GET("/equipment/stEquipment/queryByEquipmentCode")
// suspend fun getDeviceInfo(
// @Query("equipmentCode") equipmentCode: String,
// @Query("appVersion") appVersion: String = App.appVersion
// ): RespData<DeviceConfigInfo>
//
// @GET
// suspend fun getAccessToken(
// @Url url: String = UrlConfig.GET_ACCESS_TOKEN,
// @Query("qrcodeId") qrcodeId: String
// ): RespData<String?>
@GET
suspend fun getShelfList(
@Url url: String = UrlConfig.GET_SHELF_LIST,
@Query("deviceId") deviceId: String
): RespData<ShelfResult>
@GET
suspend fun getGoodsList(
@Url url: String = UrlConfig.GET_GOODS_LIST,
@Query("canteenId") canteenId: String = "0",
@Query("goodsName") goodsName: String? = null,
@Query("pageNo") pageNo: Int = 1,
@Query("pageSize") pageSize: Int = 50
): RespData<MutableList<GoodsModel>?>
@POST
suspend fun saveShelfGoodsList(
@Url url: String = UrlConfig.SAVE_SHELF_GOODS_LIST,
@Body body: ShelfBody
): RespData<Any?>
}
@@ -1,52 +0,0 @@
package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.model.CabinetInitResult
import com.shuwei.intelligent.shelves.model.CleanPackageOption
import com.shuwei.intelligent.shelves.model.MealPackageOption
import com.shuwei.intelligent.shelves.model.SyncBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import retrofit2.http.Url
interface ApiServiceV2 {
/**
* 4.1 货柜初始化
* 设备启动、Token 刷新后、首页重试时调用
*/
@GET
suspend fun initCabinet(
@Url url: String = UrlConfig.CABINET_INIT,
@Query("cabinetId") cabinetId: String
): RespData<CabinetInitResult>
/**
* 4.2.1 净菜包下拉列表(itemType=1
*/
@GET
suspend fun getCleanPackageOptions(
@Url url: String = UrlConfig.CLEAN_PACKAGE_OPTIONS,
@Query("keyword") keyword: String? = null
): RespData<List<CleanPackageOption>>
/**
* 4.2.2 餐品净菜包下拉列表(itemType=2)
*/
@GET
suspend fun getMealPackageOptions(
@Url url: String = UrlConfig.MEAL_PACKAGE_OPTIONS,
@Query("keyword") keyword: String? = null
): RespData<List<MealPackageOption>>
/**
* 4.3 全量同步整柜数据(定时上报)
* 警告:body.goodsList 为 null 或空列表时,后端会软删除整柜所有格口数据
*/
@POST
suspend fun syncCabinetData(
@Url url: String = UrlConfig.CABINET_RECORD_SYNC,
@Body body: SyncBody
): RespData<Any?>
}
@@ -34,22 +34,7 @@ class DynamicBaseUrlInterceptor : Interceptor {
}
}
val apiService: ApiService = Retrofit.Builder()
.baseUrl("http://placeholder/")
.client(HttpManager.instance.client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
/** v1.1 接口专用实例,使用轻量拦截器,不携带旧鉴权 Token */
val apiServiceV2: ApiServiceV2 = Retrofit.Builder()
.baseUrl("http://placeholder/")
.client(HttpManager.instance.cabinetClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiServiceV2::class.java)
/** V3 接口专用实例,与 V2 共用 cabinetClient(同款拦截器) */
/** V3 接口专用实例 */
val apiServiceV3: ApiServiceV3 = Retrofit.Builder()
.baseUrl(GlobalData.appBaseUrl)
.client(HttpManager.instance.cabinetClient)
@@ -58,38 +43,8 @@ val apiServiceV3: ApiServiceV3 = Retrofit.Builder()
.create(ApiServiceV3::class.java)
class HttpManager private constructor() {
val client: OkHttpClient by lazy {
OkHttpClient.Builder()
.apply {
connectTimeout(30, TimeUnit.SECONDS)
readTimeout(30, TimeUnit.SECONDS)
writeTimeout(30, TimeUnit.SECONDS)
sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
hostnameVerifier { _, _ -> true }
// addInterceptor(LoggingInterceptor())
addInterceptor(DynamicBaseUrlInterceptor())
addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Log.d("HttpManager","okhttp logger ==>${it}")
}).apply {
level =
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
})
// addInterceptor { chain ->
// val request = chain.request().newBuilder()
// .header("x-access-token", App.accessToken)
// .build()
// chain.proceed(request)
// }
addInterceptor(RequestInterceptor())
}
.build()
}
/** v1.1 接口专用 OkHttpClient注入 Content-Type、Accept、X-DEVICE-CODE */
/** 接口专用 OkHttpClient,注入 Content-Type、Accept、X-DEVICE-CODE、authorization */
val cabinetClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.apply {
@@ -1,149 +0,0 @@
package com.shuwei.intelligent.shelves.net
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class NetViewModel : ViewModel() {
private val _getShelfListUiState = MutableStateFlow<UiState>(UiState.Initial)
val getShelfListUiState: StateFlow<UiState> = _getShelfListUiState
private val _getGoodsListUiState = MutableStateFlow<UiState>(UiState.Initial)
val getGoodsListUiState: StateFlow<UiState> = _getGoodsListUiState
private val _getAccessTokenUiState = MutableStateFlow<UiState>(UiState.Initial)
val getAccessTokenUiState: StateFlow<UiState> = _getAccessTokenUiState
private val _getDeviceTokenUiState = MutableStateFlow<UiState>(UiState.Initial)
val getDeviceTokenUiState: StateFlow<UiState> = _getDeviceTokenUiState
private val _getDeviceConfigUiState = MutableStateFlow<UiState>(UiState.Initial)
val getDeviceConfigUiState: StateFlow<UiState> = _getDeviceConfigUiState
private val _updateOverdueStateUiState = MutableStateFlow<UiState>(UiState.Initial)
val updateOverdueStateUiState: StateFlow<UiState> = _updateOverdueStateUiState
// fun getDeviceToken(deviceId: String) {
// viewModelScope.launch {
// _getDeviceTokenUiState.value = UiState.Loading
// runCatching {
// val response = apiService.getDeviceToken(qrcodeId = deviceId)
// if (response.isSuccess()) {
// _getDeviceTokenUiState.value = UiState.Success(response)
// } else {
// _getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
// }
// }.onFailure {
// _getDeviceTokenUiState.value = UiState.Error(it.message ?: "请求异常")
// }
// }
// }
// fun getDeviceConfig(deviceId: String, deviceToken: String) {
// viewModelScope.launch {
// _getDeviceConfigUiState.value = UiState.Loading
// runCatching {
// val response = apiService.getDeviceInfo(equipmentCode = deviceId)
// if (response.isSuccess()) {
// _getDeviceConfigUiState.value = UiState.Success(response)
// } else {
// _getDeviceConfigUiState.value = UiState.Error(response.msg ?: "请求失败")
// }
// }.onFailure {
// _getDeviceConfigUiState.value = UiState.Error(it.message ?: "请求异常")
// }
// }
// }
// fun getAccessToken(deviceId: String) {
// viewModelScope.launch {
// _getAccessTokenUiState.value = UiState.Loading
// runCatching {
// val response = apiService.getAccessToken(qrcodeId = deviceId)
// if (response.isSuccess()) {
// _getAccessTokenUiState.value = UiState.Success(response)
// } else {
// _getAccessTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
// }
// }.onFailure {
// _getAccessTokenUiState.value = UiState.Error(it.message ?: "请求异常")
// }
// }
// }
fun getShelfList(deviceId: String) {
viewModelScope.launch {
_getShelfListUiState.value = UiState.Loading
runCatching {
val response = apiService.getShelfList(deviceId = deviceId)
if (response.isSuccess()) {
_getShelfListUiState.value = UiState.Success(response)
} else {
_getShelfListUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getShelfListUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun updateOverdueState() {
viewModelScope.launch {
runCatching {
val response = apiService.getShelfList(deviceId = App.deviceId)
if (response.isSuccess()) {
_updateOverdueStateUiState.value = UiState.Success(response)
} else {
_updateOverdueStateUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_updateOverdueStateUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun getGoodsList(
canteenId: String = "",
goodsName: String = "",
pageNo: Int = 1,
pageSize: Int = 50,
) {
viewModelScope.launch {
_getGoodsListUiState.value = UiState.Loading
runCatching {
val response = apiService.getGoodsList(
canteenId = canteenId,
goodsName = goodsName,
pageNo = pageNo,
pageSize = pageSize
)
if (response.isSuccess()) {
_getGoodsListUiState.value = UiState.Success(response)
} else {
_getGoodsListUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getGoodsListUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun saveShelfGoodsList(body: ShelfBody) {
viewModelScope.launch {
runCatching {
val resp = apiService.saveShelfGoodsList(body = body)
Log.d("NetViewModel", "saveShelfGoodsList,performSync: resp:${resp.toJsonString()}")
}.onFailure {
it.printStackTrace()
Log.d("NetViewModel", "saveShelfGoodsList异常:${it.message}")
}
}
}
}
@@ -1,99 +0,0 @@
package com.shuwei.intelligent.shelves.net
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.intelligent.shelves.model.CabinetInitResult
import com.shuwei.intelligent.shelves.model.CleanPackageOption
import com.shuwei.intelligent.shelves.model.MealPackageOption
import com.shuwei.intelligent.shelves.model.SyncBody
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class NetViewModelV2 : ViewModel() {
private val _initCabinetUiState = MutableStateFlow<UiState>(UiState.Initial)
val initCabinetUiState: StateFlow<UiState> = _initCabinetUiState
private val _cleanPackageOptionsUiState = MutableStateFlow<UiState>(UiState.Initial)
val cleanPackageOptionsUiState: StateFlow<UiState> = _cleanPackageOptionsUiState
private val _mealPackageOptionsUiState = MutableStateFlow<UiState>(UiState.Initial)
val mealPackageOptionsUiState: StateFlow<UiState> = _mealPackageOptionsUiState
private val _syncCabinetUiState = MutableStateFlow<UiState>(UiState.Initial)
val syncCabinetUiState: StateFlow<UiState> = _syncCabinetUiState
/** 4.1 货柜初始化 */
fun initCabinet(cabinetId: String) {
viewModelScope.launch {
_initCabinetUiState.value = UiState.Loading
runCatching {
val response = apiServiceV2.initCabinet(cabinetId = cabinetId)
if (response.isSuccess()) {
_initCabinetUiState.value = UiState.Success(response)
} else {
_initCabinetUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_initCabinetUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
/** 4.2.1 净菜包下拉列表(itemType=1 */
fun getCleanPackageOptions(keyword: String? = null) {
viewModelScope.launch {
_cleanPackageOptionsUiState.value = UiState.Loading
runCatching {
val response = apiServiceV2.getCleanPackageOptions(keyword = keyword)
if (response.isSuccess()) {
_cleanPackageOptionsUiState.value = UiState.Success(response)
} else {
_cleanPackageOptionsUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_cleanPackageOptionsUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
/** 4.2.2 餐品净菜包下拉列表(itemType=2 */
fun getMealPackageOptions(keyword: String? = null) {
viewModelScope.launch {
_mealPackageOptionsUiState.value = UiState.Loading
runCatching {
val response = apiServiceV2.getMealPackageOptions(keyword = keyword)
if (response.isSuccess()) {
_mealPackageOptionsUiState.value = UiState.Success(response)
} else {
_mealPackageOptionsUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_mealPackageOptionsUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
/**
* 4.3 全量同步整柜数据
* 警告:body.goodsList 为 null 或空列表时,后端会软删除整柜所有格口数据
*/
fun syncCabinetData(body: SyncBody) {
viewModelScope.launch {
_syncCabinetUiState.value = UiState.Loading
runCatching {
val response = apiServiceV2.syncCabinetData(body = body)
if (response.isSuccess()) {
_syncCabinetUiState.value = UiState.Success(response)
} else {
_syncCabinetUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
Log.e("NetViewModelV2", "syncCabinetData 异常:${it.message}")
_syncCabinetUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
}
@@ -1,35 +0,0 @@
package com.shuwei.intelligent.shelves.net
import android.text.TextUtils
import com.shuwei.intelligent.shelves.App
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
/**
* 请求拦截器
*/
class RequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("X-Access-Token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiJiZTE1NDgzMS0zNDY2LTNiYTItYTJlYS01NzY1MmM5MTlmZWQiLCJ0eXBlIjoiNCIsInVzZXJJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDEifQ.sN40cOC-O5WQFrF4IDUs8fFlkNdUKLbJt_rHyTsgYYM")
.header("X-DEVICE-CODE", App.deviceId)
.header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d")
val newRequest = requestBuilder.build()
return chain.proceed(newRequest)
}
// private fun getToken(originRequest: Request): String {
// val tokenParam = originRequest.header("X-Access-Token")
// if (!TextUtils.isEmpty(tokenParam)) return tokenParam!!
// // 从本地获取token的逻辑
// val spUtil = SPUtil.getInstance(context = App.getContext())
// return spUtil.get(GlobalKey.KEY_TOKEN, "") as String
//// return "eyJhbGciOiJIUzUxMiJ9.eyJpZCI6MTQ2LCJ1c2VyTmFtZSI6IjEzNjgxNDQ4ODU2IiwibmFtZSI6IuW-kOejiiIsInBhc3N3b3JkIjoiOTllOTQ1ZmVjZmZjNWIzNDI4MmUwNDRlODYyMzdjM2UxZjU5OWY5OCIsInNhbHQiOiI0NmEzMzUzYWU4OTA0MDYxYjMzODU5ZWNlYTBlMGE2NyIsInBob25lIjoiMTM2ODE0NDg4NTYiLCJzdGF0dXMiOjEsInVzZXJUeXBlIjoyLCJjcmVhdGVVc2VyTm8iOiIxNDEiLCJjcmVhdGVUaW1lIjoxNjI2OTQzNDE2MDAwLCJ1cGRhdGVVc2VyTm8iOiIxNDEiLCJ1cGRhdGVUaW1lIjoxNjI2OTQzNDE2MDAwLCJpc0RlbCI6ZmFsc2UsImVhSWQiOjk5LCJlYUlkTGlzdCI6Ijk5IiwiaXNTaG9wTWFuYWdlciI6dHJ1ZSwidXNlck5vIjoiMWY5Nzk5ZWMtODlkYi00MWYyLTk1YTEtY2UzNTA3Y2QyMTU2In0.f7wImPgBOYMV0AqRchnXGPkUWZN9dFJ9gLPsaB8uNldd21IfXLjJl8y-FiWVuVUvlwUvGpgqGDFR1JKj5H7amw"
// }
}
@@ -1,32 +0,0 @@
//package com.shuwei.intelligent.shelves.task
//
//import android.content.Context
//import android.util.Log
//import androidx.work.CoroutineWorker
//import androidx.work.WorkerParameters
//import com.shuwei.intelligent.shelves.activity.HomeActivity
//import com.shuwei.intelligent.shelves.serial.SerialPortManager
//
//class SyncDeviceTask(appContext: Context, workerParams: WorkerParameters) :
// CoroutineWorker(appContext, workerParams) {
//
// companion object {
// private const val TAG = "SyncDeviceTask"
// }
//
// override suspend fun doWork(): Result {
// return try {
// // 执行后台任务逻辑
// Log.d(TAG, "doWork: ----------SyncDeviceTask----------")
// SerialPortManager.send(HomeActivity.DEVICE_INFO_CMD)
//
// TaskManager.startDeviceTask()
//
// Result.success()
// } catch (e: Exception) {
// e.printStackTrace()
// Result.retry()
// }
// }
//
//}
@@ -1,67 +0,0 @@
//package com.shuwei.intelligent.shelves.task
//
//import android.content.Context
//import android.util.Log
//import androidx.work.CoroutineWorker
//import androidx.work.WorkerParameters
//import com.shuwei.intelligent.shelves.App
//import com.shuwei.intelligent.shelves.activity.HomeActivity
//import com.shuwei.intelligent.shelves.model.ShelfBody
//import com.shuwei.intelligent.shelves.net.apiService
//import com.shuwei.intelligent.shelves.utils.ext.toJsonString
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.withContext
//
//class SyncTask(appContext: Context, workerParams: WorkerParameters) :
// CoroutineWorker(appContext, workerParams) {
//
// companion object {
// private const val TAG = "SyncTask"
// }
//
// override suspend fun doWork(): Result {
// return try {
// // 执行后台任务逻辑
// performSync()
//
// TaskManager.startTask()
//
// Result.success()
// } catch (e: Exception) {
// e.printStackTrace()
// Result.retry()
// }
// }
//
// private suspend fun performSync() {
// withContext(Dispatchers.IO) {
// val list = HomeActivity.list
// Log.d(TAG, "performSync: list:${list.toJsonString()}")
// val submitList = list.sortedBy { it.deviceNo }
// submitList.forEach {
// if (it.goodsId.isNullOrBlank()) {
// it.weight = it.weightBak
// }
// }
//// submitList.forEach {
//// it.temperature = HomeActivity.showTemperatureC
//// it.humidity = HomeActivity.showHumidity
//// }
// val body = ShelfBody().also {
// it.deviceId = App.deviceId
// it.canteenId = App.canteenId
// it.temperature = HomeActivity.showTemperatureC
// it.humidity = HomeActivity.showHumidity
// it.goodsList = submitList
// }
// Log.d(TAG, "performSync: body:${body.toJsonString()}")
// try {
// val resp = apiService.saveShelfGoodsList(body = body)
// Log.d(TAG, "performSync: resp:${resp.toJsonString()}")
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
// }
//
//}
@@ -1,59 +0,0 @@
//package com.shuwei.intelligent.shelves.task
//
//import androidx.work.ExistingWorkPolicy
//import androidx.work.OneTimeWorkRequestBuilder
//import androidx.work.WorkManager
//import com.shuwei.intelligent.shelves.App
//import java.util.concurrent.TimeUnit
//
//object TaskManager {
// const val TASK_NAME = "IntelligentShelvesSyncTask"
// const val DEVICE_TASK_NAME = "IntelligentShelvesDeviceTask"
//
// fun startTask() {
// val nextRequest = OneTimeWorkRequestBuilder<SyncTask>()
// .setInitialDelay(2, TimeUnit.MINUTES)
//// .setInitialDelay(30, TimeUnit.SECONDS)
//// .setInitialDelay(10, TimeUnit.MINUTES)
//// .setInputData(inputData)
// .build()
// WorkManager.getInstance(App.getInstance())
// .enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
//
//// val constraints = Constraints.Builder()
//// .setRequiredNetworkType(NetworkType.CONNECTED)
//// .setRequiresBatteryNotLow(true)
//// .build()
//// val syncRequest = PeriodicWorkRequestBuilder<SyncTask>(
//// 3L, TimeUnit.MINUTES, // 最小间隔15分钟
//// 3L, TimeUnit.MINUTES // 弹性时间窗口
//// )
//// .setConstraints(constraints)
//// .build()
//// WorkManager.getInstance(App.get())
//// .enqueueUniquePeriodicWork(
//// TASK_NAME,
//// ExistingPeriodicWorkPolicy.KEEP,
//// syncRequest
//// )
// }
//
// fun cancelTask() {
// WorkManager.getInstance(App.getInstance())
// .cancelUniqueWork(TASK_NAME)
// }
//
// fun startDeviceTask() {
// val nextRequest = OneTimeWorkRequestBuilder<SyncDeviceTask>()
// .setInitialDelay(2, TimeUnit.MINUTES)
// .build()
// WorkManager.getInstance(App.getInstance())
// .enqueueUniqueWork(DEVICE_TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// }
//
// fun cancelDeviceTask() {
// WorkManager.getInstance(App.getInstance())
// .cancelUniqueWork(DEVICE_TASK_NAME)
// }
//
//}
@@ -1,8 +0,0 @@
package com.shuwei.intelligent.shelves.utils
/**
* 命令常亮定义
*/
object Constant {
}
@@ -1,5 +0,0 @@
package com.shuwei.intelligent.shelves.utils
fun f2C(fahrenheit: Double): Double {
return (fahrenheit - 32) * 5 / 9
}
@@ -1,94 +0,0 @@
package com.shuwei.intelligent.shelves.utils
import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.model.ShelfModelV2
import com.shuwei.intelligent.shelves.serial.ProtocolConstants
import com.sw.scalefusion.shelf.model.SlotModel
object GridLayoutTool {
/**
* 设备2列,10格
*/
private val device2Columns = ProtocolConstants.device2Columns
/**
* 设备3列,15格,左侧1列竖向编号1、2、3、4、5,右侧2列5行编号为6-7、8-9、10-11、12-13、14-15
*/
private val device3Columns = ProtocolConstants.device3Columns
fun getLayoutManager(context: Context): RecyclerView.LayoutManager? {
return if (device2Columns.contains(App.deviceId)) {
GridLayoutManager(context, 2, GridLayoutManager.VERTICAL, false)
} else if (device3Columns.contains(App.deviceId)) {
GridLayoutManager(context, 3, GridLayoutManager.VERTICAL, false)
} else {
null
}
}
fun getLayoutManagerV2(context: Context, columns: Int) = GridLayoutManager(context, columns, GridLayoutManager.VERTICAL, false)
/**
* 初始化list显示顺序
*/
fun initListSort(source: List<ShelfModel>): List<ShelfModel> {
val map = source.associateBy { it.deviceNo }
return if (device3Columns.contains(App.deviceId)) {
// 按全局定义的显示顺序取对应数据,找不到则跳过
ProtocolConstants.device3ColumnsOrder.mapNotNull { map[it] }
} else if (device2Columns.contains(App.deviceId)) {
ProtocolConstants.device2ColumnsOrder.mapNotNull { map[it] }
} else {
source
}
}
fun initListSortV2(rows:Int, columns:Int, source: List<ShelfModelV2>): List<ShelfModelV2> {
val map = source.associateBy { it.slotNo?.toIntOrNull()?:0 }
if (rows != 5) return source
return when (columns) {
3 -> {
// 3列顺序
ProtocolConstants.device3ColumnsOrder.mapNotNull { map[it] }
}
2 -> {
// 2列顺序
ProtocolConstants.device2ColumnsOrder.mapNotNull { map[it] }
}
else -> {
//没有则默认
source
}
}
}
fun submitListSortV2(source: List<ShelfModelV2>): List<ShelfModelV2> {
return source.sortedBy { it.slotNo?.toIntOrNull()?:0 }
}
fun initListSortV3(rows: Int, columns: Int, source: List<SlotModel>): List<SlotModel> {
val map = source.associateBy { it.slotNo.toIntOrNull() ?: 0 }
if (rows != 5) return source
return when (columns) {
3 -> ProtocolConstants.device3ColumnsOrder.mapNotNull { map[it] }
2 -> ProtocolConstants.device2ColumnsOrder.mapNotNull { map[it] }
else -> source
}
}
fun submitListSortV3(source: List<SlotModel>): List<SlotModel> {
return source.sortedBy { it.slotNo.toIntOrNull() ?: 0 }
}
/**
* 提交接口list顺序
*/
fun submitListSort(source: List<ShelfModel>): List<ShelfModel> {
return source.sortedBy { it.deviceNo }
}
}
@@ -1,64 +0,0 @@
package com.shuwei.intelligent.shelves.utils;
public class HexUtils {
/**
* 将有符号十六进制字符串转换为十进制整数
* @param hex 十六进制字符串(支持负数补码表示)
* @return 对应的十进制整数
*/
public static int signedHexToDec(String hex) {
// 验证输入格式
if (hex == null || hex.isEmpty()) {
throw new IllegalArgumentException("输入不能为空");
}
if (!hex.matches("[0-9A-Fa-f]+")) {
throw new IllegalArgumentException("无效的十六进制格式: " + hex);
}
// 转换为无符号长整型
long unsignedValue = Long.parseLong(hex, 16);
// 判断是否为负数(最高位为1
int bitLength = hex.length() * 4; // 每个十六进制字符对应4位
long signBitMask = 1L << (bitLength - 1);
if ((unsignedValue & signBitMask) != 0) {
// 负数:计算补码
long maxValue = (1L << bitLength) - 1;
return (int) (unsignedValue - (maxValue + 1));
} else {
// 正数:直接转换
return (int) unsignedValue;
}
}
/**
* 测试方法
*/
public static void main(String[] args) {
// 测试用例
String[] testCases = {
"FF", // -1 (8位)
"FFFE", // -2 (16位)
"FFFFFFFE", // -2 (32位)
"7F", // 127
"80", // -128
"7FFF", // 32767
"8000" // -32768
};
System.out.println("有符号十六进制转换测试:");
for (String hex : testCases) {
try {
int result = signedHexToDec(hex);
System.out.println(hex + " -> " + result);
} catch (Exception e) {
System.out.println(hex + " -> 错误: " + e.getMessage());
}
}
}
}
@@ -1,101 +0,0 @@
package com.shuwei.intelligent.shelves.utils
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.WriterException
import com.google.zxing.common.BitMatrix
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
/**
* 二维码生成工具类
*/
object QRCodeUtil {
/**
* 生成二维码(默认大小)
* @param content 二维码内容
* @return 生成的二维码Bitmap
*/
@JvmOverloads
fun generateQRCode(content: String, size: Int = 500): Bitmap? {
return generateQRCode(content, size, Color.BLACK, Color.WHITE)
}
/**
* 生成二维码(自定义颜色)
* @param content 二维码内容
* @param size 二维码边长(像素)
* @param colorCode 二维码颜色
* @param backgroundColor 背景颜色
* @return 生成的二维码Bitmap
*/
fun generateQRCode(
content: String,
size: Int,
colorCode: Int,
backgroundColor: Int
): Bitmap? {
if (content.isEmpty()) {
return null
}
return try {
val hints = mutableMapOf<EncodeHintType, Any>().apply {
put(EncodeHintType.CHARACTER_SET, "UTF-8")
put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H) // 纠错级别
put(EncodeHintType.MARGIN, 1) // 边距
}
val bitMatrix = QRCodeWriter().encode(
content,
BarcodeFormat.QR_CODE,
size,
size,
hints
)
val pixels = IntArray(size * size).apply {
for (y in 0 until size) {
for (x in 0 until size) {
this[y * size + x] = if (bitMatrix.get(x, y)) colorCode else backgroundColor
}
}
}
Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
setPixels(pixels, 0, size, 0, 0, size, size)
}
} catch (e: WriterException) {
e.printStackTrace()
null
}
}
/**
* 生成带Logo的二维码
* @param content 二维码内容
* @param size 二维码边长(像素)
* @param logo Logo Bitmap
* @return 带Logo的二维码Bitmap
*/
fun generateQRCodeWithLogo(content: String, size: Int, logo: Bitmap?): Bitmap? {
val qrCode = generateQRCode(content, size) ?: return null
logo ?: return qrCode
val logoSize = size / 5 // Logo大小约为二维码的1/5
val scaledLogo = Bitmap.createScaledBitmap(logo, logoSize, logoSize, false)
val offset = (size - logoSize) / 2
return Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
val canvas = Canvas(this)
canvas.drawBitmap(qrCode, 0f, 0f, null)
canvas.drawBitmap(scaledLogo, offset.toFloat(), offset.toFloat(), null)
}
}
}
@@ -1,57 +0,0 @@
//package com.shuwei.intelligent.shelves.utils
//
//
//import android.serialport.*
////import io.github.jeadyx.jserialport.SerialPort
//import java.io.File
//import java.io.IOException
//import java.io.InputStream
//import java.io.OutputStream
//
//class SerialPortHelper(
// private val devicePath: String = "/dev/ttyS1",
// private val baudRate: Int = 115200
//) {
// private var serialPort: SerialPort? = null
// private var inputStream: InputStream? = null
// private var outputStream: OutputStream? = null
//
// // 打开串口
// fun open(): Boolean {
// return try {
// serialPort = SerialPort(File(devicePath), baudRate, 0)
// inputStream = serialPort?.inputStream
// outputStream = serialPort?.outputStream
// true
// } catch (e: IOException) {
// false
// }
// }
//
// // 发送数据
// fun send(data: ByteArray): Boolean {
// return try {
// outputStream?.write(data)
// outputStream?.flush()
// true
// } catch (e: IOException) {
// false
// }
// }
//
// // 接收数据(需在子线程调用)
// fun receive(buffer: ByteArray): Int {
// return inputStream?.read(buffer) ?: -1
// }
//
// // 关闭串口
// fun close() {
// try {
// inputStream?.close()
// outputStream?.close()
// serialPort?.close()
// } catch (e: IOException) {
// e.printStackTrace()
// }
// }
//}
@@ -1,36 +0,0 @@
package com.shuwei.intelligent.shelves.utils
import android.util.SparseArray
import androidx.core.util.containsKey
import com.shuwei.intelligent.shelves.model.ShelfModel
object StaleFoodRule {
private val shelfMap = SparseArray<ShelfModel>()
// fun load(list: MutableList<ShelfModel>) {
// if (list.isEmpty()) {
// return
// }
// list.forEach {
// shelfMap.put(it.deviceNo, it)
// }
// }
fun judge(item: ShelfModel, block:(Boolean)-> Unit = {}) {
val deviceNo = item.deviceNo
if (!shelfMap.containsKey(deviceNo)) {
//首次设置数据
shelfMap.put(deviceNo, item)
return
}
val lastItem = shelfMap.get(deviceNo)
if (lastItem.goodsId != item.goodsId) {
//柜子商品更换
shelfMap.put(deviceNo, item)
return
}
//同一产品
}
}
@@ -1,282 +0,0 @@
package com.shuwei.intelligent.shelves.utils;
//import cn.hutool.core.util.HexUtil;
//import cn.hutool.core.util.StrUtil;
//import com.chengyi.framework.utils.HexUtils;
//import lombok.Data;
//import lombok.extern.slf4j.Slf4j;
/**
* @Author: NThink
* @Date: 2021/12/10
* @Description: 鸿科HK70温控器信息消息解包
*/
public class TemperatureInfoHK70MsgBodyUnpack {
// 柜温探头温度
private Double gwttTemp;
// 蒸发探头温度
private Double zfttTemp;
// 冷凝探头温度
private Double lnttTemp;
// 停机温度
private Double tjTemp;
// 开机温度回差
private Double kjTempRange;
// 压缩机启动延时
private Integer ysjStartDelay;
// 首次通电压缩机启动延时
private Integer ysjStartDelayFirst;
// 柜温探头补偿
private Double gwttbcTemp;
// 停机温度可设置的最小值
private Double tjTempMin;
// 停机温度可设置的最大值
private Double tjTempMax;
// 压缩机最大待机时间
private Integer ysjMaxWait;
// 压缩机最短运行时间
private Integer ysjMinWork;
// 蒸发器探头状态
private Integer zfttStatus;
// 自动除霜周期
private Integer zdcszq;
// 除霜过程显示
private Integer csWorkShow;
// 除霜最长时间
private Integer cszcsj;
// 除霜中止温度
private Double cszzTemp;
// 除霜类型
private Integer csType;
// 风机运行模式
private Integer fanWorkType;
// 风机首次启动延时
private Integer fanFirstStartDelay;
// 滴水后风机启动延时
private Integer fanStartDelayAfterDrip;
// 开关机状态
private String powerStatus;
// 制冷状态
private String coolStatus;
// 除霜状态
private String csStatus;
// 滴水状态
private String dsStatus;
// 柜温探头报警
private String gwttAlarm;
// 除霜探头报警
private String csttAlarm;
// 冷凝探头报警
private String lnttAlarm;
// 终端ID
private String imei;
// 固件版本
private String version;
// 命令次数
private Integer cmdCount;
public TemperatureInfoHK70MsgBodyUnpack(String body) {
// ===================== 寄存器内容:110字节 =====================
String jcqContent = body.substring(0, 220);
// 柜温探头温度 [0, 4]
double gwttTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(0, 4)) * 0.1));
// setGwttTemp(gwttTemp);
// // 蒸发探头温度 [4, 8]
// double zfttTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(4, 8)) * 0.1));
// setZfttTemp(zfttTemp);
// // 冷凝探头温度 [8, 12]
// double lnttTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(8, 12)) * 0.1));
// setLnttTemp(lnttTemp);
// // 停机温度 [12, 16]
// double tjTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(12, 16)) * 1.0));
// setTjTemp(tjTemp);
// // 开机温度回差 [16, 20]
// double kjwdhc = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(16, 20)) * 0.1));
// setKjTempRange(kjwdhc);
// // 压缩机启动延时 [20, 24]
// int ysjqdys = Integer.parseInt(jcqContent.substring(20, 24), 16);
// setYsjStartDelay(ysjqdys);
// // 首次通电压缩机启动延时 [24, 28]
// int ysjqdysFirst = Integer.parseInt(jcqContent.substring(24, 28), 16);
// setYsjStartDelayFirst(ysjqdysFirst);
// // 柜温探头补偿 [28, 32]
// double gwttbc = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(28, 32)) * 0.1));
// setGwttbcTemp(gwttbc);
// // 停机温度可设置的最小值 [32, 36]
// double tjTempMin = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(32, 36)) * 1.0));
// setTjTempMin(tjTempMin);
// // 停机温度可设置的最大值 [36, 40]
// double tjTempMax = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(36, 40)) * 1.0));
// setTjTempMin(tjTempMax);
// // 压缩机最大待机时间 [40, 44]
// int ysjzddjsj = Integer.parseInt(jcqContent.substring(40, 44), 16);
// setYsjMaxWait(ysjzddjsj);
// // 压缩机最短运行时间 [44, 48]
// int ysjzdyxsj = Integer.parseInt(jcqContent.substring(44, 48), 16);
// setYsjMinWork(ysjzdyxsj);
// // 蒸发器探头选择 [48, 52]
// int zfqtt = Integer.parseInt(jcqContent.substring(48, 52), 16);
// setZfttStatus(zfqtt);
// // TODO 蒸发器探头补偿 [52, 56]
// // TODO 自动除霜周期计算方式 [56, 60]
// // 自动除霜周期 [60, 64]
// int zdcszq = Integer.parseInt(jcqContent.substring(60, 64), 16);
// setZdcszq(zdcszq);
// // 除霜过程显示选择 [64, 68]
// int csgcxx = Integer.parseInt(jcqContent.substring(64, 68), 16);
// setCsWorkShow(csgcxx);
// // 除霜最长时间 [68, 72]
// int cszcsj = Integer.parseInt(jcqContent.substring(68, 72), 16);
// setCszcsj(cszcsj);
// // 除霜终止温度 [72, 76]
// double cszzwd = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(72, 76)) * 1.0));
// setCszzTemp(cszzwd);
// // TODO 除霜滴水时间 [76, 80]
// // TODO 除霜后柜温显示延时 [80, 84]
// // TODO 除霜启动延迟 [84, 88]
// // 除霜类型选择 [88, 92]
// int cslx = Integer.parseInt(jcqContent.substring(88, 92), 16);
// setCsType(cslx);
// // TODO 辅热提前启动时间 [92, 96]
// // TODO 强制除霜温度 [96, 100]
// // 风机运行模式 [100, 104]
// int fjyxms = Integer.parseInt(jcqContent.substring(100, 104), 16);
// setFanWorkType(fjyxms);
// // 风机首次启动延时 [104, 108]
// int fjscqdys = Integer.parseInt(jcqContent.substring(104, 108), 16);
// setFanFirstStartDelay(fjscqdys);
// // 滴水后风机启动延时 [108, 112]
// int dshfjqdys = Integer.parseInt(jcqContent.substring(108, 112), 16);
// setFanStartDelayAfterDrip(dshfjqdys);
// // TODO 风机工作最低温度 [112, 116]
// // TODO 风机工作最高温度 [116, 120]
// // TODO 风机循环开启时间 [120, 124]
// // TODO 风机循环关闭时间 [124, 128]
// // TODO 比例制冷压缩机停止时间 [128, 132]
// // TODO 比例制冷压缩机运行时间 [132, 136]
// // TODO 蜂鸣音报警 [136, 140]
// // TODO 柜温低温报警(绝对温度) [140, 144]
// // TODO 柜温高温报警(绝对温度) [144, 148]
// // TODO 柜温报警延时 [148, 152]
// // TODO 上电首次柜温报警延时 [152, 156]
// // TODO 辅助继电器功能选择 [156, 160]
// // TODO 门开关控制输出选择 [160, 164]
// // TODO 开门时蜂鸣器响应延时 [164, 168]
// // TODO 第三探头功能选择 [168, 172]
// // TODO 冷凝器高温报警启动值 [172, 176]
// // TODO 冷凝器高温报警下回差 [176, 180]
// // TODO 冷凝器高温保护启动值 [180, 184]
// // TODO 实时时钟功能开关 [不上报]
// // TODO 非营业模式开启时间 [不上报]
// // TODO 非营业模式关闭时间 [不上报]
// // TODO 非营业模式温度增量 [不上报]
// // TODO 非营业模式灯光开启时间 [不上报]
// // TODO 节假日设置 [不上报]
// // TODO 节假日非营业模式开启时间 [不上报]
// // TODO 除雾自动控制模式 [184, 188]
// // TODO 除雾自动开启时间 [188, 192]
// // TODO 除雾自动关闭时间 [192, 196]
// // TODO 开关机自动控制模式 [不上报]
// // TODO 自动开机时间 [不上报]
// // TODO 自动关机时间 [不上报]
// // TODO 单位选择 [196, 200]
// // TODO 柜温每升高 1℃显示延时 [204, 208]
// // TODO 显示分辨率选择 [208, 212]
// // TODO 机器地址 [212, 216]
// // TODO 管理员密码设定 [216, 220]
//
// // ===================== 状态位解析:4字节 =====================
// // 1.状态信息
// String statusHexStr = body.substring(220, 224);
// statusHexStr = HexUtils.reverse(statusHexStr);
// String statusBitStr = HexUtils.hexString2binaryString(statusHexStr);
// statusBitStr = StrUtil.reverse(statusBitStr);
// // 机组运行状态 0:停止 1:运行 [0, 1]
// String powerStatus = "0".equals(statusBitStr.substring(0, 1)) ? "close" : "open";
// setPowerStatus(powerStatus);
// // TODO 自检状态 0:停止 1:运行 [1, 2]
// // 压缩机运行状态 0:停止 1:运行 [2, 3]
// String coolStatus = "0".equals(statusBitStr.substring(3, 4)) ? "close" : "open";
// setCoolStatus(coolStatus);
// // 除霜状态 0:停止 1:运行 [3, 4]
// String csStatus = "0".equals(statusBitStr.substring(10, 11)) ? "close" : "open";
// setCsStatus(csStatus);
// // 滴水状态 0:关闭 1:运行中 [4, 5]
// String dsStatus = "0".equals(statusBitStr.substring(12, 13)) ? "close" : "open";
// setDsStatus(dsStatus);
// // TODO 风机状态 0:停止 1:运行 [5, 6]
// // TODO 除露状态 0:停止 1:运行 [6, 7]
// // TODO 灯光状态 0:停止 1:运行 [7, 8]
// // TODO 除霜加热或电磁阀状态 0:停止 1:运行 [8, 9]
// // TODO 辅助加热状态 0:停止 1:运行 [9, 10]
// // TODO 报警状态 0:停止 1:运行 [10, 11]
// // TODO 开门状态 0:停止 1:运行 [11, 12]
// // TODO 非营业(节能)状态 0:停止 1:运行 [12, 13]
// // TODO 节假日状态 0:停止 1:运行 [13, 14]
// // TODO 保留 0:停止 1:运行 [14, 15]
// // TODO 保留 0:停止 1:运行 [15, 16]
//
// // 2.报警信息
// String alarmHexStr = body.substring(224, 228);
// String alarmBitStr = HexUtils.hexString2binaryString(alarmHexStr);
// alarmBitStr = StrUtil.reverse(alarmBitStr);
// // 柜温探头故障 0:无报警 1:报警
// String gwttAlarm = "0".equals(alarmBitStr.substring(0, 1)) ? "normal" : "alarm";
// setGwttAlarm(gwttAlarm);
// // 除霜探头故障 0:无报警 1:报警
// String csAlarm = "0".equals(alarmBitStr.substring(1, 2)) ? "normal" : "alarm";
// setCsttAlarm(csAlarm);
// // 冷凝探头故障 0:无报警 1:报警
// String lnttAlarm = "0".equals(alarmBitStr.substring(2, 3)) ? "normal" : "alarm";
// setLnttAlarm(lnttAlarm);
// // TODO 柜温高温报警 0:无报警 1:报警
// // TODO 柜温低温报警 0:无报警 1:报警
// // TODO 冷凝高温报警 0:无报警 1:报警
// // TODO 冷凝保护报警 0:无报警 1:报警
// // TODO 门打开报警 0:无报警 1:报警
// // TODO 时钟故障报警 0:无报警 1:报警
//
// // 终端ID 15字节
// setImei(HexUtil.decodeHexStr(body.substring(228, 258)));
// // 固件版本 5字节
// setVersion(HexUtil.decodeHexStr(body.substring(258, 268)));
// // 命令次数
// setCmdCount(Integer.parseInt(body.substring(268, 270), 16));
}
public static void main(String[] args) {
// String reverse = HexUtils.reverse("8000");
// System.out.println(reverse);
}
}
@@ -1,70 +0,0 @@
package com.shuwei.intelligent.shelves.utils.ext
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import androidx.core.graphics.toColorInt
interface DrawableDsl {
fun shape(init: ShapeDsl.() -> Unit)
}
interface ShapeDsl {
fun solidColor(color: Int)
fun stroke(width: Int, color: Int)
fun cornerRadius(radius: Float)
}
class DrawableBuilder : DrawableDsl {
val drawables = mutableListOf<ShapeDrawable>()
override fun shape(init: ShapeDsl.() -> Unit) {
val shape = ShapeDrawable()
init(shape)
drawables.add(shape)
}
}
class ShapeDrawable : ShapeDsl {
var color: Int? = null
var strokeWidth: Int? = null
var strokeColor: Int? = null
var cornerRadius: Float? = null
override fun solidColor(color: Int) {
this.color = color
}
override fun stroke(width: Int, color: Int) {
this.strokeWidth = width
this.strokeColor = color
}
override fun cornerRadius(radius: Float) {
this.cornerRadius = radius
}
}
fun createCustomDrawable(
solidColor: String,
strokeWidth: Int,
strokeColor: String,
radius: Int
): Drawable {
val builder = DrawableBuilder()
builder.shape {
solidColor(solidColor.toColorInt())
stroke(strokeWidth, strokeColor.toColorInt())
cornerRadius(radius.toFloat())
}
return convertToDrawable(builder.drawables[0])
}
fun convertToDrawable(shapeDrawable: ShapeDrawable): Drawable {
val drawable = GradientDrawable()
shapeDrawable.color?.let { drawable.setColor(it) }
shapeDrawable.cornerRadius?.let { drawable.cornerRadius = it }
shapeDrawable.strokeWidth?.let { drawable.setStroke(it, shapeDrawable.strokeColor ?: Color.BLACK) }
return drawable
}
@@ -1,24 +0,0 @@
package com.shuwei.intelligent.shelves.utils.ext
import android.text.SpannableStringBuilder
import android.text.Spanned
inline fun buildSpannableString(builderAction: SpannableStringBuilder.() -> Unit): SpannableStringBuilder {
return SpannableStringBuilder().apply(builderAction)
}
fun SpannableStringBuilder.appendText(text: String, vararg spans: Any): SpannableStringBuilder {
val start = length
append(text)
spans.forEach { span ->
setSpan(span, start, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
return this
}
fun SpannableStringBuilder.withSpan(span: Any, block: SpannableStringBuilder.() -> Unit) {
val start = length
block()
setSpan(span, start, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadiusRatio="3"
android:thicknessRatio="8"
android:useLevel="false">
<gradient
android:type="sweep"
android:startColor="#FF6B6B"
android:centerColor="#4ECDC4"
android:endColor="#FF6B6B"
android:angle="0" />
</shape>
@@ -1,7 +0,0 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="@android:color/white" />
</item>
</ripple>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#90000000"/>
<corners android:radius="5dp"/>
</shape>
-27
View File
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:background="@color/bg_page">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvShelf"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
android:overScrollMode="never"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
tools:listitem="@layout/list_item_shelf"/>
<include
android:id="@+id/include"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/layout_empty_view"
android:visibility="gone"/>
</FrameLayout>
-269
View File
@@ -1,269 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@color/bg_page">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="376dp"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/tvShelfName"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_marginTop="24dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textColor="@color/bg_page"
android:textSize="30sp"
tools:text="货架 - 03" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/food_weight_orange"
android:textSize="60sp"
android:textStyle="bold"
tools:text="1506克" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_name_black"
android:textSize="36sp"
android:textStyle="bold"
tools:text="金针菇" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearZero"
android:layout_width="120dp"
android:layout_height="60dp"
android:layout_gravity="top|end"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清零"
android:textColor="@color/white"
android:textSize="30sp"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<ImageView
android:id="@+id/ivBack"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="top|start"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="48dp"
android:background="@drawable/shape_search"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:src="@drawable/ic_search_food" />
<EditText
android:id="@+id/etInputFood"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginEnd="24dp"
android:background="@null"
android:fontFamily="sans-serif-medium"
android:hint="输入食材名称"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:paddingStart="1dp"
android:paddingEnd="1dp"
android:text=""
android:textColor="@color/food_name_black"
android:textColorHint="#B4BEC8"
android:textSize="30sp"
tools:ignore="Autofill,HardcodedText,TextFields" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="24dp"
android:layout_weight="1">
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.scwang.smart.refresh.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingStart="36dp"
android:paddingTop="12dp"
android:paddingEnd="36dp"
android:paddingBottom="12dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"
tools:listitem="@layout/list_item_search" />
<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<include
android:id="@+id/include"
layout="@layout/layout_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearEmpty"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清空"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="48dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="确定"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
@@ -1,311 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@color/bg_page">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="376dp"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/tvShelfName"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_marginTop="24dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textColor="@color/bg_page"
android:textSize="30sp"
tools:text="货架 - 03" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/food_weight_orange"
android:textSize="60sp"
android:textStyle="bold"
tools:text="1506克" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_name_black"
android:textSize="36sp"
android:textStyle="bold"
tools:text="金针菇" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearZero"
android:layout_width="120dp"
android:layout_height="60dp"
android:layout_gravity="top|end"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清零"
android:textColor="@color/white"
android:textSize="30sp"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<ImageView
android:id="@+id/ivBack"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="top|start"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 类型单选:净菜包 / 餐品净菜包 -->
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroupType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="36dp"
android:layout_marginEnd="48dp"
app:selectionRequired="true"
android:layout_gravity="center_horizontal"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/chipClean"
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="180dp"
android:layout_height="64dp"
android:checked="true"
android:textAlignment="center"
android:fontFamily="sans-serif-medium"
android:text="净菜包"
android:textSize="24sp"
app:chipMinHeight="64dp"
tools:ignore="HardcodedText" />
<com.google.android.material.chip.Chip
android:id="@+id/chipMeal"
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="180dp"
android:layout_height="64dp"
android:layout_marginStart="50dp"
android:textAlignment="center"
android:fontFamily="sans-serif-medium"
android:text="餐品净菜包"
android:textSize="24sp"
app:chipMinHeight="64dp"
tools:ignore="HardcodedText" />
</com.google.android.material.chip.ChipGroup>
<!-- 搜索框 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="48dp"
android:background="@drawable/shape_search"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:src="@drawable/ic_search_food"
tools:ignore="ContentDescription" />
<EditText
android:id="@+id/etInputFood"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginEnd="24dp"
android:background="@null"
android:fontFamily="sans-serif-medium"
android:hint="输入名称搜索"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:paddingStart="1dp"
android:paddingEnd="1dp"
android:text=""
android:textColor="@color/food_name_black"
android:textColorHint="#B4BEC8"
android:textSize="30sp"
tools:ignore="Autofill,HardcodedText,TextFields" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="24dp"
android:layout_weight="1">
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.scwang.smart.refresh.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingStart="36dp"
android:paddingTop="12dp"
android:paddingEnd="36dp"
android:paddingBottom="12dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"
tools:listitem="@layout/list_item_search" />
<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<include
android:id="@+id/include"
layout="@layout/layout_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearEmpty"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清空"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="48dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="确定"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
@@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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:id="@+id/layoutCard"
android:layout_width="match_parent"
android:layout_height="216dp"
android:layout_margin="12dp"
app:cardBackgroundColor="@color/bg_card_blue"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:rippleColor="#B0BEC5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/tvShelfName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/shelf_name_white"
android:textSize="20sp"
android:textStyle="bold"
tools:text="货架-01" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
tools:text="空" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.833" />
<TextView
android:id="@+id/tvFoodWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_weight_blue"
android:textSize="24sp"
android:textStyle="bold"
tools:text="0千克" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.933" />
<TextView
android:id="@+id/tvStoreDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_weight_blue"
android:textSize="18sp"
tools:text="-" />
</LinearLayout>
<ImageView
android:id="@+id/ivStaleFood"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_stale_food_512"
android:layout_gravity="top|end"
tools:ignore="ContentDescription"
android:visibility="gone"/>
</com.google.android.material.card.MaterialCardView>
+2 -2
View File
@@ -4,5 +4,5 @@
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Jan 13 10:38:32 CST 2026
sdk.dir=C\:\\Users\\HUAWEI\\AppData\\Local\\Android\\Sdk
#Fri Jun 26 09:17:18 CST 2026
sdk.dir=D\:\\Android\\sdk