Author SHA1 Message Date
mazengfei 40cd27fa7b 增加左上角点击关闭页面,增加启动网络检测和连接功能,删除手机端测试布局; 2026-03-30 11:18:11 +08:00
mazengfei 11e6123cc9 隐藏温湿度 2026-03-10 18:06:23 +08:00
lvmeng 14d8064b13 优化 2026-02-05 14:50:33 +08:00
mazengfei 8006b0d077 优化 2026-01-29 18:53:16 +08:00
mazengfei dbb7ccf8c7 优化 2026-01-15 11:37:37 +08:00
lvmeng 3ff58641aa 优化 2026-01-13 09:29:30 +08:00
lvmeng 5d58cdc4cf 增加定时任务更新货柜数据过期状态 2026-01-13 09:27:41 +08:00
马增飞 9a222037ea 优化 2026-01-12 18:03:41 +08:00
lvmeng 247a332ff3 优化 2026-01-12 14:42:46 +08:00
mazengfei 2fe0381987 jks 2025-12-30 17:23:56 +08:00
lvmeng 7e32268a4c 优化 2025-12-30 16:59:56 +08:00
马增飞 5b0b98c8b8 优化 2025-12-30 16:28:35 +08:00
马增飞 aff3d5cc69 优化 2025-12-03 09:44:37 +08:00
马增飞 6d1b997145 优化 2025-11-26 16:00:19 +08:00
马增飞 ad669e421b 优化 2025-11-20 10:11:28 +08:00
lvmeng 119a69bbbc 优化 2025-11-19 08:31:40 +08:00
马增飞 096499cd65 优化并增加日志 2025-11-18 18:49:46 +08:00
lvmeng 79d14ca300 增加终端反馈 2025-11-18 16:29:38 +08:00
马增飞 f8eeb83c6f 优化 2025-11-17 16:27:00 +08:00
马增飞 99001fff2f 优化 2025-11-13 18:10:34 +08:00
马增飞 10059bf4f6 联调优化 2025-11-12 20:14:37 +08:00
马增飞 32ea2f1379 设备联调优化 2025-11-11 18:26:07 +08:00
lvmeng c20ce471cb 优化 2025-11-07 08:58:21 +08:00
68 changed files with 3102 additions and 1807 deletions
+5 -4
View File
@@ -67,12 +67,13 @@ dependencies {
implementation("io.github.jeadyx:kmp-serialport:1.0.0") implementation("io.github.jeadyx:kmp-serialport:1.0.0")
// 基础依赖包,必须要依赖 // // 基础依赖包,必须要依赖
implementation("com.geyifeng.immersionbar:immersionbar:3.2.2") // implementation("com.geyifeng.immersionbar:immersionbar:3.2.2")
// kotlin扩展(可选) // // kotlin扩展(可选)
implementation("com.geyifeng.immersionbar:immersionbar-ktx:3.2.2") // implementation("com.geyifeng.immersionbar:immersionbar-ktx:3.2.2")
implementation("com.squareup.okhttp3:okhttp:4.9.1") implementation("com.squareup.okhttp3:okhttp:4.9.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation("com.squareup.retrofit2:converter-gson:2.9.0")
+5 -3
View File
@@ -26,7 +26,7 @@
android:theme="@style/Theme.IntelligentShelves" android:theme="@style/Theme.IntelligentShelves"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity <activity
android:name=".InitActivity" android:name=".activity.InitActivity"
android:exported="true" android:exported="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity"> tools:ignore="DiscouragedApi,LockedOrientationActivity">
@@ -37,12 +37,14 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".HomeActivity" android:name=".activity.HomeActivity"
android:screenOrientation="portrait"/> android:screenOrientation="portrait"/>
<activity <activity
android:name=".ShelfActivity" android:name=".activity.ShelfActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" /> tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.SettingActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.LogActivity" />
<receiver <receiver
android:name=".utils.BootReceiver" android:name=".utils.BootReceiver"
@@ -6,6 +6,7 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.SharedPreferences import android.content.SharedPreferences
import com.shuwei.intelligent.shelves.utils.BootReceiver import com.shuwei.intelligent.shelves.utils.BootReceiver
import com.shuwei.intelligent.shelves.utils.CrashHandler
/** /**
* @author: star * @author: star
@@ -17,6 +18,7 @@ class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
app = this app = this
CrashHandler.init(this)
val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED) val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
registerReceiver(BootReceiver(), filter) registerReceiver(BootReceiver(), filter)
} }
@@ -1,589 +0,0 @@
package com.shuwei.intelligent.shelves
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.util.Log
import android.util.SparseArray
import android.view.View
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.chad.library.adapter4.util.setOnDebouncedItemClick
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.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.model.WeightEvent
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.net.apiService
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.task.SyncTask
import com.shuwei.intelligent.shelves.task.TaskManager
import com.shuwei.intelligent.shelves.utils.binaryToHex
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 com.shuwei.intelligent.shelves.utils.f2C
import com.shuwei.intelligent.shelves.utils.hexToBinary
import com.shuwei.intelligent.shelves.utils.hexToDec
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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
class HomeActivity : BaseActivity() {
companion object {
private const val TAG = "HomeActivity"
private const val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss"
//协议头
const val HEADER = "D6DADEDB"
//协议尾
const val FOOTER = "FFFF"
// const val ACTIVE_CMD =
// "${HEADER}000281C5D70A7D428A4FA67B50F7DACA241938938B4B9CBD73673E9C8E7F2E15062D${FOOTER}"
const val LEFT_SHELF_OPEN_CMD =
"${HEADER}020101000000000000000000000000000000000000${FOOTER}"
const val RIGHT_SHELF_OPEN_CMD =
"${HEADER}020100010000000000000000000000000000000000${FOOTER}"
//const val TEMPERATURE_CMD = "${HEADER}1001${FOOTER}"
const val DEVICE_INFO_CMD = "${HEADER}0301000000000000000000000000${FOOTER}"
const val ACTIVE_CMD =
"${HEADER}000281C5D70A7D428948AD7254FADBB1516738938B4B9CBD73673E9D8E7F2E15062D${FOOTER}"
//开灯(开 继电器1
const val LIGHT_CLOSE = "${HEADER}020102020102020000000000000000000000000000${FOOTER}"
const val LIGHT_OPEN = "${HEADER}020102020002020000000000000000000000000000${FOOTER}"
// const val LIGHT_CLOSE = "${HEADER}020102020101010000000000000000000000000000${FOOTER}"
// const val LIGHT_OPEN = "${HEADER}020102020000000000000000000000000000000000${FOOTER}"
// const val LIGHT_CLOSE2 = "${HEADER}020102020102020000000000000000000000000000${FOOTER}"
// const val LIGHT_OPEN2 = "${HEADER}020102020002020000000000000000000000000000${FOOTER}"
// const val LIGHT_CLOSE3 = "${HEADER}020102020102020000000000000000000000000000${FOOTER}"
// const val LIGHT_OPEN3 = "${HEADER}020102020002020000000000000000000000000000${FOOTER}"
val list: MutableList<ShelfModel> = mutableListOf()
val weightArray = SparseArray<Double?>()
var shelfIndex = 0
// public fun getRealWeight():Double = weightArray[shelfIndex]?:0.0
// 温度
var showTemperatureC = "0"
// 湿度
var showHumidity = "0"
}
private lateinit var binding: ActivityHomeBinding
private val viewModel: NetViewModel by viewModels()
@SuppressLint("NotifyDataSetChanged", "HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
EventBus.getDefault().register(this)
setBackground()
setContentView(binding.root)
updateLeftStatus("1-1冷藏柜 -°C / -%")
initRecyclerView()
lifecycleScope.launch {
val openState = SerialPortManager.open()
Log.d(TAG, "onCreate: openState=$openState")
if (openState) {
SerialPortManager.startReceive { data ->
runOnUiThread {
// 更新UI显示接收数据
receiveSerialPortData(data)
}
}
SerialPortManager.send(ACTIVE_CMD)
//SerialPortManager.send(DEVICE_INFO_CMD)
}
repeatOnLifecycle(Lifecycle.State.CREATED) {
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 -> {}
}
}
}
}
}
TaskManager.startTask()
// var androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
//// androidId = "d0e7a2d5f25c775c"
// Log.d(TAG, "onCreate: androidId=${androidId}")
// if (androidId == "65e1012638f78260" || androidId == "40f89e91ee40611d") {
// androidId = "d0e7a2d5f25c775c"
// }
// App.deviceId = androidId
// //"53babf69-0d2f-3875-a649-f2e12f80ad4c1"
//
// val swDpValue = resources?.configuration?.smallestScreenWidthDp
// Log.d(TAG, "onCreate: swDpValue=$swDpValue")
// viewModel.getAccessToken(App.deviceId)
viewModel.getShelfList(deviceId = App.deviceId)
// repeat(100) {
// lifecycleScope.launch {
// val data = "D6DADEDB01010C6102000000000000010000000500008000000000060000000500000004000080060000000100000010190080701800800000000000000000000000000000000000000000000000005037414645333633333334333731373056322E313069FFFF"
// receiveSerialPortData(data)
// delay(500)
// }
// }
// lifecycleScope.launch {
// repeat(100) {
// val sb = StringBuilder().apply {
//// 610200000000000001000000050000800000000006000000050000000400008006000000010000001019008070180080
// repeat(12) {
// val num = (0..99).random()
// val num2 = (0..99).random()
// val num3 = (0..99).random()
// val num4 = (0..1).random()
// val numStr = if (num <= 9) "0${num}" else "" + num
// val numStr2 = if (num2 <= 9) "0${num2}" else "" + num2
// val numStr3 = if (num3 <= 9) "0${num3}" else "" + num3
// val numStr4 = if (num4 == 1) "80" else "00"
// append(numStr).append(numStr2).append(numStr3).append(numStr4)
// }
// }
// val data =
// "${HEADER}0302000000000C${sb}5037414645333633333334333731373056322E313069${FOOTER}"
// receiveSerialPortData(data)
// delay(500)
// }
// }
lightSwitchClick()
}
fun switchLight(isOpen: Boolean) {
val tag = getLeftView().tag?.toString()?.toBoolean() ?: true
if(tag == isOpen) {
return
}
lifecycleScope.launch {
SerialPortManager.send(if (isOpen) LIGHT_OPEN else LIGHT_CLOSE)
}
getLeftView().tag = isOpen
}
private fun lightSwitchClick() {
switchLight(true)
addLeftClick {
val tag = getLeftView().tag?.toString()?.toBoolean() ?: true
switchLight(tag.not())
}
}
private fun getTokenSuccess(data: RespData<*>) {
Log.d(TAG, "getTokenSuccess: $data")
data.data?.let {
App.accessToken = it.toString()
//UIUtils.toast(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)
if (data.data !is ShelfResult) {
loadEmptyView()
return
}
App.canteenId = data.data.placeId
val tempList = data.data.containerGoodsList
if (tempList.isNullOrEmpty()) {
loadEmptyView()
return
}
// if (tempList.size < 10) {
// toast("数据错误")
// return
// }
list.clear()
val subList01 = tempList.subList(0, tempList.size / 2)
val subList02 = tempList.subList(tempList.size / 2, tempList.size)
repeat(tempList.size / 2) { index ->
list.add(subList01[index].also { it.deviceId = App.deviceId })
list.add(subList02[index].also { it.deviceId = App.deviceId })
}
shelfAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
Loading.dismiss()
toast(message)
loadEmptyView()
}
private val serialReader by lazy { StringBuilder() }
private fun receiveSerialPortData(srcData: String) {
Log.d(TAG, "receiveSerialPortData: $srcData")
var data = ""
if (srcData.startsWith(HEADER) && srcData.contains(FOOTER)) {
serialReader.clear()
val endIndex = srcData.indexOf(FOOTER) + FOOTER.length
serialReader.append(srcData.substring(0, endIndex))
data = serialReader.toString()
} else if (srcData.startsWith(HEADER) && srcData.contains(FOOTER).not()) {
serialReader.clear()
serialReader.append(srcData).append(",")
return
} else if (srcData.startsWith(HEADER).not() && srcData.contains(FOOTER).not()) {
serialReader.append(srcData).append(",")
return
} else {
val endIndex = srcData.indexOf(FOOTER) + FOOTER.length
serialReader.append(srcData.substring(0, endIndex))
Log.d(TAG, "receiveSerialPortData:serialReader: $serialReader")
data = serialReader.toString().replace(",", "")
}
val cmd = data.substring(HEADER.length, HEADER.length + 4)
when (cmd) {
"0001" -> {
}
"0101" -> {
//心跳-01
Log.d(TAG, "receiveSerialPortData:心跳01: $data")
heartBeat(data)
}
// "8101" -> {
// //心跳-81
// Log.d(TAG, "receiveSerialPortData:心跳08: $data")
// heartBeat(data)
// }
// "0202" -> {
// //开关锁反馈
// lockFeedback(data)
// }
"0302" -> {
//获取设备状态
getDeviceInfo(data)
}
else -> {}
}
}
private var fixedDataBody = ""
private fun lockFeedback(data: String) {
runCatching {
val fixedDataBodyEndIndex = data.length - FOOTER.length
val fixedDataBodyStartIndex = HEADER.length + "0202".length + 42
fixedDataBody = data.substring(fixedDataBodyStartIndex, fixedDataBodyEndIndex)
val lockNo = if (list[shelfIndex].deviceNo in 1..5) "01" else "02"
val lockOneState = data.substring(HEADER.length + 4, HEADER.length + 6)
if (lockOneState == "00") {
//锁1关
lifecycleScope.launch {
SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
}
}
val lockTwoState = data.substring(HEADER.length + 6, HEADER.length + 8)
if (lockTwoState == "00") {
//锁2关
lifecycleScope.launch {
SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
}
}
}.onFailure {
it.printStackTrace()
}
}
private fun heartBeat(data: String) {
runCatching {
if (data.length < 200) {
return
}
val weightStartIndex = 14
val weightEndIndex = weightStartIndex + 12 * 8
getWeightInfo(
start = weightStartIndex,
end = weightEndIndex,
data = data
)
val temperatureStartIndex = 158
val temperatureEndIndex = temperatureStartIndex + 2
val temperatureHex = data.substring(temperatureStartIndex, temperatureEndIndex)
val temperature = hexToDec(temperatureHex)
val tempC = f2C(temperature.toDouble())
showTemperatureC = "%.1f".format(tempC)
val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
updateLeftStatus("1-1冷藏柜 [$temperatureHex] ${showTemperatureC}°C / ${percent}%")
}.onFailure {
it.printStackTrace()
}
}
private fun getWeightInfo(start: Int, end: Int, data: String) {
var index = 1
var count = start
while (count < end) {
val weightInfo = data.substring(count, count + 8)
var weightHex = ""
weightInfo.run {
weightHex =
substring(6, 8) + substring(4, 6) + substring(2, 4) + substring(0, 2)
}
var realWeight = 0
var firstByteBinary = hexToBinary(weightHex.substring(0, 2))
if (firstByteBinary.startsWith("0")) {
//正数
realWeight = weightHex.toInt()
} else {
//负数
firstByteBinary = "0${firstByteBinary.substring(1)}"
val firstByteHex = binaryToHex(firstByteBinary)
weightHex = firstByteHex + weightHex.substring(2)
realWeight = -1 * weightHex.toInt()
}
val realWeightDouble = realWeight / 1000.0
weightArray.put(index, realWeightDouble)
if (index <= 10) {
list.firstOrNull { it.deviceNo == index }?.let {
it.weight = realWeightDouble
val pos = list.indexOf(it)
shelfAdapter.notifyItemChanged(pos)
Log.d(TAG, "getWeightInfo: deviceNo=${it.deviceNo},realWeight=${realWeight}")
EventBus.getDefault().post(SendWeightEvent(it.deviceNo, realWeight))
}
}
count += 8
index++
}
// ShelfActivity.updateWeight(getRealWeight())
// if (list.isNotEmpty()) {
// val intent = Intent(ShelfActivity.RECEIVER_DEVICE_INFO + list[shelfIndex].deviceNo)
// intent.putExtra(ShelfActivity.SHELF_WEIGHT, weightArray[shelfIndex])
// LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
// }
}
private fun getDeviceInfo(data: String) {
Log.d(TAG, "getDeviceInfo: data=$data")
val weightStartIndex = HEADER.length + 14
val temperatureStartIndex = weightStartIndex + 12 * 8
val temperatureEndIndex = temperatureStartIndex + 2
val temperatureHex = data.substring(temperatureStartIndex, temperatureEndIndex)
val temperature = hexToDec(temperatureHex)
val tempC = f2C(temperature.toDouble())
showTemperatureC = "%.1f".format(tempC)
val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
updateLeftStatus("1-1冷藏柜 ${showTemperatureC}°C / ${percent}%")
getWeightInfo(
start = weightStartIndex,
end = temperatureStartIndex,
data = data
)
}
override fun onDestroy() {
lifecycleScope.launch {
SerialPortManager.close()
}
TaskManager.cancelTask()
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@Suppress("DEPRECATION")
private val shelfAdapter by lazy {
ShelfAdapter(list).apply {
setOnDebouncedItemClick { adapter, view, position ->
shelfIndex = position
lifecycleScope.launch {
// val openCmd = if (list[position].deviceNo in 1..5)
// LEFT_SHELF_OPEN_CMD
// else
// RIGHT_SHELF_OPEN_CMD
// val openState = SerialPortManager.send(openCmd)
// if (openState.not()) {
// toast("${list[position].goodsName}柜门开启失败")
// return@launch
// }
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
}) {
it?.run {
val model =
getSerializableExtra(ShelfActivity.SHELF_MODEL) as ShelfModel
list[position].apply {
goodsId = model.goodsId
goodsName = model.goodsName
weight = model.weight
if (model.createTime.isNullOrBlank().not()) {
createTime = model.createTime
}
}
notifyItemChanged(position)
try {
val submitList = list.sortedBy { it.deviceNo }
val body = ShelfBody().also {
it.deviceId = App.deviceId ?: ""
it.canteenId = App.canteenId
it.temperature = showTemperatureC
it.humidity = showHumidity
it.goodsList = submitList
}
Log.d("mzf", "performSync: body:${body.toJsonString()}")
lifecycleScope.launch {
val resp = apiService.saveShelfGoodsList(body = body)
Log.d("mzf", "performSync: resp:${resp.toJsonString()}")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
}
}
private fun initRecyclerView() {
binding.rvShelf.run {
layoutManager =
GridLayoutManager(this@HomeActivity, 2, GridLayoutManager.VERTICAL, false)
adapter = shelfAdapter
itemAnimator.let {
if (it is DefaultItemAnimator) {
it.supportsChangeAnimations = false
}
}
}
}
private fun updateDateTime() {
val sdf = SimpleDateFormat(MM_DD_EEEE__HH_MM_SS, Locale.CHINA)
val dateTime = sdf.format(Date())
updateRightStatus(dateTime)
}
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)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
shelfAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
viewModel.getAccessToken(App.deviceId)
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_white)
it.tvEmptyContent.setTextColor(Color.WHITE)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun weightEvent(event: WeightEvent) {
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun clearEmptyShelf(event: ClearShelfEvent) {
val item = list.firstOrNull { it.deviceNo == event.shelfNo }
item?.apply {
goodsId = ""
goodsName = null
weight = null
createTime = null
overdueDay = null
}
shelfAdapter.notifyItemChanged(list.indexOf(item))
}
}
@@ -0,0 +1,811 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.SparseIntArray
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.model.WeightEvent
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.net.apiService
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.utils.HexUtils
import com.shuwei.intelligent.shelves.utils.IntervalExecutor
import com.shuwei.intelligent.shelves.utils.binaryToHex
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 com.shuwei.intelligent.shelves.utils.hexToBinary
import kotlinx.coroutines.Job
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
class HomeActivity : BaseActivity() {
companion object {
private const val TAG = "HomeActivity"
private const val LOG_PREFIX = "receiveSerialPortData:收到"
private const val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss"
//协议头
const val HEADER = "D6DADEDB"
//协议尾
const val FOOTER = "FFFF"
// const val ACTIVE_CMD =
// "${HEADER}000281C5D70A7D428A4FA67B50F7DACA241938938B4B9CBD73673E9C8E7F2E15062D${FOOTER}"
const val LEFT_SHELF_OPEN_CMD =
"${HEADER}020101000000000000000000000000000000000000${FOOTER}"
const val RIGHT_SHELF_OPEN_CMD =
"${HEADER}020100010000000000000000000000000000000000${FOOTER}"
//const val TEMPERATURE_CMD = "${HEADER}1001${FOOTER}"
const val DEVICE_INFO_CMD = "${HEADER}0301000000000000000000000000${FOOTER}"
const val START_TEMP_CTRL_CMD = "${HEADER}0F01360000${FOOTER}"
//const val START_CMD= "${HEADER}0F01050020${FOOTER}"
// const val ACTIVE_CMD = "${HEADER}000281C5D70A7D428948AD7254FADBB1516738938B4B9CBD73673E9D8E7F2E15062D$FOOTER"
// 410激活码
// const val ACTIVE_CMD = "${HEADER}000281C5D70A7D42894EA17650F5D0BB516838E7863C9ABD766C38958AFF0F3C062D${FOOTER}"
//1楼餐厅激活码
const val ACTIVE_CMD =
"${HEADER}000281C5D70A7D428B44A57454F5DACA241938938B4B9CBD73673E9C8E7FC0C4992D${FOOTER}"
//华氏温度单位
const val F_TEMP_CMD = "${HEADER}0F01390000${FOOTER}"
//摄氏温度单位
const val C_TEMP_CMD = "${HEADER}0F01390001${FOOTER}"
//开灯(开 继电器1
const val LIGHT_CLOSE = "${HEADER}020102020102020000000000000000000000000000${FOOTER}"
const val LIGHT_OPEN = "${HEADER}020102020002020000000000000000000000000000${FOOTER}"
const val TEMPERATURE_CTRL = "${HEADER}0F01040000${FOOTER}"
val list: MutableList<ShelfModel> = mutableListOf()
val weightArray = SparseIntArray()
var shelfIndex = 0
// public fun getRealWeight():Double = weightArray[shelfIndex]?:0.0
// 温度
var showTemperatureC = "0"
// 湿度
var showHumidity = "0"
}
//401-false1楼餐厅-true
private var isNeedOpenLock = true
private lateinit var binding: ActivityHomeBinding
private val viewModel: NetViewModel by viewModels()
@SuppressLint("NotifyDataSetChanged", "HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
EventBus.getDefault().register(this)
setBackground()
setContentView(binding.root)
// updateLeftStatus("-°C / -%")
updateLeftStatus("")
initRecyclerView()
lifecycleScope.launch {
val openState = SerialPortManager.open()
log("onCreate: openState=$openState")
if (openState) {
SerialPortManager.startReceive { data ->
// 更新UI显示接收数据
lifecycleScope.launch {
try {
receiveSerialPortData(data)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
//SerialPortManager.send(DEVICE_INFO_CMD)
}
repeatOnLifecycle(Lifecycle.State.CREATED) {
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 -> {}
}
}
}
}
}
//sendCmd(ACTIVE_CMD)
binding.root.postDelayed({
sendCmd(START_TEMP_CTRL_CMD)
sendCmd(C_TEMP_CMD)
}, 5000)
// TaskManager.startTask()
saveGoodsTask()
overdueTask()
// var androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
//// androidId = "d0e7a2d5f25c775c"
// log("onCreate: androidId=${androidId}")
// if (androidId == "65e1012638f78260" || androidId == "40f89e91ee40611d") {
// androidId = "d0e7a2d5f25c775c"
// }
// App.deviceId = androidId
// //"53babf69-0d2f-3875-a649-f2e12f80ad4c1"
//
// val swDpValue = resources?.configuration?.smallestScreenWidthDp
// log("onCreate: swDpValue=$swDpValue")
// viewModel.getAccessToken(App.deviceId)
viewModel.getShelfList(deviceId = App.deviceId)
// binding.root.postDelayed({
// loadTestData()
// }, 30*1000)
}
private fun loadTestData() {
list.add(ShelfModel(deviceNo = 1, goodsName = "鸡蛋", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 6, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 2, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 7, goodsName = "生姜", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 3, goodsName = "黄瓜", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 8, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 4, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 9, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 5, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 10, goodsName = "", deviceId = App.deviceId))
shelfAdapter.notifyDataSetChanged()
}
private fun getTokenSuccess(data: RespData<*>) {
log("getTokenSuccess: $data")
data.data?.let {
App.accessToken = it.toString()
//UIUtils.toast(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)
if (data.data !is ShelfResult) {
loadEmptyView()
return
}
deviceName = data.data.deviceName
// updateLeftStatus("$deviceName -°C / -%")
updateLeftStatus("$deviceName")
App.canteenId = data.data.placeId
val tempList = data.data.containerGoodsList
if (tempList.isNullOrEmpty()) {
loadEmptyView()
return
}
// if (tempList.size < 10) {
// toast("数据错误")
// return
// }
list.clear()
val subList01 = tempList.subList(0, tempList.size / 2)
val subList02 = tempList.subList(tempList.size / 2, tempList.size)
repeat(tempList.size / 2) { index ->
list.add(subList01[index].also { it.deviceId = App.deviceId })
list.add(subList02[index].also { it.deviceId = App.deviceId })
}
shelfAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
Loading.dismiss()
toast(message)
loadEmptyView()
}
/**
* 终端id
*/
private var terminalId: String = ""
/**
* 固件版本
*/
private var terminalVersion: String = ""
private val serialReader by lazy { StringBuilder() }
private fun receiveSerialPortData(srcData: String) {
log("receiveSerialPortData: $srcData")
var data = ""
if (srcData.contains(HEADER) && srcData.contains(FOOTER)) {
serialReader.clear()
val startIndex = srcData.lastIndexOf(HEADER)
var endIndex = srcData.indexOf(FOOTER) + FOOTER.length
if (endIndex < startIndex) {
//类似这种:31314FFFFFD6DADEDB01010CFOOTER在HEADER前
data = srcData.substring(startIndex)
if (data.contains(FOOTER)) {
endIndex = srcData.indexOf(FOOTER) + FOOTER.length
data = data.substring(0, endIndex)
handleReceivedData(data)
serialReader.clear()
return
}
serialReader.clear()
serialReader.append(data).append(",")
return
}
serialReader.append(srcData.substring(startIndex, endIndex))
data = serialReader.toString()
handleReceivedData(data)
serialReader.clear()
return
}
if (srcData.contains(HEADER) && srcData.contains(FOOTER).not()) {
serialReader.clear()
val startIndex = srcData.lastIndexOf(HEADER)
serialReader.append(srcData.substring(startIndex)).append(",")
return
}
if (srcData.contains(HEADER).not() && srcData.contains(FOOTER).not()) {
serialReader.append(srcData).append(",")
return
}
//if (srcData.contains(HEADER).not() && srcData.contains(FOOTER))
val endIndex = srcData.indexOf(FOOTER) + FOOTER.length
serialReader.append(srcData.substring(0, endIndex))
log("receiveSerialPortData:serialReader: $serialReader")
data = serialReader.toString().replace(",", "")
handleReceivedData(data)
}
private fun handleReceivedData(data: String) {
if (data.isEmpty()) {
return
}
val cmd = data.substring(HEADER.length, HEADER.length + 4)
val cmdFlag = cmd.substring(0, 2)
when (cmd) {
"0001" -> {
//终端发起激活指令,拿到固定数据体(终端ID-15字节、固件版本-5字节、命令次数-1字节)
val reqBody =
data.substring(HEADER.length + cmd.length, data.length - FOOTER.length)
if (reqBody.length == 44) {
terminalId = reqBody.substring(2, 32)
terminalVersion = reqBody.substring(32, 42)
log("${LOG_PREFIX}[激活]${cmdFlag}指令:${data},终端id:${terminalId},固件版本:${terminalVersion},发送激活命令:$ACTIVE_CMD")
sendCmd(ACTIVE_CMD)
}
}
"0101", "8101" -> {
//心跳0102${p1}${p2}${version}
//心跳81(终端发起,当终端发送此命令时需要服务端存储串口屏显示信息)
val respCmd = "${HEADER}01020506${terminalVersion}$FOOTER"
log("${LOG_PREFIX}[心跳]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
heartBeat(data)
}
"0202" -> {
//开关锁反馈020200${deviceId}${version}
val respCmd = "${HEADER}020200${terminalId}${terminalVersion}$FOOTER"
log("${LOG_PREFIX}[开锁]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
//lockFeedback(data)
}
"0302" -> {
//获取设备状态
log("${LOG_PREFIX}[获取设备状态]${cmdFlag}指令:${data}")
getDeviceInfo(data)
}
"0501" -> {
//卡号上发
val respCmd = "${HEADER}050203$FOOTER"
log("${LOG_PREFIX}[卡号上发]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"0B01" -> {
//操作记录上传
val respCmd = "${HEADER}0B0203$FOOTER"
log("${LOG_PREFIX}[操作记录上传]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"0C01" -> {
//对时
val timeLong = System.currentTimeMillis() / 1000 + (8 * 3600)
val timeStr = "0000000" + String.format("%X", timeLong)
val time =
timeStr.substring(timeStr.length - 8).chunked(2).reversed().joinToString("")
val respCmd = "${HEADER}0B02${time}$FOOTER"
log("${LOG_PREFIX}[对时]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"0E01" -> {
//终端位置上报
val respCmd = "${HEADER}0E0200$FOOTER"
log("${LOG_PREFIX}[终端位置上报]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"1001" -> {
//温控信息上报
val respCmd = "${HEADER}100200$FOOTER"
val logMsg =
"${LOG_PREFIX}[温控信息上报]${cmdFlag}指令:${data},发送应答指令:${respCmd}"
log(logMsg)
sendCmd(respCmd)
//val tempC = HexUtils.signedHexToDec(data.take(4)) * 0.1
// updateLeftStatus("$deviceName ${"%.1f".format(tempC)}°C / -%")
// updateLeftStatus("$deviceName -°C / -%")
updateLeftStatus("$deviceName")
//D6DADEDB1001015D020000000027001E00020000001E001C00B40000000000010000000100040000001900440003000F00000000000FFFE6000200010002FFD80044000300010005001E0001FFD80056001400280000000000000000008300090000003C00D2000000000000007F003700000000A100000031323136334238423134303233303456322E313100FFFF
runOnUiThread {
if (tipDialog == null) {
CommonDialog(this).apply {
dialogTitle = "温馨提示"
// dialogContent = logMsg
confirmBlock = {
logMsg?.copyTextToClipboard(this@HomeActivity)
}
}
}
if (tipDialog?.isShowing == false) {
tipDialog?.dialogContent = logMsg
tipDialog?.show()
}
}
}
"1002" -> {
log("${LOG_PREFIX}[温控信息应答]1002数据:${data}")
}
"0F02" -> {
log("${LOG_PREFIX}[温控信息应答]0F02数据:${data}")
//D6DADEDB0F020331323136334238423134303233303456322E31313EFFFF00000055010000000000002707000001000000220100000000000091040000120100000100000095140080611400800000000000000000000000000000000000000000010100005031323136334238423134303233303456322E31313EFFFF
runOnUiThread { }
}
else -> {}
}
}
private var tipDialog: CommonDialog? = null
private var fixedDataBody = ""
// private fun lockFeedback(data: String) {
// runCatching {
// val fixedDataBodyEndIndex = data.length - FOOTER.length
// val fixedDataBodyStartIndex = HEADER.length + "0202".length + 42
// fixedDataBody = data.substring(fixedDataBodyStartIndex, fixedDataBodyEndIndex)
//
// val lockNo = if (list[shelfIndex].deviceNo in 1..5) "01" else "02"
// val lockOneState = data.substring(HEADER.length + 4, HEADER.length + 6)
// if (lockOneState == "00") {
// //锁1关
// SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
// }
// val lockTwoState = data.substring(HEADER.length + 6, HEADER.length + 8)
// if (lockTwoState == "00") {
// //锁2关
// SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
// }
// }.onFailure {
// it.printStackTrace()
// }
// }
private fun heartBeat(data: String) {
runCatching {
if (data.length < 200) {
return
}
val weightStartIndex = 14
val weightEndIndex = weightStartIndex + 12 * 8
getWeightInfo(
start = weightStartIndex,
end = weightEndIndex,
data = data
)
val temperatureStartIndex = 158
val temperatureEndIndex = temperatureStartIndex + 2
// val temperatureHex = data.substring(temperatureStartIndex, temperatureEndIndex)
// val temperature = hexToDec(temperatureHex)
// val tempC = f2C(temperature.toDouble())
// showTemperatureC = "%.1f".format(tempC)
//val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
// updateLeftStatus("${deviceName} [$temperatureHex] ${showTemperatureC}°C / -%")
getFixDataBody(data, temperatureEndIndex)
}.onFailure {
it.printStackTrace()
}
}
private var deviceName = ""
private fun getWeightInfo(start: Int, end: Int, data: String) {
var index = 1
var count = start
while (count < end) {
val weightInfo = data.substring(count, count + 8)
var weightHex = ""
weightInfo.run {
weightHex =
substring(6, 8) + substring(4, 6) + substring(2, 4) + substring(0, 2)
}
var realWeight = 0
var firstByteBinary = hexToBinary(weightHex.substring(0, 2))
if (firstByteBinary.startsWith("0")) {
//正数
realWeight = weightHex.toInt()
} else {
//负数
firstByteBinary = "0${firstByteBinary.substring(1)}"
val firstByteHex = binaryToHex(firstByteBinary)
weightHex = firstByteHex + weightHex.substring(2)
realWeight = -1 * weightHex.toInt()
}
weightArray.put(index, realWeight)
if (index in 1..10 && list.size == 10) {
list.firstOrNull { it.deviceNo == index }?.let {
it.weight = realWeight.toDouble()
val pos = list.indexOf(it)
shelfAdapter.notifyItemChanged(pos)
log("getWeightInfo: deviceNo=${it.deviceNo},realWeight=${realWeight}")
EventBus.getDefault().post(SendWeightEvent(it.deviceNo, realWeight))
}
// val pos = index - 1
// val item = list[pos]
// item.weight = realWeight.toDouble()
// shelfAdapter.notifyItemChanged(pos)
// log("getWeightInfo: deviceNo=${item.deviceNo},realWeight=${realWeight}")
// EventBus.getDefault().post(SendWeightEvent(item.deviceNo, realWeight))
}
count += 8
index++
}
// ShelfActivity.updateWeight(getRealWeight())
// if (list.isNotEmpty()) {
// val intent = Intent(ShelfActivity.RECEIVER_DEVICE_INFO + list[shelfIndex].deviceNo)
// intent.putExtra(ShelfActivity.SHELF_WEIGHT, weightArray[shelfIndex])
// LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
// }
}
private fun getDeviceInfo(data: String) {
log("getDeviceInfo: data=$data")
val weightStartIndex = HEADER.length + 14
val temperatureStartIndex = weightStartIndex + 12 * 8
val temperatureEndIndex = temperatureStartIndex + 2
val temperatureHex = try {
data.substring(temperatureStartIndex, temperatureEndIndex)
} catch (e: Exception) {
e.printStackTrace()
return
}
// val temperature = hexToDec(temperatureHex)
// val tempC = f2C(temperature.toDouble())
// showTemperatureC = "%.1f".format(tempC)
//val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
// updateLeftStatus("${deviceName} ${showTemperatureC}°C / -%")
// val tempC = HexUtils.signedHexToDec(temperatureHex) * 0.1
// updateLeftStatus("$deviceName -°C / -%")
updateLeftStatus("$deviceName")
getWeightInfo(
start = weightStartIndex,
end = temperatureStartIndex,
data = data
)
getFixDataBody(data, temperatureEndIndex)
}
fun getFixDataBody(data: String, temperatureEndIndex: Int) {
try {
if (terminalId.isNotBlank() && terminalVersion.isNotBlank()) {
return
}
val fixDataBodyStartIndex = temperatureEndIndex + 1
val fixDataBody = data.substring(fixDataBodyStartIndex, data.length - FOOTER.length)
if (fixDataBody.length == 42) {
terminalId = fixDataBody.substring(0, 30)
terminalVersion = fixDataBody.substring(30, 40)
log("receiveSerialPortData:从设备状态获取数据,终端id:${terminalId},固件版本:${terminalVersion}")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onDestroy() {
lifecycleScope.launch {
SerialPortManager.close()
}
// TaskManager.cancelTask()
saveTaskJob?.cancel()
overdueTaskJob?.cancel()
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@Suppress("DEPRECATION")
private val shelfAdapter by lazy {
ShelfAdapter(list).apply {
setOnItemClickListener { _, _, position ->
shelfIndex = position
lifecycleScope.launch {
if (isNeedOpenLock) {
val openCmd = if (list[position].deviceNo in 1..5)
LEFT_SHELF_OPEN_CMD
else
RIGHT_SHELF_OPEN_CMD
val openState = SerialPortManager.send(openCmd)
}
// if (openState.not()) {
// toast("${list[position].goodsName}柜门开启失败")
// return@launch
// }
//SerialPortManager.send(START_CMD)
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
}) {
it?.run {
val model =
getSerializableExtra(ShelfActivity.SHELF_MODEL) as ShelfModel
list[position].apply {
goodsId = model.goodsId
goodsName = model.goodsName
weight = model.weight
if (model.putCabinetDay.isNullOrBlank().not()) {
putCabinetDay = model.putCabinetDay
}
}
notifyItemChanged(position)
try {
val submitList = list.sortedBy { item -> item.deviceNo }
val body = ShelfBody().also { body ->
body.deviceId = App.deviceId ?: ""
body.canteenId = App.canteenId
body.temperature = showTemperatureC
body.humidity = showHumidity
body.goodsList = submitList
}
Log.d("mzf", "performSync: body:${body.toJsonString()}")
lifecycleScope.launch {
val resp = apiService.saveShelfGoodsList(body = body)
Log.d("mzf", "performSync: resp:${resp.toJsonString()}")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
}
}
private fun initRecyclerView() {
binding.rvShelf.run {
layoutManager =
GridLayoutManager(this@HomeActivity, 2, GridLayoutManager.VERTICAL, false)
adapter = shelfAdapter
itemAnimator.let {
if (it is DefaultItemAnimator) {
it.supportsChangeAnimations = false
}
}
}
}
private fun updateDateTime() {
val sdf = SimpleDateFormat(MM_DD_EEEE__HH_MM_SS, Locale.CHINA)
val dateTime = sdf.format(Date())
updateRightStatus(dateTime)
}
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)
// D6DADEDB00010031323136334238423134303233303456322E313100FFFF
// sendCmd("${HEADER}00010031323136334238423134303233303456322E313152${FOOTER}")
}
override fun onPause() {
super.onPause()
handler.removeCallbacks(updateTask)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
shelfAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
//viewModel.getAccessToken(App.deviceId)
viewModel.getShelfList(deviceId = App.deviceId)
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_white)
it.tvEmptyContent.setTextColor(Color.WHITE)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun weightEvent(event: WeightEvent) {
}
@Subscribe(threadMode = ThreadMode.MAIN)
public 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) {
saveShelfGoodsList()
}
}
private var overdueTaskJob: Job?=null
/**
* 过期时间定时任务
*/
private fun overdueTask() {
overdueTaskJob = taskExecutor.startIntervalTaskWithInitialDelay(5 * 1000L, 5 * 60* 1000L) {
updateOverdueState()
}
}
private fun updateOverdueState() {
viewModel.updateOverdueState { state, items ->
if (items.isNullOrEmpty()) return@updateOverdueState
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}")
}
}
}
}
private fun saveShelfGoodsList() {
log("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 = showTemperatureC
it.humidity = showHumidity
it.goodsList = submitList
}
log("performSync: body:${body.toJsonString()}")
viewModel.saveShelfGoodsList(body)
}
}
@@ -1,14 +1,21 @@
package com.shuwei.intelligent.shelves package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.util.Log import android.util.Log
import android.view.KeyEvent
import android.view.animation.RotateAnimation
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.graphics.toColorInt
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.base.BaseActivity import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityInitBinding import com.shuwei.intelligent.shelves.databinding.ActivityInitBinding
import com.shuwei.intelligent.shelves.model.DeviceConfigInfo import com.shuwei.intelligent.shelves.model.DeviceConfigInfo
@@ -16,6 +23,7 @@ import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.UiState import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.net.UrlConfig import com.shuwei.intelligent.shelves.net.UrlConfig
import com.shuwei.intelligent.shelves.utils.AppUtil import com.shuwei.intelligent.shelves.utils.AppUtil
import com.shuwei.intelligent.shelves.utils.NetworkUtils
import com.shuwei.intelligent.shelves.utils.QRCodeUtil import com.shuwei.intelligent.shelves.utils.QRCodeUtil
import com.shuwei.intelligent.shelves.utils.SpTool import com.shuwei.intelligent.shelves.utils.SpTool
import com.shuwei.intelligent.shelves.utils.ext.dp import com.shuwei.intelligent.shelves.utils.ext.dp
@@ -24,17 +32,13 @@ import com.shuwei.intelligent.shelves.utils.ext.startActivity
import com.shuwei.intelligent.shelves.utils.ext.toJsonString import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toObject import com.shuwei.intelligent.shelves.utils.ext.toObject
import com.shuwei.intelligent.shelves.utils.ext.toast import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.collections.forEach import kotlin.collections.forEach
import kotlin.getValue import kotlin.getValue
import kotlin.jvm.java
import kotlin.ranges.downTo import kotlin.ranges.downTo
import kotlin.text.isBlank import kotlin.text.isBlank
import kotlin.to
class InitActivity : BaseActivity() { class InitActivity : BaseActivity() {
companion object { companion object {
@@ -44,6 +48,9 @@ class InitActivity : BaseActivity() {
private lateinit var binding: ActivityInitBinding private lateinit var binding: ActivityInitBinding
private val viewModel: NetViewModel by viewModels() private val viewModel: NetViewModel by viewModels()
private val handler = Handler(Looper.getMainLooper())
private var startTime = 0L // 倒计时开始时间
private var lastNetworkCheckTime = 0L // 上次检测网络的时间
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -53,7 +60,9 @@ class InitActivity : BaseActivity() {
App.deviceId = AppUtil.getUDID( this) App.deviceId = AppUtil.getUDID( this)
SpTool.put(SpTool.DEVICE_ID, App.deviceId) // App.deviceId = "4787e213-90ab-3e32-88e0-ac271a937751"
Log.d(TAG, "onCreate: deviceId = ${App.deviceId}")
SpTool.put(SpTool.DEVICE_ID, App.deviceId)
App.configUrl = UrlConfig.BASE_URL App.configUrl = UrlConfig.BASE_URL
App.canteenId = "0" App.canteenId = "0"
@@ -76,7 +85,8 @@ class InitActivity : BaseActivity() {
binding.ivQrCode.invisible() binding.ivQrCode.invisible()
binding.btnInit.invisible() binding.btnInit.invisible()
countDown() // countDown()
initViews()
} }
private fun countDown() { private fun countDown() {
@@ -164,4 +174,142 @@ class InitActivity : BaseActivity() {
return true return true
} }
/**
* 初始化控件事件
*/
private fun initViews() {
// 连接网络按钮点击事件
binding.btnConnectNetwork.setOnClickListener {
openNetworkSettings()
}
// 启动加载动画 - 持续旋转
startLoadingAnimation()
}
/**
* 启动加载动画 - ImageView 持续旋转
*/
private fun startLoadingAnimation() {
val rotateAnimation = RotateAnimation(
0f, 360f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f
).apply {
duration = 1200 // 旋转周期 1.2 秒
repeatCount = RotateAnimation.INFINITE // 无限循环
repeatMode = RotateAnimation.RESTART
}
binding.ivLoading.startAnimation(rotateAnimation)
}
override fun onResume() {
super.onResume()
if (NetworkUtils.isNetworkConnected(this)) {
navigateToHome()
return
}
startCountdown()
}
override fun onPause() {
super.onPause()
// 页面不可见时停止倒计时
handler.removeCallbacksAndMessages(null)
}
/**
* 启动 60 秒倒计时
*/
private fun startCountdown() {
startTime = SystemClock.elapsedRealtime()
lastNetworkCheckTime = 0
binding.llNetwork.setBackgroundColor(Color.TRANSPARENT)
binding.llLoading.visibility = android.view.View.VISIBLE
binding.llNetworkButton.visibility = android.view.View.GONE
binding.tvCountdown.text = "60秒"
scheduleCountdown()
}
/**
* 定时更新倒计时
* 使用 SystemClock.elapsedRealtime() 确保精确计时
*/
private fun scheduleCountdown() {
handler.postDelayed({
val elapsedTime = SystemClock.elapsedRealtime() - startTime
val remainingTime = 60 - (elapsedTime / 1000).toInt()
val currentNetworkCheckTime = (elapsedTime / 1000).toInt()
// 每 10 秒检测一次网络
if (currentNetworkCheckTime > 0 && currentNetworkCheckTime % 10 == 0 && currentNetworkCheckTime != lastNetworkCheckTime.toInt()) {
lastNetworkCheckTime = currentNetworkCheckTime.toLong()
checkNetworkConnection()
}
// 倒计时未结束,继续更新 UI
if (remainingTime > 0) {
binding.tvCountdown.text = "${remainingTime}"
scheduleCountdown()
} else {
// 倒计时结束,最后检测一次网络
if (NetworkUtils.isNetworkConnected(this)) {
navigateToHome()
} else {
// 网络未连接,显示"连接网络"按钮
showNetworkButton()
}
}
}, 100) // 每 100ms 检查一次,确保精确性
}
/**
* 检测网络连接
*/
private fun checkNetworkConnection() {
if (NetworkUtils.isNetworkConnected(this)) {
// 网络连接成功,停止倒计时并跳转到 HomeActivity
handler.removeCallbacksAndMessages(null)
navigateToHome()
}
}
/**
* 跳转到 HomeActivity
*/
private fun navigateToHome() {
startActivity<HomeActivity>()
finish()
}
/**
* 显示"连接网络"按钮
*/
private fun showNetworkButton() {
binding.llNetwork.setBackgroundColor("#5C77F7".toColorInt())
binding.llLoading.visibility = android.view.View.GONE
binding.llNetworkButton.visibility = android.view.View.VISIBLE
}
/**
* 打开系统网络设置页面
*/
private fun openNetworkSettings() {
val intent = Intent(android.provider.Settings.ACTION_WIFI_SETTINGS)
startActivity(intent)
}
/**
* 禁用返回键
*/
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return if (keyCode == KeyEvent.KEYCODE_BACK) {
// 禁用返回键,不执行任何操作
true
} else {
super.onKeyDown(keyCode, event)
}
}
} }
@@ -0,0 +1,93 @@
package com.shuwei.intelligent.shelves.activity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.intelligent.shelves.adapter.LogAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityLogListBinding
import com.shuwei.intelligent.shelves.dialog.CommonDialog
import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.startActivity
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
class LogActivity : BaseActivity() {
companion object {
const val IS_LOG_DIR = "isLogDir"
const val LOG_DATE = "logDate"
}
private lateinit var binding:ActivityLogListBinding
private var isLogDir = false
private var logDate = ""
private val logList = mutableListOf<String>()
private val logAdapter by lazy {
LogAdapter(logList).apply {
setOnItemClickListener { _, _, position ->
if (isLogDir.not()) {
showConfirmDialog(logList[position])
return@setOnItemClickListener
}
startActivity<LogActivity> {
putExtra(IS_LOG_DIR, false)
putExtra(LOG_DATE, logList[position])
}
}
}
}
private fun showConfirmDialog(content:String) {
CommonDialog(this).apply {
dialogTitle = "温馨提示"
dialogContent = content
confirmBlock = {
content?.copyTextToClipboard(this@LogActivity)
}
}.show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLogListBinding.inflate(layoutInflater)
setBackground()
setContentView(binding.root)
isLogDir = intent.getBooleanExtra(IS_LOG_DIR, false)
logDate = intent.getStringExtra(LOG_DATE) ?: ""
binding.ivBack.setOnClickListener { finish() }
binding.tvTitle.text = if (isLogDir) "全部日志" else logDate
binding.tvTitle.setOnClickListener {
logList.reverse()
logAdapter.notifyDataSetChanged()
}
if (isLogDir) {
logList.addAll(fileLogger.getLogFiles())
} else {
logList.addAll(fileLogger.readLogsByName(logDate))
}
binding.rvLogList.let {
it.layoutManager = LinearLayoutManager(this)
it.adapter = logAdapter
}
binding.btnFind.run {
if (isLogDir) gone() else visible()
}
binding.btnFind.setOnClickListener {
if (isLogDir) {
return@setOnClickListener
}
val list = logList.filter { it.contains("${HomeActivity.HEADER}1001") }
if (list.isEmpty()) {
toast("未找到到1001指令")
return@setOnClickListener
}
logList.clear()
logList.addAll(list)
logAdapter.notifyDataSetChanged()
}
}
}
@@ -0,0 +1,66 @@
//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,4 +1,4 @@
package com.shuwei.intelligent.shelves package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
@@ -16,21 +16,20 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.chad.library.adapter4.util.setOnDebouncedItemClick import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.scwang.smart.refresh.layout.constant.RefreshState import com.scwang.smart.refresh.layout.constant.RefreshState
import com.shuwei.intelligent.shelves.HomeActivity.Companion.DEVICE_INFO_CMD import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.DEVICE_INFO_CMD
import com.shuwei.intelligent.shelves.adapter.SearchAdapter import com.shuwei.intelligent.shelves.adapter.SearchAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding
import com.shuwei.intelligent.shelves.model.ClearShelfEvent import com.shuwei.intelligent.shelves.model.ClearShelfEvent
import com.shuwei.intelligent.shelves.model.GoodsModel import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.GoodsRecord
import com.shuwei.intelligent.shelves.model.SendWeightEvent import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfModel import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.net.Loading import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.task.TaskManager
import com.shuwei.intelligent.shelves.utils.DateTimeUtil import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.KeyboardUtil import com.shuwei.intelligent.shelves.utils.KeyboardUtil
import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener
@@ -54,7 +53,7 @@ class ShelfActivity : BaseActivity() {
companion object { companion object {
private const val TAG = "ShelfActivity" private const val TAG = "ShelfActivity"
private const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss" const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
const val SHELF_MODEL = "shelfModel" const val SHELF_MODEL = "shelfModel"
const val RECEIVER_DEVICE_INFO = "receiverDeviceInfo" const val RECEIVER_DEVICE_INFO = "receiverDeviceInfo"
const val SHELF_WEIGHT = "shelfWeight" const val SHELF_WEIGHT = "shelfWeight"
@@ -90,9 +89,9 @@ class ShelfActivity : BaseActivity() {
shelfModel?.let { shelfModel?.let {
binding.tvShelfName.text = it.shelfName binding.tvShelfName.text = it.shelfName
binding.tvFoodName.text = if (it.goodsName.isNullOrBlank()) "-" else it.goodsName binding.tvFoodName.text = if (it.goodsName.isNullOrBlank()) "-" else it.goodsName
realWeight = ((it.weight ?: 0.0) * 1000).toInt() realWeight = it.weight?.toInt()?:0
// realWeight = (HomeActivity.getRealWeight() * 1000).toInt() binding.tvFoodWeight.text = if(realWeight < 1000) "${realWeight}" else "%.3f千克".format(realWeight/1000.0)
binding.tvFoodWeight.text = "${realWeight}" startTime = System.currentTimeMillis()
} }
initRecyclerView() initRecyclerView()
@@ -125,10 +124,10 @@ class ShelfActivity : BaseActivity() {
shelfName = shelfModel?.shelfName, shelfName = shelfModel?.shelfName,
goodsId = currentGoodsId, goodsId = currentGoodsId,
goodsName = list[clickIndex].goodsName, goodsName = list[clickIndex].goodsName,
weight = realWeight / 1000.0, weight = realWeight.toDouble(),
overdueDay = list[clickIndex].overdueDay, overdueDay = list[clickIndex].overdueDay,
// createTime = if (shelfModel?.goodsId == currentGoodsId) null else DateTimeUtil.formatDateTime(dateTime = Date()) // createTime = if (shelfModel?.goodsId == currentGoodsId) null else DateTimeUtil.formatDateTime(dateTime = Date())
createTime = DateTimeUtil.formatDateTime(dateTime = Date()) putCabinetDay = DateTimeUtil.formatDateTime(dateTime = Date())
) )
val intent = Intent().apply { val intent = Intent().apply {
putExtra(SHELF_MODEL, data) putExtra(SHELF_MODEL, data)
@@ -142,15 +141,15 @@ class ShelfActivity : BaseActivity() {
binding.btnClearZero.setOnClickListener { binding.btnClearZero.setOnClickListener {
clearZero() clearZero()
} }
binding.btnClearEmpty?.setOnClickListener { binding.btnClearEmpty.setOnClickListener {
val tempWeight = shelfModel?.weight?.toInt()?:0
clearZero() clearZero()
binding.tvFoodName.text = "-" binding.tvFoodName.text = "-"
binding.tvFoodWeight.text = "0克" //binding.tvFoodWeight.text = "0克"
EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo)) EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo, tempWeight))
} }
sendCmd(DEVICE_INFO_CMD)
lifecycleScope.launch { lifecycleScope.launch {
SerialPortManager.send(DEVICE_INFO_CMD)
repeatOnLifecycle(Lifecycle.State.STARTED) { repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.getGoodsListUiState.collect { state -> viewModel.getGoodsListUiState.collect { state ->
when (state) { when (state) {
@@ -176,7 +175,7 @@ class ShelfActivity : BaseActivity() {
} }
getGoodsList() getGoodsList()
TaskManager.startDeviceTask() // TaskManager.startDeviceTask()
} }
private fun finishRefresh() { private fun finishRefresh() {
@@ -281,7 +280,7 @@ class ShelfActivity : BaseActivity() {
override fun onDestroy() { override fun onDestroy() {
// unregisterReceiver(receiver) // unregisterReceiver(receiver)
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
TaskManager.cancelDeviceTask() // TaskManager.cancelDeviceTask()
super.onDestroy() super.onDestroy()
} }
@@ -358,7 +357,12 @@ class ShelfActivity : BaseActivity() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public fun receiveWeightEvent(event: SendWeightEvent) { public fun receiveWeightEvent(event: SendWeightEvent) {
if (event.shelfNo == (shelfModel?.deviceNo ?: 0)) { if (event.shelfNo == (shelfModel?.deviceNo ?: 0)) {
binding.tvFoodWeight.text = "${event.weight}" 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)
} }
} }
@@ -368,17 +372,21 @@ class ShelfActivity : BaseActivity() {
val hex = shelfNo.toString(16).padStart(2, '0').uppercase() val hex = shelfNo.toString(16).padStart(2, '0').uppercase()
Log.d(TAG, "onCreate: hex=$hex") Log.d(TAG, "onCreate: hex=$hex")
val zeroClearingCmd = "${HomeActivity.HEADER}0401${hex}${HomeActivity.FOOTER}" val zeroClearingCmd = "${HomeActivity.HEADER}0401${hex}${HomeActivity.FOOTER}"
Log.d(TAG, "onCreate: zeroClearingCmd=$zeroClearingCmd") log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}")
SerialPortManager.send(zeroClearingCmd) sendCmd(zeroClearingCmd)
sendCmd(
if (shelfNo in 1..5) HomeActivity.LEFT_SHELF_OPEN_CMD else HomeActivity.RIGHT_SHELF_OPEN_CMD
)
//清零 //清零
realWeight = 0 realWeight = 0
// binding.tvFoodWeight.text = "${realWeight}克" // binding.tvFoodWeight.text = "${realWeight}克"
SerialPortManager.send(DEVICE_INFO_CMD) sendCmd(DEVICE_INFO_CMD)
startTime = System.currentTimeMillis()
Loading.show(this@ShelfActivity) Loading.show(this@ShelfActivity)
window.decorView.postDelayed({ Loading.dismiss() }, 5000) // window.decorView.postDelayed({ Loading.dismiss() }, 5000)
} }
KeyboardUtil.hideKeyboard(window.decorView) KeyboardUtil.hideKeyboard(window.decorView)
} }
private var startTime = 0L
} }
@@ -0,0 +1,25 @@
package com.shuwei.intelligent.shelves.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.databinding.ListItemLogBinding
class LogAdapter(list: MutableList<String>) :
BaseQuickAdapter<String, LogAdapter.VH>(list) {
inner class VH(var binding: ListItemLogBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemLogBinding.inflate(inflater, parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: String?) {
holder.binding.tvLogName.text = item
}
}
@@ -14,7 +14,7 @@ import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.ext.gone import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.visible import com.shuwei.intelligent.shelves.utils.ext.visible
import java.util.Date import java.util.Date
import kotlin.math.roundToInt import kotlin.math.abs
class ShelfAdapter(list: MutableList<ShelfModel>) : class ShelfAdapter(list: MutableList<ShelfModel>) :
BaseQuickAdapter<ShelfModel, ShelfAdapter.VH>(list) { BaseQuickAdapter<ShelfModel, ShelfAdapter.VH>(list) {
@@ -36,25 +36,29 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
override fun onBindViewHolder(holder: VH, position: Int, item: ShelfModel?) { override fun onBindViewHolder(holder: VH, position: Int, item: ShelfModel?) {
val shelfNo = item!!.deviceNo val shelfNo = item!!.deviceNo
item.shelfName = "货架-${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo"}" //货架-
val weight = item.weight ?: 0.toDouble() item.shelfName = "${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo"}"
val weight = item.weight ?: 0.0
val binding = holder.binding val binding = holder.binding
if (item.overdueDay.isNullOrBlank()) { if (item.overdueDay.isNullOrBlank()) {
binding.ivStaleFood.gone() binding.ivStaleFood.gone()
} else { } else {
runCatching { runCatching {
val shelfDate = stringToDate(item.createTime) // val shelfDate = stringToDate(item.putCabinetDay)
if (shelfDate == null) { binding.ivStaleFood.run {
binding.ivStaleFood.gone() if (item.overdue == true) visible() else gone()
} else {
val days = item.overdueDay!!.toInt()
val resultDate = DateTimeUtil.addDays(shelfDate, days)
if (resultDate.before(Date())) {
binding.ivStaleFood.visible()
} else {
binding.ivStaleFood.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 { }.onFailure {
binding.ivStaleFood.gone() binding.ivStaleFood.gone()
} }
@@ -63,7 +67,8 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
binding.tvFoodWeight.run { binding.tvFoodWeight.run {
setTextColor(getColor(R.color.food_weight_orange)) setTextColor(getColor(R.color.food_weight_orange))
//val showWeight = if (weight == 0.toDouble()) "0克" else "%.3f千克".format(weight) //val showWeight = if (weight == 0.toDouble()) "0克" else "%.3f千克".format(weight)
text = "${(weight*1000).roundToInt()}" text = if(abs(weight) < 1000) "${weight.toInt()}" else "%.3f千克".format(weight/1000.0)
//"${(weight*1000).roundToInt()}克"
} }
//val isBlankShelf = weight <= 0 //val isBlankShelf = weight <= 0
val isBlankShelf = item.goodsName.isNullOrBlank() val isBlankShelf = item.goodsName.isNullOrBlank()
@@ -103,7 +108,7 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
// } // }
binding.tvStoreDate.run { binding.tvStoreDate.run {
setTextColor(getColor(R.color.black999)) setTextColor(getColor(R.color.black999))
text = item.createTime text = item.putCabinetDay
} }
} }
@@ -1,26 +1,34 @@
package com.shuwei.intelligent.shelves.base package com.shuwei.intelligent.shelves.base
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import android.view.View import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.gyf.immersionbar.BarHide import androidx.lifecycle.lifecycleScope
import com.gyf.immersionbar.ImmersionBar import com.shuwei.intelligent.shelves.activity.HomeActivity
import com.shuwei.intelligent.shelves.HomeActivity import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.LIGHT_CLOSE
import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.LIGHT_OPEN
import com.shuwei.intelligent.shelves.R import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ActivityBaseBinding import com.shuwei.intelligent.shelves.databinding.ActivityBaseBinding
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.utils.FileLogger
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
import kotlinx.coroutines.launch
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.shuwei.intelligent.shelves.utils.ext.setOnDoubleClickListener
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
open class BaseActivity : AppCompatActivity() { open class BaseActivity : AppCompatActivity() {
val TAG = this.javaClass.simpleName
private lateinit var binding: ActivityBaseBinding private lateinit var binding: ActivityBaseBinding
private var launcher: ActivityResultLauncher<Intent>? = null private var launcher: ActivityResultLauncher<Intent>? = null
@@ -38,6 +46,7 @@ open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
hideSystemBars()
binding = ActivityBaseBinding.inflate(layoutInflater) binding = ActivityBaseBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
statusBarDarkFont() statusBarDarkFont()
@@ -57,17 +66,22 @@ open class BaseActivity : AppCompatActivity() {
// } // }
// } // }
lightSwitchClick()
binding.tvLeftStatus.setOnDoubleClickListener {
finish()
}
} }
fun hideStatusBar() { fun hideStatusBar() {
// enableEdgeToEdge() //// enableEdgeToEdge()
// val uiOptions = (View.SYSTEM_UI_FLAG_FULLSCREEN // 隐藏状态栏 //// val uiOptions = (View.SYSTEM_UI_FLAG_FULLSCREEN // 隐藏状态栏
// or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) // 隐藏导航栏(可选) //// or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) // 隐藏导航栏(可选)
// window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN //// window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
//
ImmersionBar.with(this) // ImmersionBar.with(this)
.hideBar(BarHide.FLAG_HIDE_STATUS_BAR) // .hideBar(BarHide.FLAG_HIDE_STATUS_BAR)
.init() // .init()
} }
fun statusBarDarkFont() { fun statusBarDarkFont() {
@@ -78,7 +92,7 @@ open class BaseActivity : AppCompatActivity() {
} }
fun setBackground() { fun setBackground() {
binding.root.setBackgroundColor(ContextCompat.getColor(this,R.color.bg_page)) binding.root.setBackgroundColor(ContextCompat.getColor(this, R.color.bg_page))
} }
override fun setContentView(view: View?) { override fun setContentView(view: View?) {
@@ -97,23 +111,6 @@ open class BaseActivity : AppCompatActivity() {
binding.tvRightStatus.text = data binding.tvRightStatus.text = data
} }
fun addViewClick(view:View, click: () -> Unit) {
view.setOnClickListener {
click()
}
}
fun getLeftView(): TextView {
return binding.tvLeftStatus
}
fun addLeftClick(click: (View) -> Unit) {
binding.tvLeftStatus.setOnClickListener(click)
}
fun addRightClick(click: (View) -> Unit) {
binding.tvRightStatus.setOnClickListener(click)
}
// 5分钟 // 5分钟
private val TIME_OUT: Long = (5 * 60 * 1000).toLong() private val TIME_OUT: Long = (5 * 60 * 1000).toLong()
private val mHandler by lazy { Handler(Looper.getMainLooper()) } private val mHandler by lazy { Handler(Looper.getMainLooper()) }
@@ -125,11 +122,18 @@ open class BaseActivity : AppCompatActivity() {
override fun onUserInteraction() { override fun onUserInteraction() {
super.onUserInteraction() super.onUserInteraction()
//开灯
if (System.currentTimeMillis() - lastTouchTime >= TIME_OUT) {
this.switchLight(true)
}
// 重置定时器 // 重置定时器
mHandler.removeCallbacks(mTimeoutRunnable) mHandler.removeCallbacks(mTimeoutRunnable)
mHandler.postDelayed(mTimeoutRunnable, TIME_OUT) mHandler.postDelayed(mTimeoutRunnable, TIME_OUT)
lastTouchTime = System.currentTimeMillis()
} }
private var lastTouchTime = 0L
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// 启动定时器 // 启动定时器
@@ -142,8 +146,74 @@ open class BaseActivity : AppCompatActivity() {
mHandler.removeCallbacks(mTimeoutRunnable) mHandler.removeCallbacks(mTimeoutRunnable)
} }
fun timeOutOperation() { fun switchLight(isOpen: Boolean) {
val tag = binding.tvLeftStatus.tag?.toString()?.toBoolean() ?: true
if (tag == isOpen) {
return
}
sendCmd(if (isOpen) LIGHT_OPEN else LIGHT_CLOSE)
binding.tvLeftStatus.tag = isOpen
}
private fun lightSwitchClick() {
switchLight(true)
binding.tvLeftStatus.clickWithDebounce {
val tag = binding.tvLeftStatus.tag?.toString()?.toBoolean() ?: true
switchLight(tag.not())
}
binding.tvRightStatus.clickWithDebounce {
// if (this is HomeActivity) {
// startActivity<LogActivity> {
// putExtra(LogActivity.IS_LOG_DIR, true)
// }
// return@clickWithDebounce
// }
// startActivity<SettingActivity> { }
}
}
fun sendCmd(cmd:String) {
lifecycleScope.launch {
val result = SerialPortManager.send(cmd)
log("发送命令${cmd}结果:${result}")
}
}
val fileLogger by lazy { FileLogger(this) }
fun log(message:String) {
Log.d(TAG, message)
fileLogger.log(message)
}
/**
* 窗口焦点变化时回调
* 场景:弹出 Dialog 后焦点丢失,Dialog 消失后焦点恢复,此时需重新隐藏系统栏,
* 避免系统栏在 Dialog 关闭后意外"复活"
*/
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
hideSystemBars()
}
}
/**
* 隐藏状态栏和导航栏
* 使用 WindowInsetsControllerCompatAndroidx 推荐方式,向下兼容性好)
* - 状态栏:完全隐藏
* - 导航栏:完全隐藏,底部上滑时临时出现,松手后自动收回(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
*/
private fun hideSystemBars() {
// 允许布局延伸到系统栏区域(edge-to-edge),避免内容被系统栏遮挡
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).apply {
// 同时隐藏状态栏和导航栏
hide(WindowInsetsCompat.Type.systemBars())
// 底部上滑时临时显示导航栏,松手后自动隐藏(沉浸式 Sticky 模式)
systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
} }
} }
@@ -0,0 +1,38 @@
package com.shuwei.intelligent.shelves.base
import android.app.Dialog
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.View
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.utils.ext.dp
import com.shuwei.intelligent.shelves.utils.ext.hideKeyboard
abstract class BaseDialog(
context: Context,
var defWidth: Int = 400.dp,
var defHeight: Int = 300.dp
) : Dialog(context, R.style.DialogTheme) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootView = getRootView()
setContentView(rootView)
setCancelable(false)
setCanceledOnTouchOutside(false)
window?.apply {
setLayout(defWidth, defHeight)
setBackgroundDrawable(ColorDrawable())
setGravity(Gravity.CENTER)
}
rootView.setOnClickListener { it.hideKeyboard() }
initView()
}
abstract fun getRootView(): View
abstract fun initView()
}
@@ -0,0 +1,33 @@
package com.shuwei.intelligent.shelves.dialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import com.shuwei.intelligent.shelves.base.BaseDialog
import com.shuwei.intelligent.shelves.databinding.DialogLayoutBinding
class CommonDialog(context: Context):BaseDialog(context) {
private lateinit var binding:DialogLayoutBinding
override fun getRootView(): View {
binding = DialogLayoutBinding.inflate(LayoutInflater.from(context))
return binding.root
}
var dialogTitle:String?=null
var dialogContent:String?=null
var confirmBlock:(()->Unit)?=null
override fun initView() {
binding.btnConfirm.setOnClickListener {
confirmBlock?.invoke()
dismiss()
}
}
override fun show() {
super.show()
binding.tvDialogTitle.text = dialogTitle
binding.tvDialogContent.text = dialogContent
}
}
@@ -7,4 +7,4 @@ data class SendWeightEvent(
data class WeightEvent(var weight: Int) data class WeightEvent(var weight: Int)
data class ClearShelfEvent(var shelfNo: Int) data class ClearShelfEvent(var shelfNo: Int, var weight: Int)
@@ -6,12 +6,16 @@ data class ShelfModel(
var id: String? = null, var id: String? = null,
var deviceNo: Int = 0, var deviceNo: Int = 0,
var shelfName: String? = null, var shelfName: String? = null,
var goodsId: String = "", var goodsId: String? = "",
var goodsName: String? = null, var goodsName: String? = null,
var deviceId: String? = null, var deviceId: String? = null,
var weight: Double? = null, var weight: Double = 0.0,
var weightBak: Double = 0.0,
var createTime: String? = null, var createTime: String? = null,
var putCabinetDay: String? = null,
var updateTime: String? = null,
var overdueDay: String? = null, var overdueDay: String? = null,
var temperature: String? = null, var temperature: String? = null,
var humidity: String = "0", var humidity: String = "0",
var overdue:Boolean ?= false
) : Serializable ) : Serializable
@@ -3,5 +3,6 @@ package com.shuwei.intelligent.shelves.model
data class ShelfResult( data class ShelfResult(
val placeId: String = "", val placeId: String = "",
val canteenName: String = "", val canteenName: String = "",
val deviceName: String = "",
val containerGoodsList: List<ShelfModel>? = null val containerGoodsList: List<ShelfModel>? = null
) )
@@ -3,7 +3,6 @@ package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.App import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.DeviceConfigInfo import com.shuwei.intelligent.shelves.model.DeviceConfigInfo
import com.shuwei.intelligent.shelves.model.GoodsModel import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.GoodsRecord
import com.shuwei.intelligent.shelves.model.ShelfBody import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfResult import com.shuwei.intelligent.shelves.model.ShelfResult
import retrofit2.http.Body import retrofit2.http.Body
@@ -29,23 +28,19 @@ interface ApiService {
@GET("/equipment/stEquipment/queryByEquipmentCode") @GET("/equipment/stEquipment/queryByEquipmentCode")
suspend fun getDeviceInfo( suspend fun getDeviceInfo(
@Query("equipmentCode") equipmentCode: String, @Query("equipmentCode") equipmentCode: String,
@Query("appVersion") appVersion: String = App.appVersion, @Query("appVersion") appVersion: String = App.appVersion
@Header("X-Access-Token") token: String
): RespData<DeviceConfigInfo> ): RespData<DeviceConfigInfo>
@GET @GET
suspend fun getAccessToken( suspend fun getAccessToken(
@Url url: String = UrlConfig.GET_ACCESS_TOKEN, @Url url: String = UrlConfig.GET_ACCESS_TOKEN,
@Query("qrcodeId") qrcodeId: String, @Query("qrcodeId") qrcodeId: String
@Query("x-access-token") token: String = App.accessToken
): RespData<String?> ): RespData<String?>
@GET @GET
suspend fun getShelfList( suspend fun getShelfList(
@Url url: String = UrlConfig.GET_SHELF_LIST, @Url url: String = UrlConfig.GET_SHELF_LIST,
@Query("deviceId") deviceId: String, @Query("deviceId") deviceId: String
@Query("x-access-token") token: String = App.accessToken,
@Header("authorization") authorization: String = "57ee87183f2a4fa59683ec9ef41c8f5d"
): RespData<ShelfResult> ): RespData<ShelfResult>
@GET @GET
@@ -54,17 +49,13 @@ interface ApiService {
@Query("canteenId") canteenId: String = "0", @Query("canteenId") canteenId: String = "0",
@Query("goodsName") goodsName: String? = null, @Query("goodsName") goodsName: String? = null,
@Query("pageNo") pageNo: Int = 1, @Query("pageNo") pageNo: Int = 1,
@Query("pageSize") pageSize: Int = 50, @Query("pageSize") pageSize: Int = 50
@Query("x-access-token") token: String = App.accessToken,
@Header("authorization") authorization: String = "57ee87183f2a4fa59683ec9ef41c8f5d"
): RespData<MutableList<GoodsModel>?> ): RespData<MutableList<GoodsModel>?>
@POST @POST
suspend fun saveShelfGoodsList( suspend fun saveShelfGoodsList(
@Url url: String = UrlConfig.SAVE_SHELF_GOODS_LIST, @Url url: String = UrlConfig.SAVE_SHELF_GOODS_LIST,
@Body body: ShelfBody, @Body body: ShelfBody
@Query("x-access-token") token: String = App.accessToken,
@Header("authorization") authorization: String = "57ee87183f2a4fa59683ec9ef41c8f5d"
): RespData<Any?> ): RespData<Any?>
} }
@@ -6,6 +6,7 @@ import com.shuwei.intelligent.shelves.App
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -32,13 +33,24 @@ class HttpManager private constructor() {
writeTimeout(15, TimeUnit.SECONDS) writeTimeout(15, TimeUnit.SECONDS)
sslSocketFactory(createSSLSocketFactory(), TrustAllCerts()) sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
hostnameVerifier { _, _ -> true } hostnameVerifier { _, _ -> true }
addInterceptor(LoggingInterceptor()) // addInterceptor(LoggingInterceptor())
addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Log.d("HttpManager","okhttp logger ==>${it}")
}).apply {
level =
// if (MyApp.DEBUG) {
HttpLoggingInterceptor.Level.BODY
// } else {
// HttpLoggingInterceptor.Level.NONE
// }
})
// addInterceptor { chain -> // addInterceptor { chain ->
// val request = chain.request().newBuilder() // val request = chain.request().newBuilder()
// .header("x-access-token", App.accessToken) // .header("x-access-token", App.accessToken)
// .build() // .build()
// chain.proceed(request) // chain.proceed(request)
// } // }
addInterceptor(RequestInterceptor())
} }
.build() .build()
} }
@@ -49,19 +61,18 @@ class HttpManager private constructor() {
// fun newCall(request: Request): Call = client.newCall(request) // fun newCall(request: Request): Call = client.newCall(request)
inner class LoggingInterceptor : Interceptor { // inner class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { // override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request() // val request = chain.request()
// 打印请求日志 // // 打印请求日志
Log.d("OkHttp", "--> ${request.method},${request.url}") // Log.d("HttpManager", "--> ${request.method},${request.url},${request.body}")
//
val response = chain.proceed(request) // val response = chain.proceed(request)
// 打印响应日志 // // 打印响应日志
Log.d("OkHttp", "<-- ${response.code},${response.request.url}") // Log.d("HttpManager", "<-- ${response.body}")
// return response
return response // }
} // }
}
// 信任所有证书的TrustManager实现 // 信任所有证书的TrustManager实现
@SuppressLint("CustomX509TrustManager") @SuppressLint("CustomX509TrustManager")
@@ -1,5 +1,6 @@
package com.shuwei.intelligent.shelves.net package com.shuwei.intelligent.shelves.net
import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
@@ -15,24 +16,34 @@ object Loading {
private var dialog: LoadingDialog? = null private var dialog: LoadingDialog? = null
fun show(context: Context) { fun show(activity: Activity) {
if (dialog?.isShowing == true) { try {
dialog?.dismiss() if (activity.isFinishing || activity.isDestroyed) {
return
}
if (dialog?.isShowing == true) {
dialog?.dismiss()
}
if (dialog == null) {
dialog = LoadingDialog(activity)
}
dialog?.show()
} catch (e: Exception) {
e.printStackTrace()
} }
if (dialog == null) {
dialog = LoadingDialog(context)
}
dialog?.show()
} }
fun dismiss() { fun dismiss() {
dialog?.let { try {
if (it.isShowing) { dialog?.let {
it.dismiss() if (it.isShowing) {
it.dismiss()
}
} }
dialog = null
} catch (e: Exception) {
e.printStackTrace()
} }
dialog = null
} }
} }
@@ -47,12 +58,13 @@ class LoadingDialog(
binding.tvMessage.text = message binding.tvMessage.text = message
setContentView(binding.root) setContentView(binding.root)
setCancelable(true) setCancelable(true)
setCanceledOnTouchOutside(true)
window?.run { window?.run {
setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
setFlags( // setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
); // )
} }
setOnDismissListener { setOnDismissListener {
onDismiss() onDismiss()
@@ -1,9 +1,13 @@
package com.shuwei.intelligent.shelves.net package com.shuwei.intelligent.shelves.net
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.ShelfBody import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.net.HttpManager.Companion.instance import com.shuwei.intelligent.shelves.net.HttpManager.Companion.instance
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -44,7 +48,7 @@ class NetViewModel : ViewModel() {
_getDeviceTokenUiState.value = UiState.Loading _getDeviceTokenUiState.value = UiState.Loading
runCatching { runCatching {
val response = apiService.getDeviceToken(qrcodeId = deviceId) val response = apiService.getDeviceToken(qrcodeId = deviceId)
if (response.code == 0) { if (response.isSuccess()) {
_getDeviceTokenUiState.value = UiState.Success(response) _getDeviceTokenUiState.value = UiState.Success(response)
} else { } else {
_getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败") _getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
@@ -60,8 +64,8 @@ class NetViewModel : ViewModel() {
_getDeviceTokenUiState.value = UiState.Loading _getDeviceTokenUiState.value = UiState.Loading
runCatching { runCatching {
val response = val response =
apiService.getDeviceInfo(equipmentCode = deviceId, token = deviceToken) apiService.getDeviceInfo(equipmentCode = deviceId)
if (response.code == 0) { if (response.isSuccess()) {
_getDeviceTokenUiState.value = UiState.Success(response) _getDeviceTokenUiState.value = UiState.Success(response)
} else { } else {
_getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败") _getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
@@ -77,7 +81,7 @@ class NetViewModel : ViewModel() {
_getAccessTokenUiState.value = UiState.Loading _getAccessTokenUiState.value = UiState.Loading
runCatching { runCatching {
val response = apiService.getAccessToken(qrcodeId = deviceId) val response = apiService.getAccessToken(qrcodeId = deviceId)
if (response.code == 0) { if (response.isSuccess()) {
_getAccessTokenUiState.value = UiState.Success(response) _getAccessTokenUiState.value = UiState.Success(response)
} else { } else {
_getAccessTokenUiState.value = UiState.Error(response.msg ?: "请求失败") _getAccessTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
@@ -93,7 +97,7 @@ class NetViewModel : ViewModel() {
_getShelfListUiState.value = UiState.Loading _getShelfListUiState.value = UiState.Loading
runCatching { runCatching {
val response = apiService.getShelfList(deviceId = deviceId) val response = apiService.getShelfList(deviceId = deviceId)
if (response.code == 0) { if (response.isSuccess()) {
_getShelfListUiState.value = UiState.Success(response) _getShelfListUiState.value = UiState.Success(response)
} else { } else {
_getShelfListUiState.value = UiState.Error(response.msg ?: "请求失败") _getShelfListUiState.value = UiState.Error(response.msg ?: "请求失败")
@@ -104,6 +108,21 @@ class NetViewModel : ViewModel() {
} }
} }
fun updateOverdueState(block:(Boolean, List<ShelfModel>?)-> Unit) {
viewModelScope.launch {
runCatching {
val response = apiService.getShelfList(deviceId = App.deviceId)
if (response.isSuccess()) {
block(true, response.data?.containerGoodsList)
} else {
block(false, emptyList())
}
}.onFailure {
block(false, null)
}
}
}
// fun saveShelfGoodsList(body: ShelfBody) { // fun saveShelfGoodsList(body: ShelfBody) {
// viewModelScope.launch { // viewModelScope.launch {
// _saveShelfGoodsListUiState.value = UiState.Loading // _saveShelfGoodsListUiState.value = UiState.Loading
@@ -129,13 +148,16 @@ class NetViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
_getGoodsListUiState.value = UiState.Loading _getGoodsListUiState.value = UiState.Loading
runCatching { runCatching {
if (App.configUrl.isNullOrBlank()) {
App.configUrl = UrlConfig.BASE_URL
}
val response = apiService.getGoodsList( val response = apiService.getGoodsList(
canteenId = canteenId, canteenId = canteenId,
goodsName = goodsName, goodsName = goodsName,
pageNo = pageNo, pageNo = pageNo,
pageSize = pageSize pageSize = pageSize
) )
if (response.code == 0) { if (response.isSuccess()) {
_getGoodsListUiState.value = UiState.Success(response) _getGoodsListUiState.value = UiState.Success(response)
} else { } else {
_getGoodsListUiState.value = UiState.Error(response.msg ?: "请求失败") _getGoodsListUiState.value = UiState.Error(response.msg ?: "请求失败")
@@ -146,4 +168,17 @@ class NetViewModel : ViewModel() {
} }
} }
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}")
}
}
}
} }
@@ -0,0 +1,35 @@
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"
// }
}
@@ -3,7 +3,9 @@ package com.shuwei.intelligent.shelves.net
data class RespData<T>( data class RespData<T>(
// val success: Boolean = false, // val success: Boolean = false,
val msg: String? = null, val msg: String? = null,
val code: Int = 0, val code: String? = null,
val data: T? = null, val data: T? = null,
val total: Int // val total: Int
) ) {
fun isSuccess() = code == "00000"
}
@@ -3,7 +3,7 @@ package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.App import com.shuwei.intelligent.shelves.App
object UrlConfig { object UrlConfig {
const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889" // const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889"
// /** // /**
// * device获取token // * device获取token
// */ // */
@@ -13,9 +13,10 @@ object UrlConfig {
// */ // */
// const val DEVICE_CONFIG = "${DEVICE_BASE_URL}/equipment/stEquipment/queryByEquipmentCode" // const val DEVICE_CONFIG = "${DEVICE_BASE_URL}/equipment/stEquipment/queryByEquipmentCode"
const val BASE_URL = "http://dev.yixiong-tech.com:8081" const val BASE_URL = "https://dev.yixiong-tech.com:8081"
// const val BASE_URL = "https://yyjk.shuziweidao.com/gateway" // const val BASE_URL = "https://yyjk.shuziweidao.com/gateway"
// const val BASE_URL = "http://192.168.1.201:14801" // const val BASE_URL = "http://192.168.1.201:14801"
const val DEVICE_BASE_URL = BASE_URL
var GET_ACCESS_TOKEN = "${App.configUrl}/restaurant/equipment/stEquipment/getEquipmentToken" var GET_ACCESS_TOKEN = "${App.configUrl}/restaurant/equipment/stEquipment/getEquipmentToken"
var GET_SHELF_LIST = var GET_SHELF_LIST =
@@ -1,6 +1,7 @@
package com.shuwei.intelligent.shelves.serial package com.shuwei.intelligent.shelves.serial
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.lifecycle.lifecycleScope
import com.shuwei.intelligent.shelves.utils.hexToBytes import com.shuwei.intelligent.shelves.utils.hexToBytes
import com.shuwei.intelligent.shelves.utils.toHexString2 import com.shuwei.intelligent.shelves.utils.toHexString2
import io.github.jeadyx.jserialport.AndroidSerialPort import io.github.jeadyx.jserialport.AndroidSerialPort
@@ -14,8 +15,9 @@ import java.util.Locale
object SerialPortManager { object SerialPortManager {
private const val portName: String = "/dev/ttyS1" private const val portName: String = "/dev/ttyS1"
private const val baudRate: Int = 115200 private const val baudRate: Int = 115200
private var serialPort: AndroidSerialPort? = null private var serialPort: AndroidSerialPort? = null
// private var serialPort: SerialPort? = null
// private var serialPort: SerialPort? = null
private val scope = CoroutineScope(Dispatchers.IO) private val scope = CoroutineScope(Dispatchers.IO)
// 打开串口 // 打开串口
@@ -1,32 +1,32 @@
package com.shuwei.intelligent.shelves.task //package com.shuwei.intelligent.shelves.task
//
import android.content.Context //import android.content.Context
import android.util.Log //import android.util.Log
import androidx.work.CoroutineWorker //import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters //import androidx.work.WorkerParameters
import com.shuwei.intelligent.shelves.HomeActivity //import com.shuwei.intelligent.shelves.activity.HomeActivity
import com.shuwei.intelligent.shelves.serial.SerialPortManager //import com.shuwei.intelligent.shelves.serial.SerialPortManager
//
class SyncDeviceTask(appContext: Context, workerParams: WorkerParameters) : //class SyncDeviceTask(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) { // CoroutineWorker(appContext, workerParams) {
//
companion object { // companion object {
private const val TAG = "SyncDeviceTask" // private const val TAG = "SyncDeviceTask"
} // }
//
override suspend fun doWork(): Result { // override suspend fun doWork(): Result {
return try { // return try {
// 执行后台任务逻辑 // // 执行后台任务逻辑
Log.d(TAG, "doWork: ----------SyncDeviceTask----------") // Log.d(TAG, "doWork: ----------SyncDeviceTask----------")
SerialPortManager.send(HomeActivity.DEVICE_INFO_CMD) // SerialPortManager.send(HomeActivity.DEVICE_INFO_CMD)
//
TaskManager.startDeviceTask() // TaskManager.startDeviceTask()
//
Result.success() // Result.success()
} catch (e: Exception) { // } catch (e: Exception) {
e.printStackTrace() // e.printStackTrace()
Result.retry() // Result.retry()
} // }
} // }
//
} //}
@@ -1,58 +1,67 @@
package com.shuwei.intelligent.shelves.task //package com.shuwei.intelligent.shelves.task
//
import android.content.Context //import android.content.Context
import android.util.Log //import android.util.Log
import androidx.work.CoroutineWorker //import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters //import androidx.work.WorkerParameters
import com.shuwei.intelligent.shelves.App //import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.HomeActivity //import com.shuwei.intelligent.shelves.activity.HomeActivity
import com.shuwei.intelligent.shelves.model.ShelfBody //import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.net.apiService //import com.shuwei.intelligent.shelves.net.apiService
import com.shuwei.intelligent.shelves.utils.ext.toJsonString //import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import kotlinx.coroutines.Dispatchers //import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext //import kotlinx.coroutines.withContext
//
class SyncTask(appContext: Context, workerParams: WorkerParameters) : //class SyncTask(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) { // CoroutineWorker(appContext, workerParams) {
//
companion object { // companion object {
private const val TAG = "SyncTask" // private const val TAG = "SyncTask"
} // }
//
override suspend fun doWork(): Result { // override suspend fun doWork(): Result {
return try { // return try {
// 执行后台任务逻辑 // // 执行后台任务逻辑
performSync() // performSync()
//
TaskManager.startTask() // TaskManager.startTask()
//
Result.success() // Result.success()
} catch (e: Exception) { // } catch (e: Exception) {
e.printStackTrace() // e.printStackTrace()
Result.retry() // Result.retry()
} // }
} // }
//
private suspend fun performSync() { // private suspend fun performSync() {
withContext(Dispatchers.IO) { // withContext(Dispatchers.IO) {
val list = HomeActivity.list // val list = HomeActivity.list
Log.d(TAG, "performSync: list:${list.toJsonString()}") // Log.d(TAG, "performSync: list:${list.toJsonString()}")
val submitList = list.sortedBy { it.deviceNo } // val submitList = list.sortedBy { it.deviceNo }
// submitList.forEach { // 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.temperature = HomeActivity.showTemperatureC
// it.humidity = HomeActivity.showHumidity // it.humidity = HomeActivity.showHumidity
// it.goodsList = submitList
// } // }
val body = ShelfBody().also { // Log.d(TAG, "performSync: body:${body.toJsonString()}")
it.deviceId = App.deviceId ?: "" // try {
it.canteenId = App.canteenId // val resp = apiService.saveShelfGoodsList(body = body)
it.temperature = HomeActivity.showTemperatureC // Log.d(TAG, "performSync: resp:${resp.toJsonString()}")
it.humidity = HomeActivity.showHumidity // } catch (e: Exception) {
it.goodsList = submitList // e.printStackTrace()
} // }
Log.d(TAG, "performSync: body:${body.toJsonString()}") // }
val resp = apiService.saveShelfGoodsList(body = body) // }
Log.d(TAG, "performSync: resp:${resp.toJsonString()}") //
} //}
}
}
@@ -1,58 +1,59 @@
package com.shuwei.intelligent.shelves.task //package com.shuwei.intelligent.shelves.task
//
import androidx.work.ExistingWorkPolicy //import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder //import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager //import androidx.work.WorkManager
import com.shuwei.intelligent.shelves.App //import com.shuwei.intelligent.shelves.App
import java.util.concurrent.TimeUnit //import java.util.concurrent.TimeUnit
//
object TaskManager { //object TaskManager {
const val TASK_NAME = "IntelligentShelvesSyncTask" // const val TASK_NAME = "IntelligentShelvesSyncTask"
const val DEVICE_TASK_NAME = "IntelligentShelvesDeviceTask" // const val DEVICE_TASK_NAME = "IntelligentShelvesDeviceTask"
//
fun startTask() { // fun startTask() {
val nextRequest = OneTimeWorkRequestBuilder<SyncTask>() // val nextRequest = OneTimeWorkRequestBuilder<SyncTask>()
.setInitialDelay(2, TimeUnit.MINUTES) // .setInitialDelay(2, TimeUnit.MINUTES)
// .setInitialDelay(30, TimeUnit.SECONDS) //// .setInitialDelay(30, TimeUnit.SECONDS)
// .setInputData(inputData) //// .setInitialDelay(10, TimeUnit.MINUTES)
.build() //// .setInputData(inputData)
WorkManager.getInstance(App.getInstance())
.enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// val constraints = Constraints.Builder()
// .setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresBatteryNotLow(true)
// .build() // .build()
// val syncRequest = PeriodicWorkRequestBuilder<SyncTask>( // WorkManager.getInstance(App.getInstance())
// 3L, TimeUnit.MINUTES, // 最小间隔15分钟 // .enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// 3L, TimeUnit.MINUTES // 弹性时间窗口 //
// ) //// val constraints = Constraints.Builder()
// .setConstraints(constraints) //// .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() // .build()
// WorkManager.getInstance(App.get()) // WorkManager.getInstance(App.getInstance())
// .enqueueUniquePeriodicWork( // .enqueueUniqueWork(DEVICE_TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// TASK_NAME, // }
// ExistingPeriodicWorkPolicy.KEEP, //
// syncRequest // fun cancelDeviceTask() {
// ) // WorkManager.getInstance(App.getInstance())
} // .cancelUniqueWork(DEVICE_TASK_NAME)
// }
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)
}
}
@@ -406,44 +406,45 @@ public class AppUtil {
*/ */
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
public static String getUDID(Context context) { public static String getUDID(Context context) {
return "test111"; // return "SWZC-HG-001";
// String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); //// //"test111";
// if (!androidID.equals("")) { String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
// try { if (!androidID.equals("")) {
// if (!"9774d56d682e549c".equals(androidID)) { try {
// androidID = UUID.nameUUIDFromBytes(androidID.getBytes("utf8")).toString(); if (!"9774d56d682e549c".equals(androidID)) {
// } else { androidID = UUID.nameUUIDFromBytes(androidID.getBytes("utf8")).toString();
// @SuppressLint("MissingPermission") final String deviceId = ((TelephonyManager) context.getSystemService(TELEPHONY_SERVICE)).getDeviceId(); } else {
// androidID = deviceId != null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")).toString() : UUID.randomUUID().toString(); @SuppressLint("MissingPermission") final String deviceId = ((TelephonyManager) context.getSystemService(TELEPHONY_SERVICE)).getDeviceId();
// } androidID = deviceId != null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")).toString() : UUID.randomUUID().toString();
// } catch (UnsupportedEncodingException e) { }
// throw new RuntimeException(e); } catch (UnsupportedEncodingException e) {
// } throw new RuntimeException(e);
// return androidID; }
// } return androidID;
// }
// //需要权限 android.permission.READ_PHONE_STATE
// TelephonyManager TelephonyMgr = (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); //需要权限 android.permission.READ_PHONE_STATE
// String szImei = TelephonyMgr.getDeviceId(); TelephonyManager TelephonyMgr = (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
// if (!szImei.equals("")) { String szImei = TelephonyMgr.getDeviceId();
// return szImei; if (!szImei.equals("")) {
// } return szImei;
// }
// //需要权限 android.permission.ACCESS_WIFI_STATE
// WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); //需要权限 android.permission.ACCESS_WIFI_STATE
// String m_szWLANMAC = wm.getConnectionInfo().getMacAddress(); WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
// if (!m_szWLANMAC.equals("")) { String m_szWLANMAC = wm.getConnectionInfo().getMacAddress();
// return m_szWLANMAC; if (!m_szWLANMAC.equals("")) {
// } return m_szWLANMAC;
// }
// //需要权限 android.permission.BLUETOOTH
// BluetoothAdapter m_BluetoothAdapter = null; //需要权限 android.permission.BLUETOOTH
// m_BluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter m_BluetoothAdapter = null;
// String m_szBTMAC = m_BluetoothAdapter.getAddress(); m_BluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// if (!m_szBTMAC.equals("")) { String m_szBTMAC = m_BluetoothAdapter.getAddress();
// return m_szBTMAC; if (!m_szBTMAC.equals("")) {
// } return m_szBTMAC;
// return getUniquePsuedoID(); }
return getUniquePsuedoID();
} }
//获得 Psuedo ID //获得 Psuedo ID
@@ -3,7 +3,7 @@ package com.shuwei.intelligent.shelves.utils
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.shuwei.intelligent.shelves.InitActivity import com.shuwei.intelligent.shelves.activity.InitActivity
import kotlin.jvm.java import kotlin.jvm.java
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
@@ -0,0 +1,251 @@
package com.shuwei.intelligent.shelves.utils
import android.app.ActivityManager
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Process
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.system.exitProcess
/**
* 崩溃处理
*/
class CrashHandler private constructor(private val context: Context) :
Thread.UncaughtExceptionHandler {
companion object {
private const val TAG = "CrashHandler"
const val CRASH_REPORTS_DIR = "crash_reports"
private const val LOG_LINES = 500 // 收集最近500行日志
@Volatile
private var instance: CrashHandler? = null
fun init(context: Context) {
if (instance == null) {
synchronized(CrashHandler::class.java) {
if (instance == null) {
instance = CrashHandler(context.applicationContext)
}
}
}
}
//fun getCrashReportFiles(context: Context): Array<File> {
// val crashDir = getCrashDir()
// return if (crashDir.exists() && crashDir.isDirectory) {
// crashDir.listFiles { _, name -> name.endsWith(".log") } ?: emptyArray()
// } else {
// emptyArray()
// }
//}
//
//fun clearCrashReports(context: Context) {
// getCrashReportFiles(context).forEach { it.delete() }
//}
}
private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
init {
Thread.setDefaultUncaughtExceptionHandler(this)
}
override fun uncaughtException(thread: Thread, ex: Throwable) {
handleException(thread, ex)
try {
Thread.sleep(3000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
// 如果系统提供了默认的异常处理器,则交给系统去结束程序
// 否则自己结束程序
defaultHandler?.uncaughtException(thread, ex) ?: run {
Process.killProcess(Process.myPid())
exitProcess(1)
}
}
/**
* 自动重启app
*/
// private fun restartApp() {
// // 延迟1秒后重启应用
// Handler(Looper.getMainLooper()).postDelayed({
// val intent = Intent(context, MainActivity::class.java).apply {
// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// }
//
// val pendingIntent = PendingIntent.getActivity(
// context, 0, intent,
// PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
// )
//
// val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent)
//
// Process.killProcess(Process.myPid())
// exitProcess(1)
// }, 1000)
// }
private fun handleException(thread: Thread, ex: Throwable) {
// 收集设备信息和异常信息
val crashInfo = collectCrashInfo(thread, ex)
// 保存日志文件
saveCrashInfoToFile(crashInfo)
// 这里可以添加其他处理逻辑,比如上传到服务器等
}
private fun collectCrashInfo(thread: Thread, ex: Throwable): String {
return buildString {
// 收集设备信息
collectDeviceInfo(this)
// 收集应用日志
append("\n\n").append(collectLogs())
// 收集线程和异常信息
append("\n\n========== Thread & Exception Info ==========\n")
append("Thread: ${thread.name}\n")
append("Stack Trace:\n")
val sw = StringWriter()
val pw = PrintWriter(sw)
ex.printStackTrace(pw)
var cause: Throwable? = ex.cause
while (cause != null) {
cause.printStackTrace(pw)
cause = cause.cause
}
pw.close()
append(sw.toString())
}
}
private fun collectDeviceInfo(sb: StringBuilder) {
sb.append("========== Device Info ==========\n")
try {
// 应用信息
val pm = context.packageManager
val pi = pm.getPackageInfo(context.packageName, 0)
sb.append("App Version: ${pi.versionName}_${pi.versionCode}\n")
// Android 版本信息
sb.append("OS Version: ${Build.VERSION.RELEASE}_${Build.VERSION.SDK_INT}\n")
// 设备信息
sb.append("Vendor: ${Build.MANUFACTURER}\n")
sb.append("Model: ${Build.MODEL}\n")
sb.append("CPU ABI: ${Build.SUPPORTED_ABIS[0]}\n")
// 其他信息
sb.append("Locale: ${Locale.getDefault()}\n")
sb.append(
"Current Time: ${
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss",
Locale.getDefault()
).format(Date())
}\n"
)
// 内存信息
val memoryInfo = ActivityManager.MemoryInfo()
val activityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(memoryInfo)
sb.append("Available Memory: ${memoryInfo.availMem / (1024 * 1024)}MB\n")
sb.append("Total Memory: ${memoryInfo.totalMem / (1024 * 1024)}MB\n")
sb.append("Low Memory: ${memoryInfo.lowMemory}\n")
} catch (e: Exception) {
Log.e("","Error while collecting device info")
sb.append("Error while collecting device info: ${e.message}\n")
}
}
private fun collectLogs(): String {
return buildString {
append("========== Application Logs ==========\n")
try {
val process = Runtime.getRuntime().exec("logcat -d -v threadtime")
val reader = process.inputStream.bufferedReader()
val logLines = reader.readLines()
val start = maxOf(0, logLines.size - LOG_LINES)
logLines.subList(start, logLines.size).forEach {
append(it).append("\n")
}
} catch (e: IOException) {
Log.e(TAG, "Error collecting logs")
append("Error collecting logs: ${e.message}\n")
}
}
}
private fun saveCrashInfoToFile(crashInfo: String) {
try {
val time = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()).format(Date())
val fileName = "crash_$time.log"
val crashDir = getCrashDir()
val crashFile = File(crashDir, fileName)
FileOutputStream(crashFile).use { it.write(crashInfo.toByteArray()) }
Log.d(TAG,"Crash info saved to: ${crashFile.absolutePath}")
} catch (e: Exception) {
Log.e(TAG, "Error saving crash info to file")
}
}
/**
* 清理旧的崩溃日志
*/
fun cleanupOldCrashReports(maxAgeDays: Int = 7) {
val crashDir = getCrashDir()
if (!crashDir.exists() || !crashDir.isDirectory) return
val now = System.currentTimeMillis()
val maxAgeMillis = maxAgeDays * 24 * 60 * 60 * 1000L
crashDir.listFiles()?.forEach { file ->
if (file.lastModified() < now - maxAgeMillis) {
file.delete()
}
}
}
private fun getCrashDir():File {
//val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
var crashDir = File(context.filesDir, CRASH_REPORTS_DIR)
if (!crashDir.exists()) {
crashDir.mkdirs()
}
if (!(crashDir.exists())) {
crashDir = File(context.cacheDir, CRASH_REPORTS_DIR)
}
return crashDir
}
}
@@ -0,0 +1,131 @@
package com.shuwei.intelligent.shelves.utils
import android.content.Context
import com.shuwei.intelligent.shelves.utils.CrashHandler.Companion.CRASH_REPORTS_DIR
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
/**
* 文件日志记录器
* 功能:保存日志到本地文件,每条数据一行,每天生成新文件
*/
class FileLogger(private val context: Context) {
companion object {
private const val LOG_DIR = "logs"
private const val FILE_PREFIX = "log_"
private const val FILE_EXTENSION = ".txt"
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
private val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
}
/**
* 记录日志
* @param message 日志消息
* @param level 日志级别
*/
fun log(message: String, level: LogLevel = LogLevel.INFO) {
try {
val logEntry = formatLogEntry(message, level)
writeToFile(logEntry)
} catch (e: IOException) {
e.printStackTrace()
}
}
/**
* 格式化日志条目
*/
private fun formatLogEntry(message: String, level: LogLevel): String {
val timestamp = timeFormat.format(Date())
return "[$timestamp] [$level] $message\n"
}
/**
* 写入文件
*/
private fun writeToFile(logEntry: String) {
// val path = File(context.filesDir, LOG_DIR)
// if (path.exists().not()) {
// path.mkdirs()
// }
val fileName = getCurrentDateFileName()
// val file = File(path, fileName)
// if (file.exists().not()) {
// file.createNewFile()
// }
val output = context.openFileOutput(fileName, Context.MODE_APPEND)
val writer = BufferedWriter(OutputStreamWriter(output))
writer.use {
it.write(logEntry)
}
}
/**
* 获取当前日期对应的文件名
*/
private fun getCurrentDateFileName(): String {
val date = dateFormat.format(Date())
return "${FILE_PREFIX}${date}${FILE_EXTENSION}"
}
/**
* 读取指定日期的日志文件
* @param date 日期字符串,格式:yyyy-MM-dd
*/
fun readLogsByDate(date: String): List<String> {
val fileName = "${FILE_PREFIX}${date}${FILE_EXTENSION}"
return readLogsByName(fileName)
}
fun readLogsByName(fileName: String): List<String> {
try {
val input = context.openFileInput(fileName)
val reader = BufferedReader(InputStreamReader(input))
val list: MutableList<String> = reader.useLines { lines ->
lines.toList().toMutableList()
}
// val input2 = FileInputStream(getCrashDir(context))
// val reader2 = BufferedReader(InputStreamReader(input2))
// val list2: MutableList<String> = reader2.useLines {lines ->
// lines.toList().toMutableList()
// }
// list.addAll(list2)
return list
} catch (e: FileNotFoundException) {
return emptyList()
}
}
public fun getCrashDir(context: Context):File {
//val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
var crashDir = File(context.filesDir, CRASH_REPORTS_DIR)
if (!crashDir.exists()) {
crashDir.mkdirs()
}
if (!(crashDir.exists())) {
crashDir = File(context.cacheDir, CRASH_REPORTS_DIR)
}
return crashDir
}
/**
* 获取所有日志文件列表
*/
fun getLogFiles(): List<String> {
return context.fileList()
.filter { it.startsWith(FILE_PREFIX) && it.endsWith(FILE_EXTENSION) }
//.map { it.replace(FILE_EXTENSION, "") }
}
/**
* 日志级别枚举
*/
enum class LogLevel {
DEBUG, INFO, WARN, ERROR
}
}
@@ -0,0 +1,64 @@
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());
}
}
}
}
@@ -0,0 +1,64 @@
package com.shuwei.intelligent.shelves.utils
import kotlinx.coroutines.*
class IntervalExecutor {
/**
* 启动定时任务
* @param delayMillis 延迟时间(毫秒)
* @param action 要执行的方法
* @return Job 可用于取消任务
*/
fun startIntervalTask(delayMillis: Long, action: suspend () -> Unit): Job {
return CoroutineScope(Dispatchers.Default).launch {
while (isActive) {
action()
delay(delayMillis)
}
}
}
/**
* 启动定时任务(带初始延迟)
* @param initialDelay 初始延迟时间(毫秒)
* @param delayMillis 后续执行间隔(毫秒)
* @param action 要执行的方法
* @return Job 可用于取消任务
*/
fun startIntervalTaskWithInitialDelay(
initialDelay: Long,
delayMillis: Long,
action: suspend () -> Unit
): Job {
return CoroutineScope(Dispatchers.Default).launch {
delay(initialDelay)
while (isActive) {
action()
delay(delayMillis)
}
}
}
}
// 使用示例
fun main() = runBlocking {
val executor = IntervalExecutor()
// 示例1:每隔10秒执行一次
val job1 = executor.startIntervalTask(10000) {
println("定时任务执行: ${System.currentTimeMillis()}")
// 这里可以执行你的业务逻辑
}
// 示例2:先延迟5秒,然后每隔3秒执行一次
val job2 = executor.startIntervalTaskWithInitialDelay(5000, 3000) {
println("带初始延迟的定时任务: ${System.currentTimeMillis()}")
}
// 运行30秒后取消任务
delay(30000)
job1.cancel()
job2.cancel()
println("所有定时任务已取消")
}
@@ -0,0 +1,24 @@
package com.shuwei.intelligent.shelves.utils
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
/**
* 网络连接检测工具类
* 提供检测设备网络连接状态的功能
*/
object NetworkUtils {
/**
* 检测设备是否连接到网络
* @param context 应用上下文
* @return true 表示已连接网络,false 表示未连接
*/
fun isNetworkConnected(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}
@@ -0,0 +1,36 @@
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
}
//同一产品
}
}
@@ -0,0 +1,282 @@
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);
}
}
@@ -10,6 +10,7 @@ import android.os.Bundle
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText import android.widget.EditText
import android.widget.Toast import android.widget.Toast
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
@@ -22,6 +23,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import android.content.ClipData
import android.content.ClipboardManager
import android.os.SystemClock
fun Context.toast( fun Context.toast(
message: String?, message: String?,
@@ -151,3 +155,43 @@ fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) {
} }
} }
} }
fun View.hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(this.windowToken, 0)
this.clearFocus() // 清除焦点避免键盘再次弹出
}
fun String.copyTextToClipboard(context: Context) {
// 获取系统服务中的ClipboardManager
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
// 创建一个ClipData对象,包含要复制的文本
val clip = ClipData.newPlainText("label", this)
// 将ClipData对象设置到剪贴板
clipboard.setPrimaryClip(clip)
// 显示一个简单的Toast提示用户文本已复制
Toast.makeText(context, "文本已复制到剪贴板", Toast.LENGTH_SHORT).show()
}
/**
* 为View添加双击点击事件的扩展函数
*/
fun View.setOnDoubleClickListener(onDoubleClickListener: (View) -> Unit) {
var lastClickTime = 0L
var lastClickView: View? = null
this.setOnClickListener { view ->
val currentTime = SystemClock.elapsedRealtime()
if (lastClickView === view && currentTime - lastClickTime < 500) {
// 双击事件触发
onDoubleClickListener(view)
lastClickTime = 0
} else {
lastClickTime = currentTime
lastClickView = view
}
}
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#4ECDC4" />
<corners android:radius="12dp" />
<stroke
android:width="2dp"
android:color="#2BA39F" />
</shape>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="80dp"
android:height="80dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- 网络断开的图标 -->
<path
android:fillColor="#FF6B6B"
android:pathData="M1,9l2,2c4.97,-4.97 13.03,-4.97 18,0l2,-2C16.93,2.93 7.08,2.93 1,9zM5,13l2,2c2.76,-2.76 7.24,-2.76 10,0l2,-2c-4.42,-4.42 -11.58,-4.42 -16,0zM9,17h6v2h-6z" />
</vector>
@@ -0,0 +1,14 @@
<?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>
@@ -0,0 +1,7 @@
<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,45 +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">
<FrameLayout
android:id="@+id/flStatusBar"
android:layout_width="match_parent"
android:layout_height="68dp"
android:paddingStart="25dp"
android:paddingEnd="25dp"
android:paddingTop="25dp">
<TextView
android:id="@+id/tv_left_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:layout_gravity="start"
android:textSize="24sp"
android:fontFamily="sans-serif-medium"
tools:text="1-1冷藏柜 -2°C / 87%" />
<TextView
android:id="@+id/tv_right_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:layout_gravity="end"
android:fontFamily="sans-serif-medium"
android:textSize="24sp"
tools:text="6月22日 星期日 15:26:33" />
</FrameLayout>
<FrameLayout
android:id="@+id/flContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
@@ -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>
@@ -1,48 +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">
<ImageView
android:layout_width="450dp"
android:layout_height="430dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="150dp"
android:src="@drawable/img_init"
tools:ignore="ContentDescription"
android:visibility="invisible"/>
<TextView
android:id="@+id/btnInit"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="100dp"
android:background="@drawable/bg_init_button"
android:gravity="center"
android:padding="10dp"
android:text="设备初始化"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
android:visibility="visible"
tools:ignore="HardcodedText" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/ivQrCode"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="50dp"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_logo512" />
</LinearLayout>
@@ -1,240 +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:gravity="center"
android:textColor="@color/bg_page"
android:textSize="30sp"
android:fontFamily="sans-serif-medium"
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:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清零"
android:fontFamily="sans-serif-medium"
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_marginTop="24dp"
android:layout_marginStart="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:hint="输入食材名称"
android:maxLines="1"
android:inputType="text"
android:text=""
android:imeOptions="actionSearch"
android:textColor="@color/food_name_black"
android:paddingStart="1dp"
android:paddingEnd="1dp"
android:textColorHint="#B4BEC8"
android:fontFamily="sans-serif-medium"
android:textSize="30sp"
tools:ignore="Autofill,HardcodedText,TextFields" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="24dp">
<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:paddingStart="36dp"
android:paddingBottom="12dp"
android:paddingTop="12dp"
android:paddingEnd="36dp"
android:clipToPadding="false"
android:overScrollMode="never"
tools:listitem="@layout/list_item_search"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"/>
<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"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/layout_empty_view"
android:visibility="gone"/>
</FrameLayout>
<!-- android:layout_marginTop="48dp"-->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="48dp"
android:layout_marginStart="48dp"
android:layout_marginBottom="24dp"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="确定"
android:textStyle="bold"
android:textColor="@color/white"
android:textSize="30sp"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_dialog">
<TextView
android:id="@+id/tvMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingStart="25dp"
android:paddingTop="13dp"
android:paddingBottom="13dp"
android:paddingEnd="25dp"
android:textColor="@color/white_f6"
android:textSize="32sp"
tools:text="暂无数据" />
</FrameLayout>
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="20dp"
android:paddingTop="10dp"
android:paddingEnd="20dp"
android:paddingBottom="10dp"
android:background="@drawable/shape_dialog"
tools:ignore="MissingDefaultResource">
<ProgressBar
android:layout_width="60dp"
android:layout_height="60dp"
android:indeterminateTint="@color/white_f6" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="加载中……"
android:textColor="@color/white_f6"
android:textSize="26sp"
tools:ignore="HardcodedText" />
</LinearLayout>
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="center"
android:orientation="vertical"
tools:ignore="MissingDefaultResource,UseCompoundDrawables">
<ImageView
android:id="@+id/ivEmptyIcon"
android:layout_width="270dp"
android:layout_height="wrap_content"
android:src="@drawable/ic_empty_gray"
android:adjustViewBounds="true"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tvEmptyContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black999"
android:layout_marginTop="60dp"
android:textSize="60sp"
android:text="暂无数据"
tools:ignore="HardcodedText" />
</LinearLayout>
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"-->
<!-- xmlns:tools="http://schemas.android.com/tools"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="80dp"-->
<!-- android:layout_margin="12dp">-->
<!-- <TextView-->
<!-- android:id="@+id/btnFoodName"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_gravity="center"-->
<!-- android:ellipsize="end"-->
<!-- android:gravity="center"-->
<!-- android:maxLines="1"-->
<!-- android:paddingStart="5dp"-->
<!-- android:paddingEnd="5dp"-->
<!-- android:fontFamily="sans-serif-medium"-->
<!-- android:textColor="@color/black999"-->
<!-- android:textSize="30sp"-->
<!-- tools:text="土豆丝" />-->
<!--</FrameLayout>-->
<com.google.android.material.button.MaterialButton 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/btnFoodName"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_margin="12dp"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
app:paddingStart="5dp"
app:paddingEnd="5dp"
android:insetRight="0dp"
android:insetLeft="0dp"
android:paddingHorizontal="8dp"
android:textColor="@color/black999"
android:textSize="30sp"
app:cornerRadius="12dp"
app:strokeWidth="2dp"
tools:text="土豆丝土豆丝土豆丝土豆丝土豆丝土豆丝"
/>
@@ -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="5dp"
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>
+8 -13
View File
@@ -5,39 +5,34 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:fitsSystemWindows="false"
tools:background="@color/bg_page"> tools:background="@color/bg_page">
<FrameLayout <FrameLayout
android:id="@+id/flStatusBar" android:id="@+id/flStatusBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="30dp"> android:layout_height="68dp"
<!-- android:paddingTop="9dp"--> android:paddingStart="25dp"
android:paddingEnd="25dp"
android:paddingTop="25dp">
<TextView <TextView
android:id="@+id/tv_left_status" android:id="@+id/tv_left_status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="@color/white" android:textColor="@color/white"
android:layout_gravity="start" android:layout_gravity="start"
android:textSize="12sp" android:textSize="24sp"
android:paddingStart="12dp"
android:paddingEnd="0dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
tools:text="1-1冷藏柜 -2°C / 87%" /> tools:text="1-1冷藏柜 -2°C / 87%" />
<TextView <TextView
android:id="@+id/tv_right_status" android:id="@+id/tv_right_status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="@color/white" android:textColor="@color/white"
android:layout_gravity="end" android:layout_gravity="end"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:textSize="12sp" android:textSize="24sp"
android:paddingStart="0dp"
android:paddingEnd="12dp"
tools:text="6月22日 星期日 15:26:33" /> tools:text="6月22日 星期日 15:26:33" />
</FrameLayout> </FrameLayout>
+2 -10
View File
@@ -13,8 +13,8 @@
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2" app:spanCount="2"
android:overScrollMode="never" android:overScrollMode="never"
android:layout_marginStart="6dp" android:layout_marginStart="12dp"
android:layout_marginEnd="6dp" android:layout_marginEnd="12dp"
tools:listitem="@layout/list_item_shelf"/> tools:listitem="@layout/list_item_shelf"/>
@@ -24,12 +24,4 @@
android:layout_height="match_parent" android:layout_height="match_parent"
layout="@layout/layout_empty_view" layout="@layout/layout_empty_view"
android:visibility="gone"/> android:visibility="gone"/>
<!-- <include-->
<!-- android:id="@+id/includeLight"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- layout="@layout/layout_light_switch"-->
<!-- android:visibility="gone"/>-->
</FrameLayout> </FrameLayout>
+136 -39
View File
@@ -1,48 +1,145 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="vertical">
<ImageView <LinearLayout
android:layout_width="225dp" android:id="@+id/llDeviceInit"
android:layout_height="215dp" android:layout_width="match_parent"
android:layout_gravity="center_horizontal" android:layout_height="match_parent"
android:layout_marginTop="75dp" android:orientation="vertical"
android:src="@drawable/img_init" android:visibility="gone">
tools:ignore="ContentDescription"
android:visibility="invisible"/>
<TextView <ImageView
android:id="@+id/btnInit" android:layout_width="450dp"
android:layout_width="150dp" android:layout_height="430dp"
android:layout_height="wrap_content" android:layout_gravity="center_horizontal"
android:layout_gravity="center_horizontal" android:layout_marginTop="150dp"
android:layout_marginTop="50dp" android:src="@drawable/img_init"
android:background="@drawable/bg_init_button" tools:ignore="ContentDescription"
android:gravity="center" android:visibility="invisible"/>
android:padding="10dp"
android:text="设备初始化"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
android:visibility="visible"
tools:ignore="HardcodedText" />
<Space <TextView
android:layout_width="wrap_content" android:id="@+id/btnInit"
android:layout_height="0dp" android:layout_width="300dp"
android:layout_weight="1" /> android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="100dp"
android:background="@drawable/bg_init_button"
android:gravity="center"
android:padding="10dp"
android:text="设备初始化"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
android:visibility="visible"
tools:ignore="HardcodedText" />
<ImageView <Space
android:id="@+id/ivQrCode" android:layout_width="wrap_content"
android:layout_width="100dp" android:layout_height="0dp"
android:layout_height="100dp" android:layout_weight="1" />
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="25dp"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_logo512" />
</LinearLayout> <ImageView
android:id="@+id/ivQrCode"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="50dp"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_logo512" />
</LinearLayout>
<LinearLayout
android:id="@+id/llNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<!-- Loading 动画容器 -->
<LinearLayout
android:id="@+id/llLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:visibility="visible"
android:layout_marginTop="260dp">
<!-- 加载动画 -->
<ImageView
android:id="@+id/ivLoading"
android:layout_width="160dp"
android:layout_height="160dp"
android:src="@drawable/loading_spinner"
android:contentDescription="加载中" />
<!-- 倒计时文字 -->
<TextView
android:id="@+id/tvCountdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="60秒"
android:textSize="40sp"
android:textColor="@color/black"
android:textStyle="bold"
android:layout_marginTop="50dp" />
<!-- 提示文字 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="网络连接检测中,请稍后……"
android:textSize="22sp"
android:textColor="#555555"
android:layout_marginTop="20dp" />
</LinearLayout>
<!-- 连接网络按钮容器 -->
<LinearLayout
android:id="@+id/llNetworkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone">
<!-- 网络错误图标 -->
<ImageView
android:layout_width="140dp"
android:layout_height="140dp"
android:src="@drawable/ic_network_error"
android:contentDescription="网络连接失败"
android:layout_marginBottom="30dp" />
<!-- 连接失败提示 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="网络连接失败"
android:textSize="36sp"
android:textColor="@color/black"
android:textStyle="bold"
android:layout_marginBottom="40dp" />
<!-- 连接网络按钮 -->
<Button
android:id="@+id/btnConnectNetwork"
android:layout_width="350dp"
android:layout_height="70dp"
android:text="连接网络"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:background="@drawable/btn_connect_network" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/ivBack"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="top|start"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="设置"
android:textColor="@color/black"
android:textSize="28sp" />
<Button
android:id="@+id/btnFind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:textSize="18sp"
android:layout_gravity="end|center_vertical"
android:text="查找1001指令"/>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvLogList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
+95 -80
View File
@@ -9,12 +9,12 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="190dp" android:layout_height="376dp"
android:layout_marginStart="12dp" android:layout_marginStart="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="12dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="24dp"
app:cardBackgroundColor="@color/white" app:cardBackgroundColor="@color/white"
app:cardCornerRadius="6dp" app:cardCornerRadius="12dp"
app:cardElevation="0dp"> app:cardElevation="0dp">
<FrameLayout <FrameLayout
@@ -25,20 +25,20 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="12dp" android:layout_marginStart="24dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="24dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/tvShelfName" android:id="@+id/tvShelfName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="30dp" android:layout_height="60dp"
android:layout_marginTop="12dp" android:layout_marginTop="24dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:gravity="center" android:gravity="center"
android:textColor="@color/bg_page" android:textColor="@color/bg_page"
android:textSize="15sp" android:textSize="30sp"
tools:text="货架 - 03" /> tools:text="货架 - 03" />
<Space <Space
@@ -54,7 +54,7 @@
android:includeFontPadding="false" android:includeFontPadding="false"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/food_weight_orange" android:textColor="@color/food_weight_orange"
android:textSize="30sp" android:textSize="60sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="1506克" /> tools:text="1506克" />
@@ -70,7 +70,7 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/food_name_black" android:textColor="@color/food_name_black"
android:textSize="18sp" android:textSize="36sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="金针菇" /> tools:text="金针菇" />
@@ -82,49 +82,33 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnClearZero" android:id="@+id/btnClearZero"
android:layout_width="65dp" android:layout_width="120dp"
android:layout_height="30dp" android:layout_height="60dp"
android:layout_gravity="top|end" android:layout_gravity="top|end"
android:layout_marginTop="12dp" android:layout_marginTop="24dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="24dp"
android:backgroundTint="@color/bg_page" android:backgroundTint="@color/bg_page"
android:ellipsize="end" android:ellipsize="end"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:insetTop="0dp" android:insetTop="0dp"
android:insetBottom="0dp" android:insetBottom="0dp"
android:maxLines="1" android:maxLines="1"
android:text="清零00" android:text="清零"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="15sp" android:textSize="30sp"
app:cornerRadius="6dp" app:cornerRadius="12dp"
app:elevation="0dp" app:elevation="0dp"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<ImageView <ImageView
android:id="@+id/ivBack" android:id="@+id/ivBack"
android:layout_width="30dp" android:layout_width="60dp"
android:layout_height="30dp" android:layout_height="60dp"
android:layout_gravity="top|start" android:layout_gravity="top|start"
android:layout_marginTop="12dp" android:layout_marginStart="24dp"
android:layout_marginStart="12dp" android:layout_marginTop="24dp"
android:src="@drawable/ic_back_512" android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<TextView
android:id="@+id/btnClearEmpty"
android:layout_width="65dp"
android:layout_height="30dp"
android:layout_gravity="bottom|end"
android:layout_marginBottom="22dp"
android:layout_marginEnd="12dp"
android:maxLines="1"
android:text="清空"
android:textStyle="bold"
android:gravity="center"
android:textColor="@color/food_name_black"
android:textSize="15sp"
tools:ignore="HardcodedText" />
</FrameLayout> </FrameLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
@@ -132,12 +116,12 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="12dp" android:layout_marginStart="24dp"
android:layout_marginTop="12dp" android:layout_marginTop="24dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="24dp"
app:cardBackgroundColor="@color/white" app:cardBackgroundColor="@color/white"
app:cardCornerRadius="6dp" app:cardCornerRadius="12dp"
app:cardElevation="0dp"> app:cardElevation="0dp">
<LinearLayout <LinearLayout
@@ -147,26 +131,26 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="80dp"
android:layout_marginStart="16dp" android:layout_marginStart="48dp"
android:layout_marginTop="16dp" android:layout_marginTop="48dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="48dp"
android:background="@drawable/shape_search" android:background="@drawable/shape_search"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
android:layout_width="16dp" android:layout_width="32dp"
android:layout_height="16dp" android:layout_height="32dp"
android:layout_marginStart="12dp" android:layout_marginStart="24dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="24dp"
android:src="@drawable/ic_search_food" /> android:src="@drawable/ic_search_food" />
<EditText <EditText
android:id="@+id/etInputFood" android:id="@+id/etInputFood"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="36dp" android:layout_height="60dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="24dp"
android:background="@null" android:background="@null"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:hint="输入食材名称" android:hint="输入食材名称"
@@ -178,14 +162,14 @@
android:text="" android:text=""
android:textColor="@color/food_name_black" android:textColor="@color/food_name_black"
android:textColorHint="#B4BEC8" android:textColorHint="#B4BEC8"
android:textSize="15sp" android:textSize="30sp"
tools:ignore="Autofill,HardcodedText,TextFields" /> tools:ignore="Autofill,HardcodedText,TextFields" />
</LinearLayout> </LinearLayout>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="8dp" android:layout_marginTop="24dp"
android:layout_weight="1"> android:layout_weight="1">
<com.scwang.smart.refresh.layout.SmartRefreshLayout <com.scwang.smart.refresh.layout.SmartRefreshLayout
@@ -203,10 +187,10 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:overScrollMode="never" android:overScrollMode="never"
android:paddingStart="12dp" android:paddingStart="36dp"
android:paddingTop="4dp" android:paddingTop="12dp"
android:paddingEnd="12dp" android:paddingEnd="36dp"
android:paddingBottom="4dp" android:paddingBottom="12dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2" app:spanCount="2"
tools:itemCount="10" tools:itemCount="10"
@@ -224,29 +208,60 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone" /> android:visibility="gone" />
</FrameLayout> </FrameLayout>
<!-- android:layout_marginTop="48dp"--> <LinearLayout
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_marginStart="24dp" android:gravity="center_vertical"
android:layout_marginTop="12dp" android:orientation="horizontal">
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp" <com.google.android.material.button.MaterialButton
android:backgroundTint="@color/bg_page" android:id="@+id/btnClearEmpty"
android:ellipsize="end" android:layout_width="0dp"
android:insetTop="0dp" android:layout_height="80dp"
android:insetBottom="0dp" android:layout_marginStart="48dp"
android:maxLines="1" android:layout_marginTop="24dp"
android:text="确定" android:layout_marginEnd="24dp"
android:textColor="@color/white" android:layout_marginBottom="24dp"
android:textSize="15sp" android:layout_weight="1"
android:textStyle="bold" android:backgroundTint="@color/bg_page"
app:cornerRadius="6dp" android:ellipsize="end"
app:elevation="0dp" android:insetTop="0dp"
tools:ignore="HardcodedText" /> 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> </LinearLayout>
+5 -5
View File
@@ -11,12 +11,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:paddingStart="12dp" android:paddingStart="25dp"
android:paddingTop="6dp" android:paddingTop="13dp"
android:paddingBottom="6dp" android:paddingBottom="13dp"
android:paddingEnd="12dp" android:paddingEnd="25dp"
android:textColor="@color/white_f6" android:textColor="@color/white_f6"
android:textSize="16sp" android:textSize="32sp"
tools:text="暂无数据" /> tools:text="暂无数据" />
</FrameLayout> </FrameLayout>
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="400dp"
android:layout_height="300dp"
app:cardCornerRadius="10dp"
app:cardElevation="0dp"
xmlns:tools="http://schemas.android.com/tools"
app:cardBackgroundColor="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginVertical="30dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="@color/black"
android:textSize="22sp"
tools:text="标题"/>
<TextView
android:id="@+id/tvDialogContent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="center_horizontal"
android:textColor="@color/black"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:textSize="16sp"
tools:text="内容"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="120dp"
android:layout_height="50dp"
android:text="确定"
android:background="@color/bg_card_blue"
android:textColor="@color/white"
android:textSize="20sp"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
+9 -9
View File
@@ -5,25 +5,25 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="10dp" android:paddingStart="20dp"
android:paddingTop="5dp" android:paddingTop="10dp"
android:paddingEnd="10dp" android:paddingEnd="20dp"
android:paddingBottom="5dp" android:paddingBottom="10dp"
android:background="@drawable/shape_dialog_small" android:background="@drawable/shape_dialog"
tools:ignore="MissingDefaultResource"> tools:ignore="MissingDefaultResource">
<ProgressBar <ProgressBar
android:layout_width="26dp" android:layout_width="60dp"
android:layout_height="26dp" android:layout_height="60dp"
android:indeterminateTint="@color/white_f6" /> android:indeterminateTint="@color/white_f6" />
<TextView <TextView
android:id="@+id/tvMessage" android:id="@+id/tvMessage"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:layout_marginStart="10dp"
android:text="加载中……" android:text="加载中……"
android:textColor="@color/white_f6" android:textColor="@color/white_f6"
android:textSize="14sp" android:textSize="26sp"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</LinearLayout> </LinearLayout>
@@ -10,7 +10,7 @@
<ImageView <ImageView
android:id="@+id/ivEmptyIcon" android:id="@+id/ivEmptyIcon"
android:layout_width="120dp" android:layout_width="250dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_empty_gray" android:src="@drawable/ic_empty_gray"
android:adjustViewBounds="true" android:adjustViewBounds="true"
@@ -21,8 +21,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/black999" android:textColor="@color/black999"
android:layout_marginTop="20dp" android:layout_marginTop="50dp"
android:textSize="20sp" android:textSize="50sp"
android:text="暂无数据" android:text="暂无数据"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</LinearLayout> </LinearLayout>
@@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#DDFFFFFF">
<ImageView
android:id="@+id/ivLightHide"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginVertical="10dp"
android:layout_marginEnd="10dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/close"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="继电器1"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightOpen"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="开"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightClose"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="关"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="继电器2"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightOpen2"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="开"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightClose2"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="关"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="继电器3"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightOpen3"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="开"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightClose3"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="关"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
</LinearLayout>
</LinearLayout>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="60dp">
<TextView
android:id="@+id/tvLogName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="30dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="20sp"
tools:text="2025-11-18" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:layout_marginHorizontal="30dp"
app:dividerColor="@color/gray_edit" />
</FrameLayout>
+11 -10
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"--> <?xml version="1.0" encoding="utf-8"?>
<!--<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"-->
<!-- xmlns:tools="http://schemas.android.com/tools"--> <!-- xmlns:tools="http://schemas.android.com/tools"-->
<!-- android:layout_width="match_parent"--> <!-- android:layout_width="match_parent"-->
<!-- android:layout_height="80dp"--> <!-- android:layout_height="80dp"-->
@@ -25,23 +26,23 @@
android:id="@+id/btnFoodName" android:id="@+id/btnFoodName"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="80dp"
android:layout_margin="6dp" android:layout_margin="12dp"
android:ellipsize="end" android:ellipsize="end"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:gravity="center" android:gravity="center"
android:insetTop="0dp" android:insetTop="0dp"
android:insetBottom="0dp" android:insetBottom="0dp"
android:maxLines="1" android:maxLines="1"
app:paddingStart="5dp"
app:paddingEnd="5dp"
android:insetRight="0dp" android:insetRight="0dp"
android:insetLeft="0dp" android:insetLeft="0dp"
android:paddingHorizontal="8dp" android:paddingHorizontal="8dp"
android:textColor="@color/black999" android:textColor="@color/black999"
android:textSize="15sp" android:textSize="30sp"
app:cornerRadius="6dp" app:cornerRadius="12dp"
app:strokeWidth="1dp" app:strokeWidth="2dp"
tools:text="土豆丝土豆丝土豆丝土豆丝土豆丝土豆丝" /> tools:text="土豆丝土豆丝土豆丝土豆丝土豆丝土豆丝"
<!-- android:paddingHorizontal="2dp"--> />
<!-- android:paddingLeft="5dp"-->
<!-- android:paddingRight="5dp"-->
+11 -11
View File
@@ -4,11 +4,11 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layoutCard" android:id="@+id/layoutCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="140dp" android:layout_height="216dp"
android:layout_margin="6dp" android:layout_margin="12dp"
app:cardBackgroundColor="@color/bg_card_blue" app:cardBackgroundColor="@color/bg_card_blue"
app:cardCornerRadius="6dp" app:cardCornerRadius="12dp"
app:cardElevation="2dp" app:cardElevation="0dp"
app:rippleColor="#B0BEC5"> app:rippleColor="#B0BEC5">
<LinearLayout <LinearLayout
@@ -16,14 +16,14 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical"
android:padding="12dp"> android:padding="24dp">
<TextView <TextView
android:id="@+id/tvShelfName" android:id="@+id/tvShelfName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/shelf_name_white" android:textColor="@color/shelf_name_white"
android:textSize="12sp" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="货架-01" /> tools:text="货架-01" />
@@ -39,7 +39,7 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="30sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="空" /> tools:text="空" />
@@ -55,7 +55,7 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/food_weight_blue" android:textColor="@color/food_weight_blue"
android:textSize="14sp" android:textSize="24sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="0千克" /> tools:text="0千克" />
@@ -71,14 +71,14 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/food_weight_blue" android:textColor="@color/food_weight_blue"
android:textSize="12sp" android:textSize="18sp"
tools:text="-" /> tools:text="-" />
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:id="@+id/ivStaleFood" android:id="@+id/ivStaleFood"
android:layout_width="60dp" android:layout_width="100dp"
android:layout_height="60dp" android:layout_height="100dp"
android:src="@drawable/ic_stale_food_512" android:src="@drawable/ic_stale_food_512"
android:layout_gravity="top|end" android:layout_gravity="top|end"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
+20
View File
@@ -19,4 +19,24 @@
<item name="android:windowIsFloating">true</item> <item name="android:windowIsFloating">true</item>
<item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowBackground">@android:color/transparent</item>
</style> </style>
<style name="DialogTheme" parent="@android:style/Theme.Dialog">
<!-- 边框 -->
<item name="android:windowFrame">@null</item>
<!-- 是否浮现在activity之上 -->
<item name="android:windowIsFloating">true</item>
<!-- 半透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- 无标题 -->
<item name="android:windowNoTitle">true</item>
<item name="android:background">@android:color/transparent</item>
<!-- 背景透明 -->
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 模糊 -->
<item name="android:backgroundDimEnabled">true</item>
<!-- 遮罩层 -->
<item name="android:backgroundDimAmount">0.5</item>
</style>
</resources> </resources>
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
#Thu Jul 17 15:32:57 CST 2025 #Thu Jul 17 15:32:57 CST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip #distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+3 -5
View File
@@ -1,10 +1,8 @@
## This file is automatically generated by Android Studio. ## This file must *NOT* be checked into Version Control Systems,
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration. # as it contains information specific to your local configuration.
# #
# Location of the SDK. This is only used by Gradle. # Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the # For customization when using a Version Control System, please read the
# header note. # header note.
sdk.dir=D\:\\Android\\sdk #Tue Jan 13 10:38:32 CST 2026
sdk.dir=C\:\\Users\\HUAWEI\\AppData\\Local\\Android\\Sdk