43 Commits
Author SHA1 Message Date
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
马增飞 3366fc02e5 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	app/src/main/java/com/shuwei/intelligent/shelves/InitActivity.kt
#	app/src/main/java/com/shuwei/intelligent/shelves/net/ApiService.kt
#	app/src/main/java/com/shuwei/intelligent/shelves/net/NetViewModel.kt
2025-11-06 19:32:30 +08:00
马增飞 647ec0dcb7 设备联调优化 2025-11-06 19:29:18 +08:00
lvmeng 61c9452295 开机启动页面修改 2025-09-26 15:58:40 +08:00
lvmeng a0737ebbc3 接口header配置修改 2025-09-18 19:22:20 +08:00
lvmeng a811d296d9 高度修改 2025-09-17 15:18:55 +08:00
lvmeng 9c2c5f073e 初始化功能调试,启动页面优化 2025-09-17 15:16:11 +08:00
lvmeng a355828bab 增加初始化功能 2025-09-11 16:09:07 +08:00
lvmeng e153f3e3b9 增加开机启动 2025-08-22 17:34:56 +08:00
lvmeng ca2041fe76 参数优化 2025-08-18 14:17:58 +08:00
lvmeng f8453c7d76 增加过期逻辑 2025-08-15 17:59:40 +08:00
lvmeng 2162ba0c9e 页面优化 2025-08-11 17:48:34 +08:00
马增飞 5b26e7e711 设备联调优化 2025-08-11 17:15:02 +08:00
lvmeng 0d99c4f22a 调试通过获取设备信息实时更新二级页面重量,首页和二级页面列表页增加空数据展示 2025-08-11 14:22:34 +08:00
马增飞 9e5ede4a8e 设备联调,接口参数优化 2025-08-08 17:48:07 +08:00
lvmeng 0b52dad0fe 串口数据长度优化 2025-08-08 15:08:26 +08:00
lvmeng 383c354f29 接口联调2 2025-08-08 14:54:11 +08:00
lvmeng 65199f4c70 接口联调 2025-08-08 13:34:18 +08:00
lvmeng eb66c2d2ff 优化数据读取异常问题 2025-08-07 19:32:04 +08:00
马增飞 e7e062ebcb 功能联调 2025-08-07 18:58:17 +08:00
lvmeng 4d91b9d338 适配优化 2025-08-07 13:01:26 +08:00
lvmeng c409621e28 功能优化,增加心跳数据处理 2025-08-07 11:07:04 +08:00
97 changed files with 5913 additions and 745 deletions
+36 -12
View File
@@ -4,14 +4,22 @@ plugins {
}
android {
signingConfigs {
getByName("debug") {
storeFile = file("../app/swkey.jks")
storePassword = "123456"
keyPassword = "123456"
keyAlias = "android"
}
}
namespace = "com.shuwei.intelligent.shelves"
compileSdk = 34
defaultConfig {
applicationId = "com.shuwei.intelligent.shelves"
minSdk = 25
//noinspection ExpiredTargetSdkVersion
targetSdk = 25
//noinspection EditedTargetSdkVersion,ExpiredTargetSdkVersion
targetSdk = 30
versionCode = 1
versionName = "1.0"
@@ -41,28 +49,44 @@ android {
ndkVersion = "29.0.13846066"
}
//noinspection UseTomlInstead
dependencies {
//implementation(fileTree("libs") { include("*.jar", "*.aar") })
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.android.core)
implementation(libs.core)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation("androidx.activity:activity-ktx:1.8.0")
implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.1.2")
implementation("com.squareup.okhttp3:okhttp:4.9.1")
implementation("com.google.code.gson:gson:2.10.1")
// implementation("io.github.jeremyliao:live-event-bus-x:1.8.0")
// implementation("com.hoho:android-usb-serial:1.3.0")
implementation("io.github.jeadyx:kmp-serialport:1.0.0")
// implementation("io.github.jeadyx:jserialport:1.5")
// 基础依赖包,必须要依赖
implementation("com.geyifeng.immersionbar:immersionbar:3.2.2")
// kotlin扩展(可选)
implementation("com.geyifeng.immersionbar:immersionbar-ktx:3.2.2")
implementation("androidx.work:work-runtime-ktx:2.8.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.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
implementation("io.github.scwang90:refresh-layout-kernel:3.0.0-alpha")
implementation("io.github.scwang90:refresh-header-classics:3.0.0-alpha")
implementation("io.github.scwang90:refresh-footer-classics:3.0.0-alpha")
implementation("org.greenrobot:eventbus:3.3.1")
// implementation("io.github.jeremyliao:live-event-bus-x:1.8.0")
// implementation("com.hoho:android-usb-serial:1.3.0")
// implementation("io.github.jeadyx:jserialport:1.5")
}
+30 -7
View File
@@ -2,22 +2,31 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:icon="@mipmap/ic_logo512"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_logo512"
android:supportsRtl="true"
android:theme="@style/Theme.IntelligentShelves">
android:theme="@style/Theme.IntelligentShelves"
android:usesCleartextTraffic="true">
<activity
android:name=".HomeActivity"
android:name=".activity.InitActivity"
android:exported="true"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity">
@@ -28,9 +37,23 @@
</intent-filter>
</activity>
<activity
android:name=".ShelfActivity"
android:name=".activity.HomeActivity"
android:screenOrientation="portrait"/>
<activity
android:name=".activity.ShelfActivity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.SettingActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.LogActivity" />
<receiver
android:name=".utils.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
@@ -1,23 +1,50 @@
package com.shuwei.intelligent.shelves
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import com.shuwei.intelligent.shelves.utils.BootReceiver
import com.shuwei.intelligent.shelves.utils.CrashHandler
/**
* @author: star
* @date: 2022-04-26
*/
class App : Application() {
//"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiIiLCJ0eXBlIjoiNiIsInVzZXJJZCI6IjE2NzgyMjY1NTI5MzE1NDkxODYifQ.CQmbELpqT6QzvVCgCeirwvSvPp8Mhft_h6b1Af574Co"
override fun onCreate() {
super.onCreate()
app = this
CrashHandler.init(this)
val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
registerReceiver(BootReceiver(), filter)
}
companion object {
private lateinit var app: App
var canteenId = "0"
var appVersion = "1"
var configUrl = ""
fun get(): App {
//从接口获取
var accessToken: String = ""
var deviceId: String = ""
@Volatile
private var sharedPref: SharedPreferences? = null
fun getInstance(): App {
return app
}
fun getSharedPref(): SharedPreferences? {
if (sharedPref != null) {
return sharedPref
}
sharedPref = app.getSharedPreferences("sp_intelligent_shelves", Context.MODE_PRIVATE)
return sharedPref
}
}
}
@@ -1,229 +0,0 @@
package com.shuwei.intelligent.shelves
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.SparseIntArray
import androidx.lifecycle.lifecycleScope
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.ShelfModel
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.task.TaskManager
import com.shuwei.intelligent.shelves.utils.UIUtils
import com.shuwei.intelligent.shelves.utils.hexToDec
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class HomeActivity : BaseActivity() {
companion object {
private const val TAG = "HomeActivity"
//协议头
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}"
val list: MutableList<ShelfModel> = mutableListOf()
}
private lateinit var binding: ActivityHomeBinding
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
updateLeftStatus("1-1冷藏柜 -2°C / 87%")
initList()
initRecyclerView()
lifecycleScope.launch {
val openState = SerialPortManager.open()
Log.d(TAG, "onCreate: openState=$openState")
if (openState) {
SerialPortManager.startReceive { data ->
runOnUiThread {
// 更新UI显示接收数据
runCatching {
receiveSerialPortData(data)
}.onFailure {
it.printStackTrace()
}
}
}
SerialPortManager.send(ACTIVE_CMD)
SerialPortManager.send(DEVICE_INFO_CMD)
}
}
TaskManager.startTask()
}
private fun receiveSerialPortData(data: String) {
Log.d(TAG, "receiveSerialPortData: $data")
if (data.startsWith(HEADER).not()) {
return
}
val cmd = data.substring(HEADER.length, HEADER.length + 2)
when (cmd) {
"03" -> {
//获取设备状态
val weightStartIndex = HEADER.length + 4 + 10
val temperatureStartIndex = weightStartIndex + 10 * 8
val temperatureHex = data.substring(temperatureStartIndex, 2)
val temperature = hexToDec(temperatureHex)
updateLeftStatus("1-1冷藏柜 $temperature°C / 87%")
var index = 1
var count = weightStartIndex
while (count < temperatureStartIndex) {
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
if (weightHex.first() in '8'..'F') {
//负数
val weightHex2 = "0${weightHex.substring(1)}"
realWeight = hexToDec(weightHex2)
} else {
//整数
realWeight = hexToDec(weightHex)
}
weightArray.put(count, realWeight)
count += 8
index++
}
val intent = Intent(ShelfActivity.RECEIVER_DEVICE_INFO)
intent.putExtra(ShelfActivity.SHELF_WEIGHT, weightArray[clickIndex])
intent.putExtra(ShelfActivity.RECEIVER_DEVICE_INFO, data)
sendBroadcast(intent)
}
else -> {}
}
}
private val weightArray by lazy { SparseIntArray() }
private var clickIndex = 0
override fun onDestroy() {
lifecycleScope.launch {
SerialPortManager.close()
}
TaskManager.cancelTask()
super.onDestroy()
}
@Suppress("DEPRECATION")
private val shelfAdapter by lazy {
// val list = viewModel.getCurrentItems()
ShelfAdapter(list).apply {
setOnDebouncedItemClick { adapter, view, position ->
this@HomeActivity.clickIndex = position
lifecycleScope.launch {
val openCmd = if (list[position].shelfNo in 1..5)
LEFT_SHELF_OPEN_CMD
else
RIGHT_SHELF_OPEN_CMD
val openState = SerialPortManager.send(openCmd)
if (openState.not()) {
UIUtils.toast("${list[position].foodName}柜门开启失败")
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 {
foodName = model.foodName
foodWeight = model.foodWeight
storeDate = model.storeDate
}
notifyItemChanged(position)
}
}
}
}
}
}
private fun initRecyclerView() {
binding.rvShelf.run {
layoutManager =
GridLayoutManager(this@HomeActivity, 2, GridLayoutManager.VERTICAL, false)
adapter = shelfAdapter
}
}
private fun initList() {
// val list: MutableList<ShelfModel> = mutableListOf()
list.add(ShelfModel(shelfNo = 1))
list.add(ShelfModel(shelfNo = 6))
list.add(ShelfModel(shelfNo = 2))
list.add(ShelfModel(shelfNo = 7))
list.add(ShelfModel(shelfNo = 3))
list.add(ShelfModel(shelfNo = 8))
list.add(ShelfModel(shelfNo = 4))
list.add(ShelfModel(shelfNo = 9))
list.add(ShelfModel(shelfNo = 5))
list.add(ShelfModel(shelfNo = 10))
// viewModel.updateItems(list)
}
private val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss"
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)
}
}
@@ -1,212 +0,0 @@
package com.shuwei.intelligent.shelves
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.shuwei.intelligent.shelves.HomeActivity.Companion.ACTIVE_CMD
import com.shuwei.intelligent.shelves.HomeActivity.Companion.DEVICE_INFO_CMD
import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.adapter.SearchAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding
import com.shuwei.intelligent.shelves.model.SearchModel
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.KeyboardUtil
import com.shuwei.intelligent.shelves.utils.decToHex
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.random.Random
@SuppressLint("NotifyDataSetChanged")
class ShelfActivity : BaseActivity() {
companion object {
private const val TAG = "ShelfActivity"
const val SHELF_MODEL = "shelfModel"
const val RECEIVER_DEVICE_INFO = "receiverDeviceInfo"
const val SHELF_WEIGHT = "shelfWeight"
}
private lateinit var binding: ActivityShelfBinding
private var shelfModel: ShelfModel? = null
private var clickIndex: Int = -1
private var realWeight: Int = 0
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShelfBinding.inflate(layoutInflater)
setContentView(binding.root)
shelfModel = intent.getSerializableExtra(SHELF_MODEL) as ShelfModel?
shelfModel?.let {
binding.tvShelfName.text = it.shelfName
binding.tvFoodName.text = if (it.foodName.isNullOrBlank()) "-" else it.foodName
}
initRecyclerView()
// initWeight()
binding.etInputFood.run {
addOnActionSearchListener {
if (text.isNullOrBlank()) {
toast(hint.toString())
return@addOnActionSearchListener
}
loadSearchData()
KeyboardUtil.hideKeyboard(context, this)
}
}
binding.btnConfirm.clickWithDebounce {
if (clickIndex < 0) {
toast("请选择食材")
return@clickWithDebounce
}
val data = ShelfModel(
shelfName = shelfModel?.shelfName,
foodName = list[clickIndex].foodName,
foodWeight = realWeight,
storeDate = DateTimeUtil.formatDateTime(dateTime = Date())
)
val intent = Intent().apply {
putExtra(SHELF_MODEL, data)
}
setResult(RESULT_OK, intent)
finish()
}
binding.tvFoodWeight.text = "${realWeight}"
binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v.context, v)
}
binding.btnClearZero.setOnClickListener { v ->
lifecycleScope.launch {
val shelfNo = shelfModel?.shelfNo ?: 0
val zeroClearingCmd =
"${HomeActivity.HEADER}0401${decToHex(shelfNo)}${HomeActivity.FOOTER}"
SerialPortManager.send(zeroClearingCmd)
}
KeyboardUtil.hideKeyboard(v.context, v)
}
lifecycleScope.launch {
SerialPortManager.send(DEVICE_INFO_CMD)
}
registerReceiver(receiver, IntentFilter(RECEIVER_DEVICE_INFO))
}
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val data = it.getStringExtra(RECEIVER_DEVICE_INFO)
Log.d(TAG, "onReceive: $data")
realWeight = it.getIntExtra(SHELF_WEIGHT, 0)
binding.tvFoodWeight.text = "${realWeight}"
}
}
}
override fun onDestroy() {
unregisterReceiver(receiver)
super.onDestroy()
}
// private fun initWeight() {
// realWeight = Random.nextInt(1, 5001)
// }
private fun loadSearchData() {
list.add(SearchModel(foodId = 1000, foodName = "菲律宾乳猪五花肉"))
list.add(SearchModel(foodId = 1001, foodName = "金针菇"))
list.add(SearchModel(foodId = 1002, foodName = "大菠菜"))
list.add(SearchModel(foodId = 1003, foodName = "清远麻鸡块"))
list.add(SearchModel(foodId = 1004, foodName = "普罗旺斯番茄"))
list.add(SearchModel(foodId = 1005, foodName = "长条茄子"))
list.add(SearchModel(foodId = 1006, foodName = "得莫利鱼"))
list.add(SearchModel(foodId = 1007, foodName = "土豆丝"))
list.add(SearchModel(foodId = 1008, foodName = "辣椒炒肉"))
list.add(SearchModel(foodId = 1009, foodName = "雪菜烧豆腐"))
list.add(SearchModel(foodId = 1010, foodName = "冬虫夏草炝拌芥兰苗"))
list.add(
SearchModel(
foodId = 1011,
foodName = "蘑菇玉米山药莲藕胡萝卜红枣莲子枸杞清炖排骨汤"
)
)
searchAdapter.notifyDataSetChanged()
}
private val list: MutableList<SearchModel> = mutableListOf()
private val searchAdapter by lazy {
SearchAdapter(list).apply {
setOnDebouncedItemClick { adapter, v, position ->
this@ShelfActivity.clickIndex = position
list.forEach { it.isClicked = false }
list[position].isClicked = true
notifyDataSetChanged()
binding.tvFoodName.text = list[position].foodName
KeyboardUtil.hideKeyboard(v.context, v)
}
}
}
private fun initRecyclerView() {
binding.rvSearch.run {
layoutManager = GridLayoutManager(this@ShelfActivity, 2)
adapter = searchAdapter
}
}
private val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
private fun updateDateTime() {
val sdf = SimpleDateFormat(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)
}
}
@@ -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)
}
}
@@ -0,0 +1,164 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityInitBinding
import com.shuwei.intelligent.shelves.model.DeviceConfigInfo
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.net.UrlConfig
import com.shuwei.intelligent.shelves.utils.AppUtil
import com.shuwei.intelligent.shelves.utils.QRCodeUtil
import com.shuwei.intelligent.shelves.utils.SpTool
import com.shuwei.intelligent.shelves.utils.ext.dp
import com.shuwei.intelligent.shelves.utils.ext.invisible
import com.shuwei.intelligent.shelves.utils.ext.startActivity
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toObject
import com.shuwei.intelligent.shelves.utils.ext.toast
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlin.collections.forEach
import kotlin.getValue
import kotlin.ranges.downTo
import kotlin.text.isBlank
class InitActivity : BaseActivity() {
companion object {
const val TAG = "InitActivity"
}
private lateinit var binding: ActivityInitBinding
private val viewModel: NetViewModel by viewModels()
@SuppressLint("HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityInitBinding.inflate(layoutInflater)
setContentView(binding.root)
App.deviceId = AppUtil.getUDID( this)
// 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.canteenId = "0"
// var deviceId = AppUtil.getUDID(this)
// Log.d(TAG, "onCreate: deviceId=$deviceId")
//// deviceId = "39a7abdd06b3c7ab"
//// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
// App.deviceId = deviceId
//
// SpTool.put(SpTool.DEVICE_ID, deviceId)
// val deviceConfigCache = SpTool.getString(SpTool.DEVICE_CONFIG_CACHE)
// val checkResult = checkConfigData(deviceConfigCache)
// if (checkResult.not()) {
// binding.ivQrCode.visible()
// binding.btnInit.visible()
// //进行初始化操作
// initConfig()
// return
// }
binding.ivQrCode.invisible()
binding.btnInit.invisible()
countDown()
}
private fun countDown() {
lifecycleScope.launch {
flow {
(2 downTo 1).forEach {
delay(1000)
emit(it)
}
}.collect {
// 倒计时结束执行跳转
if (it == 1) {
startActivity<HomeActivity>()
finish()
}
}
}
}
private fun initConfig() {
binding.ivQrCode.setImageBitmap(
QRCodeUtil.generateQRCode(
content = App.deviceId,
size = 200.dp
)
)
binding.btnInit.setOnClickListener {
viewModel.getDeviceToken(App.deviceId)
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.getDeviceTokenUiState.collect { state ->
when (state) {
is UiState.Loading -> {}
is UiState.Success<*> -> {
state.data.data?.let { deviceToken ->
Log.d(TAG, "initConfig: $deviceToken")
viewModel.getDeviceConfig(
deviceId = App.deviceId,
deviceToken = deviceToken.toString()
)
}
}
is UiState.Error -> toast(state.msg)
else -> {}
}
}
}
launch {
viewModel.getDeviceConfigUiState.collect { state ->
when (state) {
is UiState.Loading -> {}
is UiState.Success<*> -> {
state.data.data?.let {
if (it is DeviceConfigInfo) {
SpTool.put(SpTool.DEVICE_CONFIG_CACHE, it.toJsonString())
App.configUrl = it.appPackageUrl?:""
App.canteenId = it.canteenId?:""
startActivity<HomeActivity>()
finish()
}
}
}
is UiState.Error -> toast(message = state.msg)
else -> {}
}
}
}
}
}
}
private fun checkConfigData(data: String): Boolean {
if (data.isBlank()) {
return false
}
val config = data.toObject<DeviceConfigInfo?>()
if (config == null) {
return false
}
App.configUrl = config.appPackageUrl?:""
App.canteenId = config.canteenId?:""
return true
}
}
@@ -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)
}
}
@@ -0,0 +1,392 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.LinearLayout
import androidx.activity.viewModels
import androidx.core.view.updateLayoutParams
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.scwang.smart.refresh.layout.constant.RefreshState
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.DEVICE_INFO_CMD
import com.shuwei.intelligent.shelves.adapter.SearchAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding
import com.shuwei.intelligent.shelves.model.ClearShelfEvent
import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.KeyboardUtil
import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
import com.shuwei.intelligent.shelves.utils.ext.dp
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@SuppressLint("NotifyDataSetChanged")
class ShelfActivity : BaseActivity() {
companion object {
private const val TAG = "ShelfActivity"
const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
const val SHELF_MODEL = "shelfModel"
const val RECEIVER_DEVICE_INFO = "receiverDeviceInfo"
const val SHELF_WEIGHT = "shelfWeight"
const val SHELF_INDEX = "shelfIndex"
const val PAGE_SIZE = 100
}
private lateinit var binding: ActivityShelfBinding
private var shelfModel: ShelfModel? = null
private var clickIndex: Int = -1
private var realWeight: Int = 0
private var pageNo: Int = 1
private val viewModel: NetViewModel by viewModels()
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShelfBinding.inflate(layoutInflater)
setBackground()
setContentView(binding.root)
EventBus.getDefault().register(this)
// tvFoodWeight =binding.tvFoodWeight
shelfModel = intent.getSerializableExtra(SHELF_MODEL) as ShelfModel?
shelfModel?.let {
binding.tvShelfName.text = it.shelfName
binding.tvFoodName.text = if (it.goodsName.isNullOrBlank()) "-" else it.goodsName
realWeight = it.weight?.toInt()?:0
binding.tvFoodWeight.text = if(realWeight < 1000) "${realWeight}" else "%.3f千克".format(realWeight/1000.0)
startTime = System.currentTimeMillis()
}
initRecyclerView()
binding.etInputFood.run {
addOnActionSearchListener {
if (text.isNullOrBlank()) {
toast(hint.toString())
return@addOnActionSearchListener
}
pageNo = 1
getGoodsList()
KeyboardUtil.hideKeyboard(context, this)
}
addTextChangedListener(afterTextChanged = { editable ->
if (editable.isNullOrBlank()) {
pageNo = 1
getGoodsList()
}
})
}
binding.btnConfirm.clickWithDebounce {
if (clickIndex < 0) {
toast("请选择食材")
return@clickWithDebounce
}
val currentGoodsId = list[clickIndex].goodsId ?: ""
val data = ShelfModel(
shelfName = shelfModel?.shelfName,
goodsId = currentGoodsId,
goodsName = list[clickIndex].goodsName,
weight = realWeight.toDouble(),
overdueDay = list[clickIndex].overdueDay,
// createTime = if (shelfModel?.goodsId == currentGoodsId) null else DateTimeUtil.formatDateTime(dateTime = Date())
putCabinetDay = DateTimeUtil.formatDateTime(dateTime = Date())
)
val intent = Intent().apply {
putExtra(SHELF_MODEL, data)
}
setResult(RESULT_OK, intent)
finish()
}
binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v)
}
binding.btnClearZero.setOnClickListener {
clearZero()
}
binding.btnClearEmpty.setOnClickListener {
val tempWeight = shelfModel?.weight?.toInt()?:0
clearZero()
binding.tvFoodName.text = "-"
//binding.tvFoodWeight.text = "0克"
EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo, tempWeight))
}
sendCmd(DEVICE_INFO_CMD)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.getGoodsListUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateUI(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
}
// registerReceiver(receiver, IntentFilter(RECEIVER_DEVICE_INFO + shelfModel?.deviceNo))
binding.ivBack?.setOnClickListener { finish() }
binding.refreshLayout.let {
it.setEnableRefresh(true)
it.setEnableLoadMore(false)
it.setOnRefreshListener {
pageNo = 1
getGoodsList()
}
it.setOnLoadMoreListener { getGoodsList() }
}
getGoodsList()
// TaskManager.startDeviceTask()
}
private fun finishRefresh() {
binding.refreshLayout.let {
if (it.state == RefreshState.Refreshing) {
it.finishRefresh(500)
} else if (binding.refreshLayout.state == RefreshState.Loading) {
it.finishLoadMore(500)
} else {
}
}
}
private fun getGoodsList() {
viewModel.getGoodsList(
canteenId = App.canteenId,
goodsName = binding.etInputFood.text.trim().toString(),
pageNo = pageNo,
pageSize = PAGE_SIZE
)
}
private fun showProgress() {
Loading.show(this)
}
@SuppressLint("NotifyDataSetChanged")
private fun updateUI(data: RespData<*>) {
binding.include?.root?.gone()
finishRefresh()
binding.root.postDelayed({ Loading.dismiss() }, 200)
Log.d(TAG, "updateUI: ${data.toJsonString()}")
val goodsList = data.data as MutableList<GoodsModel>
if (data.data.isNullOrEmpty()) {
if (pageNo == 1) {
loadEmptyView()
}
return
}
if(pageNo == 1){
list.clear()
}
// var firstOne:GoodsModel?=null
// if (pageNo == 1) {
// if (shelfModel?.goodsId.isNullOrBlank().not()) {
// firstOne = list.firstOrNull { it.goodsId == shelfModel?.goodsId }
// if (firstOne == null) {
// firstOne = GoodsModel(
// goodsId = shelfModel?.goodsId,
// goodsName = shelfModel?.goodsName,
// isClicked = true
// )
// }
// }
// list.clear()
// }
// val records = data.data.records
// if (pageNo == 1) {
// firstOne?.let { one ->
// val filterResult = records.firstOrNull{one.goodsId == it.goodsId }
// if (filterResult != null) {
// records.remove(filterResult)
// }
// clickIndex = 0
// list.add(one)
// }
// }
list.addAll(goodsList)
val enableLoadMore = goodsList.size >= PAGE_SIZE
binding.refreshLayout.setEnableLoadMore(enableLoadMore)
if (enableLoadMore) {
pageNo++
}
searchAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
finishRefresh()
Loading.dismiss()
toast(message)
if (pageNo == 1) {
loadEmptyView()
}
}
// private val receiver = object : BroadcastReceiver() {
// override fun onReceive(context: Context?, intent: Intent?) {
// intent?.let {
// if (it.action == RECEIVER_DEVICE_INFO + shelfModel?.deviceNo) {
// val weight = it.getDoubleExtra(SHELF_WEIGHT, 0.0)
// Log.d(TAG, "getDeviceInfo:onReceive: weight=$weight")
// realWeight = (weight * 1000).toInt()
// binding.tvFoodWeight.text = "${realWeight}克"
// }
// }
// }
// }
override fun onDestroy() {
// unregisterReceiver(receiver)
EventBus.getDefault().unregister(this)
// TaskManager.cancelDeviceTask()
super.onDestroy()
}
private val list: MutableList<GoodsModel> = mutableListOf()
private val searchAdapter by lazy {
SearchAdapter(list).apply {
setOnDebouncedItemClick { adapter, v, position ->
this@ShelfActivity.clickIndex = position
list.forEach { it.isClicked = false }
list[position].isClicked = true
notifyDataSetChanged()
binding.tvFoodName.text = list[position].goodsName
KeyboardUtil.hideKeyboard(v.context, v)
}
}
}
private fun initRecyclerView() {
binding.rvSearch.run {
layoutManager = GridLayoutManager(this@ShelfActivity, 2)
adapter = searchAdapter
}
}
private fun updateDateTime() {
val sdf = SimpleDateFormat(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)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
searchAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
pageNo = 1
getGoodsList()
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_gray)
it.ivEmptyIcon.updateLayoutParams<LinearLayout.LayoutParams> {
val swDpValue = resources?.configuration?.smallestScreenWidthDp ?: 0
width = if (swDpValue > 600) 200.dp else 120.dp
}
}
binding.refreshLayout.run {
setEnableRefresh(false)
setEnableLoadMore(false)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun receiveWeightEvent(event: SendWeightEvent) {
if (event.shelfNo == (shelfModel?.deviceNo ?: 0)) {
val intervalTime = System.currentTimeMillis() - startTime
val weight = event.weight
log("编号${event.shelfNo}货架获取重量成功:${weight}克,数据获取间隔时间为:${intervalTime}")
binding.tvFoodWeight.text = if(weight < 1000) "${weight}" else "%.3f千克".format(weight/1000.0)
startTime = System.currentTimeMillis()
window.decorView.postDelayed({ Loading.dismiss() }, 1000)
}
}
private fun clearZero() {
lifecycleScope.launch {
val shelfNo = shelfModel?.deviceNo ?: 0
val hex = shelfNo.toString(16).padStart(2, '0').uppercase()
Log.d(TAG, "onCreate: hex=$hex")
val zeroClearingCmd = "${HomeActivity.HEADER}0401${hex}${HomeActivity.FOOTER}"
log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}")
sendCmd(zeroClearingCmd)
sendCmd(
if (shelfNo in 1..5) HomeActivity.LEFT_SHELF_OPEN_CMD else HomeActivity.RIGHT_SHELF_OPEN_CMD
)
//清零
realWeight = 0
// binding.tvFoodWeight.text = "${realWeight}克"
sendCmd(DEVICE_INFO_CMD)
startTime = System.currentTimeMillis()
Loading.show(this@ShelfActivity)
// window.decorView.postDelayed({ Loading.dismiss() }, 5000)
}
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
}
}
@@ -11,10 +11,10 @@ import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ListItemSearchBinding
import com.shuwei.intelligent.shelves.model.SearchModel
import com.shuwei.intelligent.shelves.model.GoodsModel
class SearchAdapter(list: MutableList<SearchModel>) :
BaseQuickAdapter<SearchModel, SearchAdapter.VH>(list) {
class SearchAdapter(list: MutableList<GoodsModel>) :
BaseQuickAdapter<GoodsModel, SearchAdapter.VH>(list) {
inner class VH(var binding: ListItemSearchBinding) : QuickViewHolder(binding.root)
@@ -24,10 +24,10 @@ class SearchAdapter(list: MutableList<SearchModel>) :
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: SearchModel?) {
override fun onBindViewHolder(holder: VH, position: Int, item: GoodsModel?) {
val binding = holder.binding
binding.btnFoodName.run {
text = item!!.foodName
text = " ${item!!.goodsName}"
setTextColor(
getColor(if (item.isClicked) R.color.shelf_name_blue else R.color.black999)
)
@@ -10,6 +10,11 @@ import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.ListItemShelfBinding
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.visible
import java.util.Date
import kotlin.math.abs
class ShelfAdapter(list: MutableList<ShelfModel>) :
BaseQuickAdapter<ShelfModel, ShelfAdapter.VH>(list) {
@@ -22,12 +27,51 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
return VH(binding)
}
private fun stringToDate(createTime: String?): Date? {
if (createTime.isNullOrBlank()) {
return null
}
return DateTimeUtil.stringToDate(createTime)
}
override fun onBindViewHolder(holder: VH, position: Int, item: ShelfModel?) {
val shelfNo = item!!.shelfNo
item.shelfName = "货架-${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo" }"
val weight = item.foodWeight ?: 0
val shelfNo = item!!.deviceNo
//货架-
item.shelfName = "${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo"}"
val weight = item.weight ?: 0.0
val binding = holder.binding
val isBlankShelf = weight <= 0
if (item.overdueDay.isNullOrBlank()) {
binding.ivStaleFood.gone()
} else {
runCatching {
// val shelfDate = stringToDate(item.putCabinetDay)
binding.ivStaleFood.run {
if (item.overdue == true) visible() else gone()
}
// if (shelfDate == null) {
// binding.ivStaleFood.gone()
// } else {
// val days = item.overdueDay!!.toInt()
// val resultDate = DateTimeUtil.addDays(shelfDate, days)
// if (resultDate.before(Date())) {
// binding.ivStaleFood.visible()
// } else {
// binding.ivStaleFood.gone()
// }
// }
}.onFailure {
binding.ivStaleFood.gone()
}
}
//?.setImageResource()
binding.tvFoodWeight.run {
setTextColor(getColor(R.color.food_weight_orange))
//val showWeight = if (weight == 0.toDouble()) "0克" else "%.3f千克".format(weight)
text = if(abs(weight) < 1000) "${weight.toInt()}" else "%.3f千克".format(weight/1000.0)
//"${(weight*1000).roundToInt()}克"
}
//val isBlankShelf = weight <= 0
val isBlankShelf = item.goodsName.isNullOrBlank()
if (isBlankShelf) {
binding.layoutCard.setCardBackgroundColor(getColor(R.color.bg_card_blue))
binding.tvShelfName.run {
@@ -38,10 +82,10 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
setTextColor(getColor(R.color.white))
text = ""
}
binding.tvFoodWeight.run {
setTextColor(getColor(R.color.food_weight_blue))
text = "0千克"
}
// binding.tvFoodWeight.run {
// setTextColor(getColor(R.color.food_weight_blue))
// text = "0千克"
// }
binding.tvStoreDate.run {
setTextColor(getColor(R.color.food_weight_blue))
text = "-"
@@ -55,15 +99,16 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
}
binding.tvFoodName.run {
setTextColor(getColor(R.color.food_name_black))
text = item.foodName
}
binding.tvFoodWeight.run {
setTextColor(getColor(R.color.food_weight_orange))
text = "${weight/1000F}千克"
text = item.goodsName
}
// binding.tvFoodWeight.run {
// setTextColor(getColor(R.color.food_weight_orange))
//// text = "${weight/1000F}千克"
// text = "${weight}克"
// }
binding.tvStoreDate.run {
setTextColor(getColor(R.color.black999))
text = item.storeDate
text = item.putCabinetDay
}
}
@@ -2,18 +2,33 @@ package com.shuwei.intelligent.shelves.base
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.gyf.immersionbar.BarHide
import com.gyf.immersionbar.ImmersionBar
import com.shuwei.intelligent.shelves.activity.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.activity.LogActivity
import com.shuwei.intelligent.shelves.activity.SettingActivity
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 com.shuwei.intelligent.shelves.utils.ext.startActivity
import kotlinx.coroutines.launch
@Suppress("DEPRECATION")
open class BaseActivity : AppCompatActivity() {
val TAG = this.javaClass.simpleName
private lateinit var binding: ActivityBaseBinding
private var launcher: ActivityResultLauncher<Intent>? = null
@@ -33,7 +48,7 @@ open class BaseActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
binding = ActivityBaseBinding.inflate(layoutInflater)
setContentView(binding.root)
statusBarDarkFont(enable = false)
statusBarDarkFont()
launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
@@ -41,28 +56,40 @@ open class BaseActivity : AppCompatActivity() {
}
}
// window.setDecorFitsSystemWindows(false) // 启用 Edge-to-Edge
// window.insetsController?.apply {
// hide(WindowInsets.Type.statusBars()) // 隐藏状态栏
// systemBarsBehavior =
// WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // 滑动时临时显示
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// window.setDecorFitsSystemWindows(false)
// window.insetsController?.apply {
// hide(WindowInsets.Type.statusBars()) // 隐藏状态栏
// systemBarsBehavior =
// WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // 滑动时临时显示
// }
// }
lightSwitchClick()
}
fun hideStatusBar() {
enableEdgeToEdge()
// enableEdgeToEdge()
// val uiOptions = (View.SYSTEM_UI_FLAG_FULLSCREEN // 隐藏状态栏
// 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)
.hideBar(BarHide.FLAG_HIDE_STATUS_BAR)
.init()
}
fun statusBarDarkFont(enable: Boolean) {
fun statusBarDarkFont() {
//window.decorView.systemUiVisibility = if (enable) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0
val color = ContextCompat.getColor(this, R.color.white)
binding.tvLeftStatus.setTextColor(color)
binding.tvRightStatus.setTextColor(color)
}
fun setBackground() {
binding.root.setBackgroundColor(ContextCompat.getColor(this, R.color.bg_page))
}
override fun setContentView(view: View?) {
if (view == binding.root) {
super.setContentView(view)
@@ -79,5 +106,79 @@ open class BaseActivity : AppCompatActivity() {
binding.tvRightStatus.text = data
}
// 5分钟
private val TIME_OUT: Long = (5 * 60 * 1000).toLong()
private val mHandler by lazy { Handler(Looper.getMainLooper()) }
private val mTimeoutRunnable = Runnable {
if (this is HomeActivity) {
this.switchLight(false)
}
}
override fun onUserInteraction() {
super.onUserInteraction()
//开灯
if (System.currentTimeMillis() - lastTouchTime >= TIME_OUT) {
this.switchLight(true)
}
// 重置定时器
mHandler.removeCallbacks(mTimeoutRunnable)
mHandler.postDelayed(mTimeoutRunnable, TIME_OUT)
lastTouchTime = System.currentTimeMillis()
}
private var lastTouchTime = 0L
override fun onResume() {
super.onResume()
// 启动定时器
mHandler.postDelayed(mTimeoutRunnable, TIME_OUT)
}
override fun onPause() {
super.onPause()
// 取消定时器
mHandler.removeCallbacks(mTimeoutRunnable)
}
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)
}
}
@@ -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
}
}
@@ -0,0 +1,6 @@
package com.shuwei.intelligent.shelves.model
class DeviceConfigInfo {
var appPackageUrl:String? = null
var canteenId:String? = null
}
@@ -0,0 +1,10 @@
package com.shuwei.intelligent.shelves.model
import java.io.Serializable
data class GoodsModel(
var goodsId: String? = null,
var goodsName: String? = null,
var overdueDay: String? = null,
var isClicked: Boolean = false
) : Serializable
@@ -0,0 +1,8 @@
package com.shuwei.intelligent.shelves.model
data class GoodsRecord(
val total: Int = 0,
val size: Int = 0,
val page: Int = 0,
val records: MutableList<GoodsModel>? = null
)
@@ -1,9 +0,0 @@
package com.shuwei.intelligent.shelves.model
import java.io.Serializable
data class SearchModel(
var foodId: Int? = null,
var foodName: String? = null,
var isClicked: Boolean = false
) : Serializable
@@ -0,0 +1,10 @@
package com.shuwei.intelligent.shelves.model
data class SendWeightEvent(
var shelfNo: Int = 0,
var weight:Int=0
)
data class WeightEvent(var weight: Int)
data class ClearShelfEvent(var shelfNo: Int, var weight: Int)
@@ -0,0 +1,11 @@
package com.shuwei.intelligent.shelves.model
class ShelfBody {
var deviceId: String = ""
var canteenId: String = ""
var temperature: String? = null
var humidity: String = "0"
var goodsList: List<ShelfModel>? = null
}
@@ -3,9 +3,19 @@ package com.shuwei.intelligent.shelves.model
import java.io.Serializable
data class ShelfModel(
var shelfNo: Int = 0,
var id: String? = null,
var deviceNo: Int = 0,
var shelfName: String? = null,
var foodName: String? = null,
var foodWeight: Int? = null,
var storeDate: String? = null
var goodsId: String? = "",
var goodsName: String? = null,
var deviceId: String? = null,
var weight: Double = 0.0,
var weightBak: Double = 0.0,
var createTime: String? = null,
var putCabinetDay: String? = null,
var updateTime: String? = null,
var overdueDay: String? = null,
var temperature: String? = null,
var humidity: String = "0",
var overdue:Boolean ?= false
) : Serializable
@@ -0,0 +1,8 @@
package com.shuwei.intelligent.shelves.model
data class ShelfResult(
val placeId: String = "",
val canteenName: String = "",
val deviceName: String = "",
val containerGoodsList: List<ShelfModel>? = null
)
@@ -0,0 +1,61 @@
package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.DeviceConfigInfo
import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfResult
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Query
import retrofit2.http.Url
interface ApiService {
/**
* device获取token
*/
@GET("/sys/getEquipmentToken")
suspend fun getDeviceToken(
@Query("qrcodeId") qrcodeId: String,
@Query("appVersion") appVersion: String = App.appVersion
): RespData<String>
/**
*获取配置信息
*/
@GET("/equipment/stEquipment/queryByEquipmentCode")
suspend fun getDeviceInfo(
@Query("equipmentCode") equipmentCode: String,
@Query("appVersion") appVersion: String = App.appVersion
): RespData<DeviceConfigInfo>
@GET
suspend fun getAccessToken(
@Url url: String = UrlConfig.GET_ACCESS_TOKEN,
@Query("qrcodeId") qrcodeId: String
): RespData<String?>
@GET
suspend fun getShelfList(
@Url url: String = UrlConfig.GET_SHELF_LIST,
@Query("deviceId") deviceId: String
): RespData<ShelfResult>
@GET
suspend fun getGoodsList(
@Url url: String = UrlConfig.GET_GOODS_LIST,
@Query("canteenId") canteenId: String = "0",
@Query("goodsName") goodsName: String? = null,
@Query("pageNo") pageNo: Int = 1,
@Query("pageSize") pageSize: Int = 50
): RespData<MutableList<GoodsModel>?>
@POST
suspend fun saveShelfGoodsList(
@Url url: String = UrlConfig.SAVE_SHELF_GOODS_LIST,
@Body body: ShelfBody
): RespData<Any?>
}
@@ -0,0 +1,99 @@
package com.shuwei.intelligent.shelves.net
import android.annotation.SuppressLint
import android.util.Log
import com.shuwei.intelligent.shelves.App
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.net.ssl.*
import java.security.SecureRandom
import java.security.cert.X509Certificate
import kotlin.apply
val apiService: ApiService = Retrofit.Builder()
// .baseUrl(UrlConfig.BASE_URL)
.baseUrl(UrlConfig.DEVICE_BASE_URL)
.client(HttpManager.instance.client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
class HttpManager private constructor() {
val client: OkHttpClient by lazy {
OkHttpClient.Builder()
.apply {
connectTimeout(15, TimeUnit.SECONDS)
readTimeout(30, TimeUnit.SECONDS)
writeTimeout(15, TimeUnit.SECONDS)
sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
hostnameVerifier { _, _ -> true }
// 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 ->
// val request = chain.request().newBuilder()
// .header("x-access-token", App.accessToken)
// .build()
// chain.proceed(request)
// }
addInterceptor(RequestInterceptor())
}
.build()
}
companion object {
val instance by lazy { HttpManager() }
}
// fun newCall(request: Request): Call = client.newCall(request)
// inner class LoggingInterceptor : Interceptor {
// override fun intercept(chain: Interceptor.Chain): Response {
// val request = chain.request()
// // 打印请求日志
// Log.d("HttpManager", "--> ${request.method},${request.url},${request.body}")
//
// val response = chain.proceed(request)
// // 打印响应日志
// Log.d("HttpManager", "<-- ${response.body}")
// return response
// }
// }
// 信任所有证书的TrustManager实现
@SuppressLint("CustomX509TrustManager")
class TrustAllCerts : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
// 创建信任所有证书的SSLSocketFactory
fun createSSLSocketFactory(): SSLSocketFactory {
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, arrayOf(TrustAllCerts()), SecureRandom())
}
return sslContext.socketFactory
}
}
@@ -0,0 +1,74 @@
package com.shuwei.intelligent.shelves.net
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.WindowManager
import androidx.core.graphics.drawable.toDrawable
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.databinding.DialogLoadingBinding
import kotlin.let
import kotlin.run
object Loading {
private var dialog: LoadingDialog? = null
fun show(activity: Activity) {
try {
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()
}
}
fun dismiss() {
try {
dialog?.let {
if (it.isShowing) {
it.dismiss()
}
}
dialog = null
} catch (e: Exception) {
e.printStackTrace()
}
}
}
class LoadingDialog(
context: Context,
message: String = "加载中……",
private var onDismiss: () -> Unit = {}
) : Dialog(context, R.style.LoadingDialog) {
init {
val binding = DialogLoadingBinding.inflate(LayoutInflater.from(context))
binding.tvMessage.text = message
setContentView(binding.root)
setCancelable(true)
setCanceledOnTouchOutside(true)
window?.run {
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
// setFlags(
// WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
// WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
// )
}
setOnDismissListener {
onDismiss()
}
}
}
@@ -0,0 +1,184 @@
package com.shuwei.intelligent.shelves.net
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfModel
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.StateFlow
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class NetViewModel : ViewModel() {
// val apiService: ApiService = HttpManager.instance.apiService
// val apiService: ApiService = Retrofit.Builder()
// .baseUrl(UrlConfig.BASE_URL)
// .client(HttpManager.instance.client)
// .addConverterFactory(GsonConverterFactory.create())
// .build()
// .create(ApiService::class.java)
private val _getShelfListUiState = MutableStateFlow<UiState>(UiState.Initial)
val getShelfListUiState: StateFlow<UiState> = _getShelfListUiState
// private val _saveShelfGoodsListUiState = MutableStateFlow<UiState>(UiState.Initial)
// val saveShelfGoodsListUiState: StateFlow<UiState> = _saveShelfGoodsListUiState
private val _getGoodsListUiState = MutableStateFlow<UiState>(UiState.Initial)
val getGoodsListUiState: StateFlow<UiState> = _getGoodsListUiState
private val _getAccessTokenUiState = MutableStateFlow<UiState>(UiState.Initial)
val getAccessTokenUiState: StateFlow<UiState> = _getAccessTokenUiState
private val _getDeviceTokenUiState = MutableStateFlow<UiState>(UiState.Initial)
val getDeviceTokenUiState: StateFlow<UiState> = _getDeviceTokenUiState
private val _getDeviceConfigUiState = MutableStateFlow<UiState>(UiState.Initial)
val getDeviceConfigUiState: StateFlow<UiState> = _getDeviceConfigUiState
fun getDeviceToken(deviceId: String) {
viewModelScope.launch {
_getDeviceTokenUiState.value = UiState.Loading
runCatching {
val response = apiService.getDeviceToken(qrcodeId = deviceId)
if (response.isSuccess()) {
_getDeviceTokenUiState.value = UiState.Success(response)
} else {
_getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getDeviceTokenUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun getDeviceConfig(deviceId: String, deviceToken: String) {
viewModelScope.launch {
_getDeviceTokenUiState.value = UiState.Loading
runCatching {
val response =
apiService.getDeviceInfo(equipmentCode = deviceId)
if (response.isSuccess()) {
_getDeviceTokenUiState.value = UiState.Success(response)
} else {
_getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getDeviceTokenUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun getAccessToken(deviceId: String) {
viewModelScope.launch {
_getAccessTokenUiState.value = UiState.Loading
runCatching {
val response = apiService.getAccessToken(qrcodeId = deviceId)
if (response.isSuccess()) {
_getAccessTokenUiState.value = UiState.Success(response)
} else {
_getAccessTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getAccessTokenUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun getShelfList(deviceId: String) {
viewModelScope.launch {
_getShelfListUiState.value = UiState.Loading
runCatching {
val response = apiService.getShelfList(deviceId = deviceId)
if (response.isSuccess()) {
_getShelfListUiState.value = UiState.Success(response)
} else {
_getShelfListUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getShelfListUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun updateOverdueState(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) {
// viewModelScope.launch {
// _saveShelfGoodsListUiState.value = UiState.Loading
// runCatching {
// val response = apiService.saveShelfGoodsList(body)
// if (response.success) {
// _saveShelfGoodsListUiState.value = UiState.Success(response)
// } else {
// _saveShelfGoodsListUiState.value = UiState.Error(response.message ?: "请求失败")
// }
// }.onFailure {
// _saveShelfGoodsListUiState.value = UiState.Error(it.message ?: "请求异常")
// }
// }
// }
fun getGoodsList(
canteenId: String = "",
goodsName: String = "",
pageNo: Int = 1,
pageSize: Int = 50,
) {
viewModelScope.launch {
_getGoodsListUiState.value = UiState.Loading
runCatching {
if (App.configUrl.isNullOrBlank()) {
App.configUrl = UrlConfig.BASE_URL
}
val response = apiService.getGoodsList(
canteenId = canteenId,
goodsName = goodsName,
pageNo = pageNo,
pageSize = pageSize
)
if (response.isSuccess()) {
_getGoodsListUiState.value = UiState.Success(response)
} else {
_getGoodsListUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getGoodsListUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
fun saveShelfGoodsList(body: ShelfBody) {
viewModelScope.launch {
runCatching {
val resp = apiService.saveShelfGoodsList(body = body)
Log.d("NetViewModel", "saveShelfGoodsList,performSync: resp:${resp.toJsonString()}")
}.onFailure {
it.printStackTrace()
Log.d("NetViewModel", "saveShelfGoodsList异常:${it.message}")
}
}
}
}
@@ -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"
// }
}
@@ -0,0 +1,11 @@
package com.shuwei.intelligent.shelves.net
data class RespData<T>(
// val success: Boolean = false,
val msg: String? = null,
val code: String? = null,
val data: T? = null,
// val total: Int
) {
fun isSuccess() = code == "00000"
}
@@ -0,0 +1,8 @@
package com.shuwei.intelligent.shelves.net
sealed class UiState {
object Initial : UiState()
object Loading : UiState()
data class Success<T>(val data: RespData<T>) : UiState()
data class Error(val msg: String) : UiState()
}
@@ -0,0 +1,30 @@
package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.App
object UrlConfig {
// const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889"
// /**
// * device获取token
// */
// const val DEVICE_TOKEN = "${DEVICE_BASE_URL}/sys/getEquipmentToken"
// /**
// *获取配置信息
// */
// const val DEVICE_CONFIG = "${DEVICE_BASE_URL}/equipment/stEquipment/queryByEquipmentCode"
const val BASE_URL = "https://dev.yixiong-tech.com:8081"
// const val BASE_URL = "https://yyjk.shuziweidao.com/gateway"
// 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_SHELF_LIST =
"${App.configUrl}/terminal/neglect/smartShelves/app/smartShelves/app/getShelvesListByDeviceId"
var GET_GOODS_LIST =
"${App.configUrl}/terminal/neglect/smartShelves/app/smartShelves/app/getGoodsPageList"
var SAVE_SHELF_GOODS_LIST =
"${App.configUrl}/terminal/neglect/smartShelves/app/smartShelves/app/saveShelvesGoodsList"
}
@@ -1,19 +1,23 @@
package com.shuwei.intelligent.shelves.serial
import android.annotation.SuppressLint
import androidx.lifecycle.lifecycleScope
import com.shuwei.intelligent.shelves.utils.hexToBytes
import com.shuwei.intelligent.shelves.utils.toHexString2
import io.github.jeadyx.jserialport.AndroidSerialPort
import io.github.jeadyx.jserialport.SerialPort
import io.github.jeadyx.jserialport.SerialPortFactory
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import java.nio.charset.Charset
import java.util.Locale
object SerialPortManager {
private const val portName: String = "/dev/ttyS1"
private const val baudRate: Int = 115200
private var serialPort: AndroidSerialPort? = null
// private var serialPort: SerialPort? = null
private var serialPort: AndroidSerialPort? = null
// private var serialPort: SerialPort? = null
private val scope = CoroutineScope(Dispatchers.IO)
// 打开串口
@@ -38,7 +42,6 @@ object SerialPortManager {
// 发送数据
suspend fun send(data: String): Boolean {
return try {
// serialPort?.write(data.toByteArray())
serialPort?.write(data.hexToBytes())
true
} catch (e: Exception) {
@@ -48,13 +51,19 @@ object SerialPortManager {
}
// 启动接收协程
@Suppress("DEPRECATION")
@SuppressLint("DefaultLocale")
fun startReceive(callback: (String) -> Unit) {
scope.launch {
while (isActive) {
serialPort?.read()?.collect { buffer ->
// val data = String(buffer)
val data = buffer.toHexString2().toUpperCase()
callback(data)
try {
val flow = serialPort?.read()
flow?.collect { buffer ->
val data = buffer.toHexString2().toUpperCase()
callback(data)
}
} catch (e: Exception) {
e.printStackTrace()
}
delay(50)
}
@@ -0,0 +1,32 @@
//package com.shuwei.intelligent.shelves.task
//
//import android.content.Context
//import android.util.Log
//import androidx.work.CoroutineWorker
//import androidx.work.WorkerParameters
//import com.shuwei.intelligent.shelves.activity.HomeActivity
//import com.shuwei.intelligent.shelves.serial.SerialPortManager
//
//class SyncDeviceTask(appContext: Context, workerParams: WorkerParameters) :
// CoroutineWorker(appContext, workerParams) {
//
// companion object {
// private const val TAG = "SyncDeviceTask"
// }
//
// override suspend fun doWork(): Result {
// return try {
// // 执行后台任务逻辑
// Log.d(TAG, "doWork: ----------SyncDeviceTask----------")
// SerialPortManager.send(HomeActivity.DEVICE_INFO_CMD)
//
// TaskManager.startDeviceTask()
//
// Result.success()
// } catch (e: Exception) {
// e.printStackTrace()
// Result.retry()
// }
// }
//
//}
@@ -1,40 +1,67 @@
package com.shuwei.intelligent.shelves.task
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.shuwei.intelligent.shelves.HomeActivity
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SyncTask(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
companion object {
private const val TAG = "SyncTask"
}
override suspend fun doWork(): Result {
return try {
// 执行后台任务逻辑
performSync()
TaskManager.startTask()
Result.success()
} catch (e: Exception) {
e.printStackTrace()
Result.retry()
}
}
private suspend fun performSync() {
withContext(Dispatchers.IO) {
val list = HomeActivity.list
Log.d(TAG, "performSync: list:${list.toJsonString()}")
}
}
}
//package com.shuwei.intelligent.shelves.task
//
//import android.content.Context
//import android.util.Log
//import androidx.work.CoroutineWorker
//import androidx.work.WorkerParameters
//import com.shuwei.intelligent.shelves.App
//import com.shuwei.intelligent.shelves.activity.HomeActivity
//import com.shuwei.intelligent.shelves.model.ShelfBody
//import com.shuwei.intelligent.shelves.net.apiService
//import com.shuwei.intelligent.shelves.utils.ext.toJsonString
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.withContext
//
//class SyncTask(appContext: Context, workerParams: WorkerParameters) :
// CoroutineWorker(appContext, workerParams) {
//
// companion object {
// private const val TAG = "SyncTask"
// }
//
// override suspend fun doWork(): Result {
// return try {
// // 执行后台任务逻辑
// performSync()
//
// TaskManager.startTask()
//
// Result.success()
// } catch (e: Exception) {
// e.printStackTrace()
// Result.retry()
// }
// }
//
// private suspend fun performSync() {
// withContext(Dispatchers.IO) {
// val list = HomeActivity.list
// Log.d(TAG, "performSync: list:${list.toJsonString()}")
// val submitList = list.sortedBy { it.deviceNo }
// submitList.forEach {
// if (it.goodsId.isNullOrBlank()) {
// it.weight = it.weightBak
// }
// }
//// submitList.forEach {
//// it.temperature = HomeActivity.showTemperatureC
//// it.humidity = HomeActivity.showHumidity
//// }
// val body = ShelfBody().also {
// it.deviceId = App.deviceId
// it.canteenId = App.canteenId
// it.temperature = HomeActivity.showTemperatureC
// it.humidity = HomeActivity.showHumidity
// it.goodsList = submitList
// }
// Log.d(TAG, "performSync: body:${body.toJsonString()}")
// try {
// val resp = apiService.saveShelfGoodsList(body = body)
// Log.d(TAG, "performSync: resp:${resp.toJsonString()}")
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
// }
//
//}
@@ -1,44 +1,59 @@
package com.shuwei.intelligent.shelves.task
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.shuwei.intelligent.shelves.App
import java.util.concurrent.TimeUnit
object TaskManager {
const val TASK_NAME = "IntelligentShelvesSyncTask"
fun startTask() {
val nextRequest = OneTimeWorkRequestBuilder<SyncTask>()
.setInitialDelay(3, TimeUnit.MINUTES)
// .setInitialDelay(10, TimeUnit.SECONDS)
// .setInputData(inputData)
.build()
WorkManager.getInstance(App.get())
.enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// val constraints = Constraints.Builder()
// .setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresBatteryNotLow(true)
//package com.shuwei.intelligent.shelves.task
//
//import androidx.work.ExistingWorkPolicy
//import androidx.work.OneTimeWorkRequestBuilder
//import androidx.work.WorkManager
//import com.shuwei.intelligent.shelves.App
//import java.util.concurrent.TimeUnit
//
//object TaskManager {
// const val TASK_NAME = "IntelligentShelvesSyncTask"
// const val DEVICE_TASK_NAME = "IntelligentShelvesDeviceTask"
//
// fun startTask() {
// val nextRequest = OneTimeWorkRequestBuilder<SyncTask>()
// .setInitialDelay(2, TimeUnit.MINUTES)
//// .setInitialDelay(30, TimeUnit.SECONDS)
//// .setInitialDelay(10, TimeUnit.MINUTES)
//// .setInputData(inputData)
// .build()
// val syncRequest = PeriodicWorkRequestBuilder<SyncTask>(
// 3L, TimeUnit.MINUTES, // 最小间隔15分钟
// 3L, TimeUnit.MINUTES // 弹性时间窗口
// )
// .setConstraints(constraints)
// WorkManager.getInstance(App.getInstance())
// .enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
//
//// val constraints = Constraints.Builder()
//// .setRequiredNetworkType(NetworkType.CONNECTED)
//// .setRequiresBatteryNotLow(true)
//// .build()
//// val syncRequest = PeriodicWorkRequestBuilder<SyncTask>(
//// 3L, TimeUnit.MINUTES, // 最小间隔15分钟
//// 3L, TimeUnit.MINUTES // 弹性时间窗口
//// )
//// .setConstraints(constraints)
//// .build()
//// WorkManager.getInstance(App.get())
//// .enqueueUniquePeriodicWork(
//// TASK_NAME,
//// ExistingPeriodicWorkPolicy.KEEP,
//// syncRequest
//// )
// }
//
// fun cancelTask() {
// WorkManager.getInstance(App.getInstance())
// .cancelUniqueWork(TASK_NAME)
// }
//
// fun startDeviceTask() {
// val nextRequest = OneTimeWorkRequestBuilder<SyncDeviceTask>()
// .setInitialDelay(2, TimeUnit.MINUTES)
// .build()
// WorkManager.getInstance(App.get())
// .enqueueUniquePeriodicWork(
// TASK_NAME,
// ExistingPeriodicWorkPolicy.KEEP,
// syncRequest
// )
}
fun cancelTask() {
WorkManager.getInstance(App.get())
.cancelUniqueWork(TASK_NAME)
}
}
// WorkManager.getInstance(App.getInstance())
// .enqueueUniqueWork(DEVICE_TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// }
//
// fun cancelDeviceTask() {
// WorkManager.getInstance(App.getInstance())
// .cancelUniqueWork(DEVICE_TASK_NAME)
// }
//
//}
@@ -0,0 +1,750 @@
package com.shuwei.intelligent.shelves.utils;
import static android.content.Context.TELEPHONY_SERVICE;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.NetworkInterface;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
public class AppUtil {
public static String getAppPackageName(Context context) {
String packageName = "";
try {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
packageName = pi.packageName;
if (AppUtil.isEmpty(packageName)) {
return "";
}
} catch (Exception e) {
e.printStackTrace();
}
return packageName;
}
public static String getAppVersionName(Context context) {
String versionName = "";
// int versioncode=1;
try {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
versionName = pi.versionName;
// versioncode = pi.versionCode;表示更新了多少次
if (versionName == null || versionName.length() <= 0) {
return "";
}
} catch (Exception e) {
e.printStackTrace();
}
return versionName;
}
public static int getAppVersionCode(Context context) {
int versioncode = 1;
try {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
versioncode = pi.versionCode;
} catch (Exception e) {
e.printStackTrace();
}
return versioncode;
}
//判断微信是否安装
public static boolean isWeixinInstalled(Context context) {
final PackageManager packageManager = context.getPackageManager();// 获取packagemanager
List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);// 获取所有已安装程序的包信息
if (pinfo != null) {
for (int i = 0; i < pinfo.size(); i++) {
String pn = pinfo.get(i).packageName;
if (pn.equals("com.tencent.mm")) {
return true;
}
}
}
return false;
}
/**
* 打电话
* <p>
* Intent.ACTION_DIAL Intent.ACTION_CALL
*
* @param context
* @param mobile
*/
public static void callUp(Context context, String mobile) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:"
+ mobile));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
* 获取设备ID
*
* @param context
* @return
*/
public static String getDevId(Context context) {
TelephonyManager TelephonyMgr = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
return TelephonyMgr.getDeviceId();
}
/**
* 姓名脱敏
*
* @param fullName
* @return
*/
public static String desensitizedName(String fullName) {
if (fullName == null || fullName.length() <= 1) {
return fullName;
}
char[] nameArr = fullName.toCharArray();
if (nameArr.length > 2) {
for (int i = 1; i < nameArr.length - 1; i++) {
nameArr[i] = '*';
}
} else {
nameArr[1] = '*';
}
return new String(nameArr);
}
public static String formatDateGetFull(String date) {
if (isEmpty(date)) {
return "";
}
Date d = new Date(Long.parseLong(date));
SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
return dateFormat1.format(d);
}
public static String formatDateGetCurrentTime() {
Date d = new Date(System.currentTimeMillis());
SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//.SSS
return dateFormat1.format(d);
}
public static String formatDateGetFull(long date) {
if (date == 0) {
return "";
}
Date d = new Date(date);
SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
return dateFormat1.format(d);
}
public static String formatDateGetDay(long date) {
if (date == 0) {
return "";
}
Date d = new Date(date);
SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd");
return dateFormat1.format(d);
}
public static boolean isEmpty(String s) {
if (TextUtils.isEmpty(s) || s.trim().equals("null")) {
return true;
} else {
return false;
}
}
/**
* 格式化浮点型
*
* @param data
* @return
*/
public static String formatDouble(double data) {
return new DecimalFormat("0.00").format(data);
}
/**
* 格式化分钟
*
* @param minutes
* @return
*/
public static String formatMinutes(int minutes) {
int hour = minutes / 60;
int minute = minutes % 60;
if (hour > 0 && minute > 0) {
return hour + "小时" + minute + "分钟";
} else if (hour > 0) {
return hour + "小时";
} else {
return minute + "分钟";
}
}
//com.fawan.news
public static void goToMarket(Context context, String packageName) {
Uri uri = Uri.parse("market://details?id=" + packageName);
Intent goToMarket = new Intent(Intent.ACTION_VIEW, uri);
try {
context.startActivity(goToMarket);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
/**
* true为存在,false为不存在
*
* @param context
* @param packageName
* @return
*/
public static boolean isInstallApp(Context context, String packageName) {
try {
context.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
/**
* 格式化float 保留两位小数
*
* @param data
* @return
*/
public static float formatFloat2(float data) {
// DecimalFormat decimalFormat = new DecimalFormat("0.00");//构造方法的字符格式这里如果小数不足2位,会以0补足.
// return decimalFormat.format(data);//返回字符串
int scale = 1;//设置位数
int roundingMode = 4;//表示四舍五入,可以选择其他舍值方式,例如去尾,等等.
BigDecimal bd = new BigDecimal((double) data);
bd = bd.setScale(scale, roundingMode);
data = bd.floatValue();
return data;
}
/**
* Android 6.0 之前(不包括6.0)获取mac地址
* 必须的权限 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
*
* @param context * @return
*/
public static String getMacDefault(Context context) {
String mac = "";
if (context == null) {
return mac;
}
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo info = null;
try {
info = wifi.getConnectionInfo();
} catch (Exception e) {
e.printStackTrace();
}
if (info == null) {
return null;
}
mac = info.getMacAddress();
if (!TextUtils.isEmpty(mac)) {
mac = mac.toUpperCase(Locale.ENGLISH);
}
return mac;
}
/**
* Android 6.0-Android 7.0 获取mac地址
*/
public static String getMacAddress() {
String macSerial = null;
String str = "";
try {
Process pp = Runtime.getRuntime().exec("cat/sys/class/net/wlan0/address");
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
while (null != str) {
str = input.readLine();
if (str != null) {
macSerial = str.trim();//去空格
break;
}
}
} catch (IOException ex) {
// 赋予默认值
ex.printStackTrace();
}
return macSerial;
}
/**
* Android 7.0之后获取Mac地址
* 遍历循环所有的网络接口,找到接口是 wlan0
* 必须的权限 <uses-permission android:name="android.permission.INTERNET"></uses-permission>
*
* @return
*/
public static String getMacFromHardware() {
try {
ArrayList<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (!nif.getName().equals("wlan0"))
continue;
byte[] macBytes = nif.getHardwareAddress();
if (macBytes == null) return "";
StringBuilder res1 = new StringBuilder();
for (Byte b : macBytes) {
res1.append(String.format("%02X:", b));
}
if (!TextUtils.isEmpty(res1)) {
res1.deleteCharAt(res1.length() - 1);
}
return res1.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 获取mac地址(适配所有Android版本)
*
* @return
*/
public static String getMac(Context context) {
String mac = "";
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
mac = getMacDefault(context);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
mac = getMacAddress();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mac = getMacFromHardware();
}
return mac;
}
//把String转化为float
public static double convertToFloat(String number, double defaultValue) {
if (TextUtils.isEmpty(number)) {
return defaultValue;
}
try {
return Double.parseDouble(number);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 获取AndroidId
*
* @param context
* @return
*/
public static String getAndroidId(Context context) {
String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
return androidId;
}
/**
* 获取设备唯一 UDID
*
* @param context
* @return
*/
@SuppressLint("MissingPermission")
public static String getUDID(Context context) {
// return "SWZC-HG-001";
//// //"test111";
String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
if (!androidID.equals("")) {
try {
if (!"9774d56d682e549c".equals(androidID)) {
androidID = UUID.nameUUIDFromBytes(androidID.getBytes("utf8")).toString();
} else {
@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);
}
return androidID;
}
//需要权限 android.permission.READ_PHONE_STATE
TelephonyManager TelephonyMgr = (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
String szImei = TelephonyMgr.getDeviceId();
if (!szImei.equals("")) {
return szImei;
}
//需要权限 android.permission.ACCESS_WIFI_STATE
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
String m_szWLANMAC = wm.getConnectionInfo().getMacAddress();
if (!m_szWLANMAC.equals("")) {
return m_szWLANMAC;
}
//需要权限 android.permission.BLUETOOTH
BluetoothAdapter m_BluetoothAdapter = null;
m_BluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
String m_szBTMAC = m_BluetoothAdapter.getAddress();
if (!m_szBTMAC.equals("")) {
return m_szBTMAC;
}
return getUniquePsuedoID();
}
//获得 Psuedo ID
public static String getUniquePsuedoID() {
String serial = null;
String m_szDevIDShort = "35" +
Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
Build.USER.length() % 10; //13 位
try {
serial = Build.class.getField("SERIAL").get(null).toString();
//API>=9 使用serial号
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
} catch (Exception exception) {
//serial需要一个初始化,随意值
serial = "serial";
}
//使用硬件信息拼凑出来的15位号码
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}
public static String getCPUSerial() {
String line = "";
String TAG = "aaa";
Log.e(TAG, " get_quck_Sn() ");
Class<?> c = null;
try {
c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class);
line = (String) get.invoke(c, "ro.serialno");
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Log.e(TAG, " get_quck_Sn() " + line);
System.out.println("设备串号" + line);
return line;
}
/**
* 判断网络连接状态
*
* @param context
* @return
*/
public static boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
if (mNetworkInfo != null) {
return mNetworkInfo.isAvailable();
}
}
return false;
}
/**
* 判断WiFi连接状态
*
* @param context
* @return
*/
public static boolean isWifiConnected(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWiFiNetworkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (mWiFiNetworkInfo != null) {
return mWiFiNetworkInfo.isAvailable();
}
}
return false;
}
/**
* 判断移动网络状态
*
* @param context
* @return
*/
public static boolean isMobileConnected(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mMobileNetworkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if (mMobileNetworkInfo != null) {
return mMobileNetworkInfo.isAvailable();
}
}
return false;
}
/**
* 获取网络连接类型
*
* @param context
* @return
*/
public static int getConnectedType(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
if (mNetworkInfo != null && mNetworkInfo.isAvailable()) {
return mNetworkInfo.getType();
}
}
return -1;
}
/**
* 根据字符的起始和结束索引提取子串
*
* @param input 原始字符串
* @param startIndex 起始索引(包含,从0开始)
* @param endIndex 结束索引(不包含)
* @return 子串,若输入无效或索引越界则返回空字符串
*/
public static String getSubstringByIndices(String input, int startIndex, int endIndex) {
if (input == null) {
return "";
}
// 处理索引越界问题
int safeStart = Math.max(startIndex, 0);
int safeEnd = Math.min(endIndex, input.length());
if (safeStart > safeEnd) {
return "";
}
return input.substring(safeStart, safeEnd);
}
public static String getSubstringByIndex(String input, int startIndex, int length) {
if (input == null) {
return "";
}
// 处理索引越界问题
int safeStart = Math.max(startIndex, 0);
int safeEnd = Math.min(startIndex + length, input.length());
if (safeStart > safeEnd) {
return "";
}
return input.substring(safeStart, safeEnd);
}
/**
* 十进制转十六进制
*
* @param decimal
* @return
*/
public static String decimalToHexWithPadding(int decimal, int padding) {
// 将十进制转换为十六进制,并转换为字符串
String hex = Integer.toHexString(decimal);
// 确保字符串长度为至少4位,不足部分前面补0
while (hex.length() < padding) {
hex = "0" + hex;
}
return hex.toUpperCase(); // 返回大写形式的十六进制字符串
}
/**
* 十进制转二进制,且返回的二进制为至少7位数
*
* @param decimal
* @return
*/
public static String decimalToBinary(int decimal) {
// 如果输入为0,直接返回"0"
if (decimal == 0) {
return "0";
}
StringBuilder binary = new StringBuilder();
// 除2取余法,将余数加入二进制字符串
while (decimal > 0) {
int remainder = decimal % 2;
binary.insert(0, remainder);
decimal = decimal / 2;
}
int length = binary.length();
if (length < 7) {
int padding = 7 - length;
for (int i = 0; i < padding; i++) {
binary.insert(0, '0');
}
}
return binary.toString();
}
/**
* 将二进制字符串转换为十六进制字符串,每8位转换为两位十六进制,不足两位前面补零
*
* @param binaryStr 输入的二进制字符串(仅包含0和1)
* @return 转换后的十六进制字符串
* @throws IllegalArgumentException 如果输入不是有效的二进制字符串
*/
public static String binaryToHex(String binaryStr) {
// 校验输入合法性
if (binaryStr == null || !binaryStr.matches("[01]+")) {
throw new IllegalArgumentException("Invalid binary string");
}
// 补前导零使长度成为8的倍数
int length = binaryStr.length();
int padding = (8 - (length % 8)) % 8; // 计算需要补零的数量
StringBuilder paddedBinary = new StringBuilder();
for (int i = 0; i < padding; i++) {
paddedBinary.append('0');
}
paddedBinary.append(binaryStr);
// 每8位转换为两位十六进制
StringBuilder hexStr = new StringBuilder();
for (int i = 0; i < paddedBinary.length(); i += 8) {
String byteStr = paddedBinary.substring(i, i + 8);
int decimalValue = Integer.parseInt(byteStr, 2);
hexStr.append(String.format("%02X", decimalValue & 0xFF));
}
return hexStr.toString();
}
/**
* 数据校验 异或处理
*/
public static String getXor(String content) {
int a = 0;
for (int i = 0; i < content.length() / 2; i++) {
a = a ^ Integer.parseInt(content.substring(i * 2, (i * 2) + 2), 16);
}
String result = Integer.toHexString(a).toUpperCase();
if (result.length() == 1) {
return "0" + result;
} else {
return result;
}
}
public static double formatPersonInfo(String input, int startIndex, int length) {
if (input == null) {
return 0;
}
// 处理索引越界问题
int safeStart = Math.max(startIndex, 0);
int safeEnd = Math.min(startIndex + length, input.length());
if (safeStart > safeEnd) {
return 0;
}
String result = input.substring(safeStart, safeEnd);
double num = Integer.parseInt(result, 16);
return num;
}
/**
* 安装apk
*
* @param activity
* @param apkFile
*/
public static void installApk(Context activity, File apkFile) {
//文件有所有者概念,现在是属于当前进程的,需要把这个文件暴露给系统安装程序(其他进程)去安装
//因此,可能会存在权限问题,需要做下面的设置
//如果文件是sdcard上的,就不需要这个操作了
try {
apkFile.setExecutable(true, false);
apkFile.setReadable(true, false);
apkFile.setWritable(true, false);
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
Uri uri;
//TODO N FileProvider
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileProvider", apkFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} else {
uri = Uri.fromFile(apkFile);
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
activity.startActivity(intent);
//TODO 0 INSTALL PERMISSION
//在AndroidManifest中加入权限即可
}
}
@@ -0,0 +1,18 @@
package com.shuwei.intelligent.shelves.utils
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.shuwei.intelligent.shelves.activity.InitActivity
import kotlin.jvm.java
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
// 启动服务或Activity
val launchIntent = Intent(context, InitActivity::class.java)
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(launchIntent)
}
}
}
@@ -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
}
}
@@ -1,9 +1,11 @@
package com.shuwei.intelligent.shelves.utils
import androidx.annotation.NonNull
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Calendar
import java.util.Date
import java.util.Locale
@@ -19,6 +21,19 @@ object DateTimeUtil {
return sdf.format(dateTime)
}
fun stringToDate(dateText: String, pattern: String = YYYY_MM_DD_HH_MM_SS): Date? {
val sdf = SimpleDateFormat(pattern, Locale.CHINA)
return sdf.parse(dateText)
}
fun addDays(date: Date, days: Int): Date {
val calendar = Calendar.getInstance().apply {
time = date
add(Calendar.DAY_OF_MONTH, days)
}
return calendar.time
}
// fun convert(dateStr: String, pattern: String = YYYY_MM_DD_HH_MM_SS): Date {
// val formatter = DateTimeFormatter.ofPattern(pattern)
// val ldt = LocalDateTime.parse(dateStr, formatter)
@@ -45,3 +45,49 @@ fun byteArrayToHexString(bytes: ByteArray): String {
fun String.hexToBytes(): ByteArray = hexStringToByteArray(this)
fun ByteArray.toHexString2(): String = byteArrayToHexString(this)
fun hexToBinary(hex: String): String {
val hexToBinaryMap = mapOf(
'0' to "0000", '1' to "0001", '2' to "0010", '3' to "0011",
'4' to "0100", '5' to "0101", '6' to "0110", '7' to "0111",
'8' to "1000", '9' to "1001", 'A' to "1010", 'B' to "1011",
'C' to "1100", 'D' to "1101", 'E' to "1110", 'F' to "1111"
)
return hex.map { char ->
hexToBinaryMap[char] ?: throw IllegalArgumentException("非法十六进制字符: $char")
}.joinToString("")
}
fun binaryToHex(binary: String): String {
// 补齐到4的倍数长度(方便分组)
val paddedBinary = binary.padStart(
length = ((binary.length + 3) / 4) * 4,
padChar = '0'
)
// 每4位二进制对应1位十六进制
return paddedBinary.chunked(4).joinToString("") { nibble ->
when (nibble) {
"0000" -> "0"
"0001" -> "1"
"0010" -> "2"
"0011" -> "3"
"0100" -> "4"
"0101" -> "5"
"0110" -> "6"
"0111" -> "7"
"1000" -> "8"
"1001" -> "9"
"1010" -> "A"
"1011" -> "B"
"1100" -> "C"
"1101" -> "D"
"1110" -> "E"
"1111" -> "F"
else -> throw IllegalArgumentException("无效的二进制段: $nibble")
}
}
.trimStart('0') // 去除前导零
.ifEmpty { "0" } // 处理全零情况
}
@@ -0,0 +1,5 @@
package com.shuwei.intelligent.shelves.utils
fun f2C(fahrenheit: Double): Double {
return (fahrenheit - 32) * 5 / 9
}
@@ -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("所有定时任务已取消")
}
@@ -13,5 +13,9 @@ object KeyboardUtil {
view.clearFocus() // 清除焦点避免键盘再次弹出
}
fun hideKeyboard(view: View) {
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
view.clearFocus() // 清除焦点避免键盘再次弹出
}
}
@@ -0,0 +1,101 @@
package com.shuwei.intelligent.shelves.utils
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.WriterException
import com.google.zxing.common.BitMatrix
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
/**
* 二维码生成工具类
*/
object QRCodeUtil {
/**
* 生成二维码(默认大小)
* @param content 二维码内容
* @return 生成的二维码Bitmap
*/
@JvmOverloads
fun generateQRCode(content: String, size: Int = 500): Bitmap? {
return generateQRCode(content, size, Color.BLACK, Color.WHITE)
}
/**
* 生成二维码(自定义颜色)
* @param content 二维码内容
* @param size 二维码边长(像素)
* @param colorCode 二维码颜色
* @param backgroundColor 背景颜色
* @return 生成的二维码Bitmap
*/
fun generateQRCode(
content: String,
size: Int,
colorCode: Int,
backgroundColor: Int
): Bitmap? {
if (content.isEmpty()) {
return null
}
return try {
val hints = mutableMapOf<EncodeHintType, Any>().apply {
put(EncodeHintType.CHARACTER_SET, "UTF-8")
put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H) // 纠错级别
put(EncodeHintType.MARGIN, 1) // 边距
}
val bitMatrix = QRCodeWriter().encode(
content,
BarcodeFormat.QR_CODE,
size,
size,
hints
)
val pixels = IntArray(size * size).apply {
for (y in 0 until size) {
for (x in 0 until size) {
this[y * size + x] = if (bitMatrix.get(x, y)) colorCode else backgroundColor
}
}
}
Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
setPixels(pixels, 0, size, 0, 0, size, size)
}
} catch (e: WriterException) {
e.printStackTrace()
null
}
}
/**
* 生成带Logo的二维码
* @param content 二维码内容
* @param size 二维码边长(像素)
* @param logo Logo Bitmap
* @return 带Logo的二维码Bitmap
*/
fun generateQRCodeWithLogo(content: String, size: Int, logo: Bitmap?): Bitmap? {
val qrCode = generateQRCode(content, size) ?: return null
logo ?: return qrCode
val logoSize = size / 5 // Logo大小约为二维码的1/5
val scaledLogo = Bitmap.createScaledBitmap(logo, logoSize, logoSize, false)
val offset = (size - logoSize) / 2
return Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
val canvas = Canvas(this)
canvas.drawBitmap(qrCode, 0f, 0f, null)
canvas.drawBitmap(scaledLogo, offset.toFloat(), offset.toFloat(), null)
}
}
}
@@ -0,0 +1,31 @@
package com.shuwei.intelligent.shelves.utils
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.utils.ext.put
import kotlin.to
object SpTool {
const val DEVICE_CONFIG_CACHE = "deviceConfigCache"
const val TOKEN = "token"
const val LAUNCH_PAGE_TYPE = "launchPageType"
const val DEVICE_ID = "deviceId"
val pref = App.getSharedPref()!!
fun put(key: String, value: Any) {
pref.put(key to value)
}
fun getInt(key: String, defValue: Int = 0): Int {
return pref.getInt(key, defValue)
}
fun getString(key: String, defValue: String? = ""): String {
return pref.getString(key, defValue) ?: ""
}
fun getBoolean(key: String, defValue: Boolean = false): Boolean {
return pref.getBoolean(key, defValue)
}
}
@@ -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);
}
}
@@ -1,14 +0,0 @@
package com.shuwei.intelligent.shelves.utils
import android.widget.Toast
import com.shuwei.intelligent.shelves.App
//D6DADEDB0302000001000C06
object UIUtils {
fun toast(strRes: Int) {
Toast.makeText(App.get(), strRes, Toast.LENGTH_SHORT).show()
}
fun toast(str: String) {
Toast.makeText(App.get(), str, Toast.LENGTH_SHORT).show()
}
}
@@ -10,6 +10,7 @@ import android.os.Bundle
import android.util.TypedValue
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.Toast
import androidx.core.app.ActivityOptionsCompat
@@ -22,6 +23,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import android.content.ClipData
import android.content.ClipboardManager
fun Context.toast(
message: String?,
@@ -151,3 +154,24 @@ 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()
}
@@ -1,19 +0,0 @@
//package com.shuwei.intelligent.shelves.viewmodel
//
//import androidx.lifecycle.LiveData
//import androidx.lifecycle.MutableLiveData
//import androidx.lifecycle.ViewModel
//import com.shuwei.intelligent.shelves.model.ShelfModel
//
//class ShelfDataViewModel : ViewModel() {
//
// private val _shelfItems = MutableLiveData<MutableList<ShelfModel>>()
// val shelfItems: LiveData<MutableList<ShelfModel>> = _shelfItems
//
// fun updateItems(newItems: MutableList<ShelfModel>) {
// _shelfItems.value = newItems
// }
//
// fun getCurrentItems(): MutableList<ShelfModel> = _shelfItems.value ?: mutableListOf()
//
//}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- android:width="800dp"-->
<!-- android:height="1280dp"-->
<item>
<shape android:shape="rectangle">
<solid android:color="@color/bg_page" />
</shape>
</item>
<item
android:width="450dp"
android:height="430dp"
android:gravity="top|center_horizontal"
android:top="150dp">
<bitmap android:src="@drawable/img_init" android:scaleType="fitCenter"/>
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#30000000">
<item>
<shape android:shape="rectangle">
<solid android:color="#ff08C6DC" />
<corners android:radius="8dp" />
</shape>
</item>
</ripple>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- android:width="800dp"-->
<!-- android:height="1280dp"-->
<item>
<shape android:shape="rectangle">
<solid android:color="@color/bg_page" />
</shape>
</item>
<item
android:width="225dp"
android:height="215dp"
android:gravity="top|center_horizontal"
android:top="75dp">
<bitmap android:src="@drawable/img_init" android:scaleType="fitCenter"/>
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

@@ -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>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#90000000"/>
<corners android:radius="5dp"/>
</shape>
@@ -0,0 +1,45 @@
<?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>
@@ -0,0 +1,27 @@
<?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>
@@ -0,0 +1,48 @@
<?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>
@@ -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>
@@ -0,0 +1,269 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@color/bg_page">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="376dp"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/tvShelfName"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_marginTop="24dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textColor="@color/bg_page"
android:textSize="30sp"
tools:text="货架 - 03" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/food_weight_orange"
android:textSize="60sp"
android:textStyle="bold"
tools:text="1506克" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_name_black"
android:textSize="36sp"
android:textStyle="bold"
tools:text="金针菇" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearZero"
android:layout_width="120dp"
android:layout_height="60dp"
android:layout_gravity="top|end"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清零"
android:textColor="@color/white"
android:textSize="30sp"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<ImageView
android:id="@+id/ivBack"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="top|start"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="48dp"
android:background="@drawable/shape_search"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:src="@drawable/ic_search_food" />
<EditText
android:id="@+id/etInputFood"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginEnd="24dp"
android:background="@null"
android:fontFamily="sans-serif-medium"
android:hint="输入食材名称"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:paddingStart="1dp"
android:paddingEnd="1dp"
android:text=""
android:textColor="@color/food_name_black"
android:textColorHint="#B4BEC8"
android:textSize="30sp"
tools:ignore="Autofill,HardcodedText,TextFields" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="24dp"
android:layout_weight="1">
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.scwang.smart.refresh.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingStart="36dp"
android:paddingTop="12dp"
android:paddingEnd="36dp"
android:paddingBottom="12dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"
tools:listitem="@layout/list_item_search" />
<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<include
android:id="@+id/include"
layout="@layout/layout_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearEmpty"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清空"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="48dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="确定"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
@@ -0,0 +1,22 @@
<?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>
@@ -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>
@@ -0,0 +1,29 @@
<?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>
@@ -0,0 +1,28 @@
<?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="250dp"
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="50dp"
android:textSize="50sp"
android:text="暂无数据"
tools:ignore="HardcodedText" />
</LinearLayout>
@@ -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>
@@ -0,0 +1,48 @@
<?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="土豆丝土豆丝土豆丝土豆丝土豆丝土豆丝"
/>
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layoutCard"
android:layout_width="match_parent"
android:layout_height="216dp"
android:layout_margin="12dp"
app:cardBackgroundColor="@color/bg_card_blue"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:rippleColor="#B0BEC5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/tvShelfName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/shelf_name_white"
android:textSize="20sp"
android:textStyle="bold"
tools:text="货架-01" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
tools:text="空" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.833" />
<TextView
android:id="@+id/tvFoodWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_weight_blue"
android:textSize="24sp"
android:textStyle="bold"
tools:text="0千克" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.933" />
<TextView
android:id="@+id/tvStoreDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_weight_blue"
android:textSize="18sp"
tools:text="-" />
</LinearLayout>
<ImageView
android:id="@+id/ivStaleFood"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_stale_food_512"
android:layout_gravity="top|end"
tools:ignore="ContentDescription"
android:visibility="gone"/>
</com.google.android.material.card.MaterialCardView>
+14 -9
View File
@@ -5,34 +5,39 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/bg_page">
android:fitsSystemWindows="false"
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">
android:layout_height="30dp">
<!-- android:paddingTop="9dp"-->
<TextView
android:id="@+id/tv_left_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textColor="@color/white"
android:layout_gravity="start"
android:textSize="24sp"
android:textSize="12sp"
android:paddingStart="12dp"
android:paddingEnd="0dp"
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:layout_height="match_parent"
android:gravity="center_vertical"
android:textColor="@color/white"
android:layout_gravity="end"
android:fontFamily="sans-serif-medium"
android:textSize="24sp"
android:textSize="12sp"
android:paddingStart="0dp"
android:paddingEnd="12dp"
tools:text="6月22日 星期日 15:26:33" />
</FrameLayout>
+19 -4
View File
@@ -1,5 +1,5 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -13,8 +13,23 @@
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
android:overScrollMode="never"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
tools:listitem="@layout/list_item_shelf"/>
</LinearLayout>
<include
android:id="@+id/include"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/layout_empty_view"
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>
+48
View File
@@ -0,0 +1,48 @@
<?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="225dp"
android:layout_height="215dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="75dp"
android:src="@drawable/img_init"
tools:ignore="ContentDescription"
android:visibility="invisible"/>
<TextView
android:id="@+id/btnInit"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="50dp"
android:background="@drawable/bg_init_button"
android:gravity="center"
android:padding="10dp"
android:text="设备初始化"
android:textColor="@color/white"
android:textSize="15sp"
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="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="25dp"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_logo512" />
</LinearLayout>
@@ -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="10dp"
android:textSize="12sp"
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>
@@ -0,0 +1,111 @@
<?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="260dp"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="6dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<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_marginTop="12dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="28sp"
android:layout_gravity="center"
android:text="设置"
android:textColor="@color/black"/>
</FrameLayout>
<!-- <EditText-->
<!-- android:id="@+id/etInputCmdPrefix"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="60dp"-->
<!-- android:hint="请输入命令"-->
<!-- android:maxLines="1"-->
<!-- android:text=""-->
<!-- android:textSize="22sp" />-->
<EditText
android:id="@+id/etInputCmd"
android:layout_width="match_parent"
android:layout_height="60dp"
android:hint="请输入命令内容"
android:maxLines="1"
android:textSize="22sp" />
<Button
android:id="@+id/btnSendCmd"
android:layout_width="180dp"
android:layout_height="60dp"
android:textSize="22sp"
android:layout_gravity="center"
android:layout_marginTop="30dp"
android:text="发送命令" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="6dp"
app:cardElevation="0dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/tvCmdRecord"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:text=""/>
</LinearLayout>
</ScrollView>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
+142 -71
View File
@@ -9,12 +9,12 @@
<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"
android:layout_height="190dp"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardCornerRadius="6dp"
app:cardElevation="0dp">
<FrameLayout
@@ -25,20 +25,20 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
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:layout_height="30dp"
android:layout_marginTop="12dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textColor="@color/bg_page"
android:textSize="30sp"
android:fontFamily="sans-serif-medium"
android:textSize="15sp"
tools:text="货架 - 03" />
<Space
@@ -54,7 +54,7 @@
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/food_weight_orange"
android:textSize="60sp"
android:textSize="30sp"
android:textStyle="bold"
tools:text="1506克" />
@@ -70,7 +70,7 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_name_black"
android:textSize="36sp"
android:textSize="18sp"
android:textStyle="bold"
tools:text="金针菇" />
@@ -82,24 +82,34 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearZero"
android:layout_width="120dp"
android:layout_height="60dp"
android:layout_width="65dp"
android:layout_height="30dp"
android:layout_gravity="top|end"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清零"
android:fontFamily="sans-serif-medium"
android:textColor="@color/white"
android:textSize="30sp"
app:cornerRadius="12dp"
android:textSize="15sp"
app:cornerRadius="6dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<ImageView
android:id="@+id/ivBack"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="top|start"
android:layout_marginTop="12dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
@@ -107,12 +117,12 @@
<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"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardCornerRadius="6dp"
app:cardElevation="0dp">
<LinearLayout
@@ -122,75 +132,136 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="48dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
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:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
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:layout_height="36dp"
android:layout_marginEnd="12dp"
android:background="@null"
android:fontFamily="sans-serif-medium"
android:hint="输入食材名称"
android:maxLines="1"
android:inputType="text"
android:text=""
android:imeOptions="actionSearch"
android:textColor="@color/food_name_black"
android:inputType="text"
android:maxLines="1"
android:paddingStart="1dp"
android:paddingEnd="1dp"
android:text=""
android:textColor="@color/food_name_black"
android:textColorHint="#B4BEC8"
android:fontFamily="sans-serif-medium"
android:textSize="30sp"
android:textSize="15sp"
tools:ignore="Autofill,HardcodedText,TextFields" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSearch"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="36dp"
android:layout_marginTop="36dp"
android:layout_marginEnd="36dp"
android:layout_weight="1"
android:overScrollMode="never"
tools:listitem="@layout/list_item_search"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"/>
android:layout_marginTop="8dp"
android:layout_weight="1">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.scwang.smart.refresh.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingStart="12dp"
android:paddingTop="4dp"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"
tools:listitem="@layout/list_item_search" />
<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<include
android:id="@+id/include"
layout="@layout/layout_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="48dp"
android:layout_marginStart="48dp"
android:layout_marginBottom="48dp"
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" />
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearEmpty"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="40dp"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
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="15sp"
android:textStyle="bold"
app:cornerRadius="6dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="40dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp"
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="15sp"
android:textStyle="bold"
app:cornerRadius="6dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
</LinearLayout>
</LinearLayout>
+5 -5
View File
@@ -11,12 +11,12 @@
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:paddingStart="12dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:paddingEnd="12dp"
android:textColor="@color/white_f6"
android:textSize="32sp"
android:textSize="16sp"
tools:text="暂无数据" />
</FrameLayout>
@@ -0,0 +1,29 @@
<?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="10dp"
android:paddingTop="5dp"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:background="@drawable/shape_dialog_small"
tools:ignore="MissingDefaultResource">
<ProgressBar
android:layout_width="26dp"
android:layout_height="26dp"
android:indeterminateTint="@color/white_f6" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="加载中……"
android:textColor="@color/white_f6"
android:textSize="14sp"
tools:ignore="HardcodedText" />
</LinearLayout>
@@ -0,0 +1,28 @@
<?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="120dp"
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="20dp"
android:textSize="20sp"
android:text="暂无数据"
tools:ignore="HardcodedText" />
</LinearLayout>
@@ -0,0 +1,126 @@
<?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>
+13 -11
View File
@@ -1,5 +1,4 @@
<?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"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="80dp"-->
@@ -26,20 +25,23 @@
android:id="@+id/btnFoodName"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_margin="12dp"
android:layout_height="40dp"
android:layout_margin="6dp"
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="土豆丝土豆丝土豆丝土豆丝土豆丝土豆丝"
/>
android:textSize="15sp"
app:cornerRadius="6dp"
app:strokeWidth="1dp"
tools:text="土豆丝土豆丝土豆丝土豆丝土豆丝土豆丝" />
<!-- android:paddingHorizontal="2dp"-->
<!-- android:paddingLeft="5dp"-->
<!-- android:paddingRight="5dp"-->
+18 -10
View File
@@ -4,26 +4,26 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layoutCard"
android:layout_width="match_parent"
android:layout_height="216dp"
android:layout_margin="12dp"
android:layout_height="140dp"
android:layout_margin="6dp"
app:cardBackgroundColor="@color/bg_card_blue"
app:cardCornerRadius="12dp"
app:cardElevation="5dp"
app:cardCornerRadius="6dp"
app:cardElevation="2dp"
app:rippleColor="#B0BEC5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="216dp"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
android:padding="12dp">
<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:textSize="12sp"
android:textStyle="bold"
tools:text="货架-01" />
@@ -39,7 +39,7 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="30sp"
android:textSize="16sp"
android:textStyle="bold"
tools:text="空" />
@@ -55,7 +55,7 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_weight_blue"
android:textSize="24sp"
android:textSize="14sp"
android:textStyle="bold"
tools:text="0千克" />
@@ -71,9 +71,17 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/food_weight_blue"
android:textSize="18sp"
android:textSize="12sp"
tools:text="-" />
</LinearLayout>
<ImageView
android:id="@+id/ivStaleFood"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/ic_stale_food_512"
android:layout_gravity="top|end"
tools:ignore="ContentDescription"
android:visibility="gone"/>
</com.google.android.material.card.MaterialCardView>
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+1
View File
@@ -12,6 +12,7 @@
<!-- Status bar color. -->
<!-- <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>-->
<item name="android:statusBarColor">@color/white</item>
<item name="android:windowBackground">@drawable/bg_splash</item>
<!-- Customize your theme here. -->
</style>
</resources>
+27 -1
View File
@@ -10,7 +10,33 @@
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="android:statusBarColor">@color/bg_page</item>
<!-- <item name="android:statusBarColor">@color/white</item>-->
<item name="android:windowBackground">@drawable/bg_splash</item>
<!-- Customize your theme here. -->
</style>
<style name="LoadingDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowIsFloating">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</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>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- <domain-config cleartextTrafficPermitted="true">-->
<!-- <domain includeSubdomains="true">192.168.1.3</domain>-->
<!-- <domain includeSubdomains="true">192.168.1.7</domain>-->
<!-- </domain-config> -->
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
BIN
View File
Binary file not shown.
+4
View File
@@ -8,8 +8,12 @@ junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.6.1"
material = "1.10.0"
core = "3.4.1"
androidCore = "3.3.0"
[libraries]
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
core = { module = "com.google.zxing:core", version.ref = "core" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+2 -2
View File
@@ -1,7 +1,7 @@
#Thu Jul 17 15:32:57 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
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\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+3 -5
View File
@@ -1,10 +1,8 @@
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=D\:\\Android\\sdk
#Tue Jan 13 10:38:32 CST 2026
sdk.dir=C\:\\Users\\HUAWEI\\AppData\\Local\\Android\\Sdk