From 133f309d4b0d0936b40abba9e222f19d61d5ca8f Mon Sep 17 00:00:00 2001 From: mazengfei <331023091@qq.com> Date: Wed, 1 Apr 2026 17:07:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=E6=97=B6?= =?UTF-8?q?=E9=92=9F=E7=AE=A1=E7=90=86=E5=B9=B6=E4=BC=98=E5=8C=96=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A0=8F=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 BaseActivity 中实现统一的时钟管理机制,避免各子类重复实现 - LogActivity 和 ShelfActivity 覆写 onClockTick() 自定义状态栏显示 - HomeActivity 移除重复的时间更新逻辑,启用右侧状态栏菜单 - 提取协议常量到 ProtocolConstants,提高代码可维护性 - 优化开锁逻辑判断,改为基于 deviceId 的动态判断 - 新增右侧状态栏弹出菜单布局文件 Co-Authored-By: Claude Sonnet 4.6 --- .../shelves/activity/HomeActivity.kt | 32 ++--- .../shelves/activity/LogActivity.kt | 130 ++++++++++++++++-- .../shelves/activity/ShelfActivity.kt | 30 ++-- .../intelligent/shelves/base/BaseActivity.kt | 41 +++++- .../shelves/serial/SerialPortManager.kt | 2 +- app/src/main/res/layout/activity_log_list.xml | 56 +++++++- .../res/layout/popup_right_status_menu.xml | 32 +++++ 7 files changed, 261 insertions(+), 62 deletions(-) create mode 100644 app/src/main/res/layout/popup_right_status_menu.xml diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/activity/HomeActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/activity/HomeActivity.kt index ac73c00..9c5f2e8 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/activity/HomeActivity.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/activity/HomeActivity.kt @@ -43,18 +43,16 @@ import com.shuwei.intelligent.shelves.utils.ext.toast import com.shuwei.intelligent.shelves.utils.ext.visible import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale class HomeActivity : BaseActivity() { - companion object { - private const val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss" - } + companion object {} + + override val enableRightStatusMenu: Boolean = true // 货架列表数据 private val list: MutableList = mutableListOf() @@ -68,8 +66,8 @@ class HomeActivity : BaseActivity() { // 当前湿度 private var showHumidity = "0" - // 是否在点击货架时发送开锁指令;有门锁的部署点(如1楼餐厅)为 true,无门锁的部署点(如401室)为 false - private var isNeedOpenLock = true + // 仅特定 deviceId 的部署点需要发送开锁指令 + private fun isNeedOpenLock() = App.deviceId == "4787e213-90ab-3e32-88e0-ac271a937751" private var deviceName = "" @@ -304,7 +302,7 @@ class HomeActivity : BaseActivity() { noDataWarningDialog?.dismiss() noDataWarningDialog = null - SerialPortManager.close() + runBlocking { SerialPortManager.close() } saveTaskJob?.cancel() overdueTaskJob?.cancel() EventBus.getDefault().unregister(this) @@ -325,7 +323,7 @@ class HomeActivity : BaseActivity() { private fun onShelfItemClicked(position: Int) { shelfIndex = position lifecycleScope.launch { - if (isNeedOpenLock) { + if (isNeedOpenLock()) { val openCmd = if (list[position].deviceNo in 1..5) LEFT_SHELF_OPEN_CMD else @@ -374,31 +372,17 @@ class HomeActivity : BaseActivity() { } } - private val dateTimeFormatter by lazy { SimpleDateFormat(MM_DD_EEEE__HH_MM_SS, Locale.CHINA) } - - private fun updateDateTime() { - updateRightStatus(dateTimeFormatter.format(Date())) - } - 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) // 启动无数据看门狗(每30秒检查一次) handler.postDelayed(noDataWatchdogTask, 30 * 1000L) } override fun onPause() { super.onPause() - handler.removeCallbacks(updateTask) // 停止无数据看门狗 handler.removeCallbacks(noDataWatchdogTask) } diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/activity/LogActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/activity/LogActivity.kt index 6dfb4b5..46c95fe 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/activity/LogActivity.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/activity/LogActivity.kt @@ -1,28 +1,53 @@ package com.shuwei.intelligent.shelves.activity +import android.graphics.Color import android.os.Bundle +import android.view.Gravity +import android.widget.TextView +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext 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.dp 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.serial.ProtocolConstants import com.shuwei.intelligent.shelves.utils.ext.visible +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale class LogActivity : BaseActivity() { companion object { const val IS_LOG_DIR = "isLogDir" const val LOG_DATE = "logDate" + const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss" + } + + /** + * 覆写时钟回调:左侧状态栏显示年月日星期,右侧状态栏显示时分秒 + */ + override fun onClockTick() { + val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA) + val arr = sdf.format(Date()).split("***") + updateLeftStatus(arr[0]) + updateRightStatus(arr[1]) } private lateinit var binding:ActivityLogListBinding private var isLogDir = false private var logDate = "" private val logList = mutableListOf() + private val originalLogList = mutableListOf() // 原始日志备份 private val logAdapter by lazy { LogAdapter(logList).apply { setOnItemClickListener { _, _, position -> @@ -62,24 +87,20 @@ class LogActivity : BaseActivity() { logAdapter.notifyDataSetChanged() } - if (isLogDir) { - logList.addAll(fileLogger.getLogFiles()) - } else { - logList.addAll(fileLogger.readLogsByName(logDate)) - } - binding.rvLogList.let { it.layoutManager = LinearLayoutManager(this) it.adapter = logAdapter } + + setupSearch() + setupJumpButton() + 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 (isLogDir) return@setOnClickListener + val list = logList.filter { it.contains("${ProtocolConstants.HEADER}${ProtocolConstants.CMD_TEMP_REPORT}") } if (list.isEmpty()) { toast("未找到到1001指令") return@setOnClickListener @@ -88,6 +109,95 @@ class LogActivity : BaseActivity() { logList.addAll(list) logAdapter.notifyDataSetChanged() } + + // 后台加载日志数据 + lifecycleScope.launch { + binding.progressBar.visible() + val data = withContext(Dispatchers.IO) { + if (isLogDir) fileLogger.getLogFiles() + else fileLogger.readLogsByName(logDate) + } + logList.addAll(data) + originalLogList.addAll(data) + logAdapter.notifyDataSetChanged() + binding.progressBar.gone() + setupTimeNavigation() + } + } + + // 搜索功能 + private fun setupSearch() { + binding.etSearch.addTextChangedListener(afterTextChanged = { s -> + val keyword = s?.toString() ?: "" + logList.clear() + logList.addAll(if (keyword.isEmpty()) originalLogList else originalLogList.filter { it.contains(keyword, ignoreCase = true) }) + logAdapter.notifyDataSetChanged() + }) + } + + // 快速跳转功能 + private fun setupJumpButton() { + var isAtTop = true + binding.fabJump.setOnClickListener { + if (isAtTop) { + binding.rvLogList.scrollToPosition(logList.size - 1) + } else { + binding.rvLogList.scrollToPosition(0) + } + isAtTop = !isAtTop + } + } + + // 时间导航功能 + private fun setupTimeNavigation() { + if (isLogDir) { + binding.hsvTimeNav.gone() + return + } + + val timeMap = mutableMapOf() + val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + + originalLogList.forEachIndexed { index, log -> + val timeStr = log.substringAfter("[").substringBefore("]") + try { + val date = dateFormat.parse(timeStr) + date?.let { + val hourKey = SimpleDateFormat("HH:00", Locale.getDefault()).format(it) + if (!timeMap.containsKey(hourKey)) { + timeMap[hourKey] = index + } + } + } catch (e: Exception) { + // 忽略解析失败的日志 + } + } + + if (timeMap.isEmpty()) { + binding.hsvTimeNav.gone() + return + } + + timeMap.toSortedMap().forEach { (hour, position) -> + val btn = TextView(this).apply { + text = hour + textSize = 14f + setTextColor(Color.BLACK) + setPadding(12.dp, 8.dp, 12.dp, 8.dp) + gravity = Gravity.CENTER + setBackgroundColor(Color.parseColor("#E0E0E0")) + setOnClickListener { + binding.rvLogList.scrollToPosition(position) + } + } + val params = android.widget.LinearLayout.LayoutParams( + android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, + android.widget.LinearLayout.LayoutParams.MATCH_PARENT + ).apply { + marginEnd = 8.dp + } + binding.llTimeNav.addView(btn, params) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/activity/ShelfActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/activity/ShelfActivity.kt index db3d4d1..0086339 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/activity/ShelfActivity.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/activity/ShelfActivity.kt @@ -3,8 +3,6 @@ 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 @@ -18,7 +16,7 @@ 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.serial.ProtocolConstants import com.shuwei.intelligent.shelves.adapter.SearchAdapter import com.shuwei.intelligent.shelves.base.BaseActivity import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding @@ -148,7 +146,7 @@ class ShelfActivity : BaseActivity() { //binding.tvFoodWeight.text = "0克" EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo, tempWeight)) } - sendCmd(DEVICE_INFO_CMD) + sendCmd(ProtocolConstants.DEVICE_INFO_CMD) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.getGoodsListUiState.collect { state -> @@ -305,31 +303,23 @@ class ShelfActivity : BaseActivity() { } } - private fun updateDateTime() { + /** + * 覆写时钟回调:左侧状态栏显示年月日星期,右侧状态栏显示时分秒 + */ + override fun onClockTick() { val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA) - val dateTime = sdf.format(Date()) - val arr = dateTime.split("***") + val arr = sdf.format(Date()).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") @@ -371,17 +361,17 @@ class ShelfActivity : BaseActivity() { 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}" + val zeroClearingCmd = "${ProtocolConstants.HEADER}0401${hex}${ProtocolConstants.FOOTER}" log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}") sendCmd(zeroClearingCmd) sendCmd( - if (shelfNo in 1..5) HomeActivity.LEFT_SHELF_OPEN_CMD else HomeActivity.RIGHT_SHELF_OPEN_CMD + if (shelfNo in 1..5) ProtocolConstants.LEFT_SHELF_OPEN_CMD else ProtocolConstants.RIGHT_SHELF_OPEN_CMD ) //清零 realWeight = 0 // binding.tvFoodWeight.text = "${realWeight}克" - sendCmd(DEVICE_INFO_CMD) + sendCmd(ProtocolConstants.DEVICE_INFO_CMD) startTime = System.currentTimeMillis() Loading.show(this@ShelfActivity) // window.decorView.postDelayed({ Loading.dismiss() }, 5000) diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt index cf8f133..19ec605 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt @@ -22,6 +22,7 @@ import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LIGHT_OPEN 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.dp import com.shuwei.intelligent.shelves.utils.ext.startActivity import kotlinx.coroutines.launch @@ -29,6 +30,9 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import com.shuwei.intelligent.shelves.utils.ext.setOnDoubleClickListener +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale @Suppress("DEPRECATION") open class BaseActivity : AppCompatActivity() { @@ -67,10 +71,13 @@ open class BaseActivity : AppCompatActivity() { finish() } binding.tvRightStatus.setOnDoubleClickListener { - showRightStatusPopup(binding.tvRightStatus) + if (enableRightStatusMenu) showRightStatusPopup(binding.tvRightStatus) } } + /** 是否允许右侧状态栏双击弹出菜单,默认关闭,仅 HomeActivity 开启 */ + protected open val enableRightStatusMenu: Boolean = false + fun hideStatusBar() {} fun statusBarDarkFont() { @@ -107,6 +114,26 @@ open class BaseActivity : AppCompatActivity() { onUserInactive() } + // ---- 右侧状态栏时钟 ---- + // 默认时间格式:月日星期 时:分:秒 + private val CLOCK_FORMAT = "MM月dd日 EEEE HH:mm:ss" + private val clockFormatter by lazy { SimpleDateFormat(CLOCK_FORMAT, Locale.CHINA) } + + /** + * 每秒时钟回调,默认将当前时间显示在右侧状态栏。 + * 子类可覆写以自定义格式或同时更新左侧状态栏。 + */ + open fun onClockTick() { + updateRightStatus(clockFormatter.format(Date())) + } + + private val clockTask = object : Runnable { + override fun run() { + onClockTick() + mHandler.postDelayed(this, 1000) + } + } + /** * 用户超时无操作时回调,默认空实现,子类按需覆写 */ @@ -128,14 +155,18 @@ open class BaseActivity : AppCompatActivity() { override fun onResume() { super.onResume() - // 启动定时器 + // 启动超时定时器 mHandler.postDelayed(mTimeoutRunnable, TIME_OUT) + // 启动右侧状态栏时钟 + mHandler.post(clockTask) } override fun onPause() { super.onPause() - // 取消定时器 + // 取消超时定时器 mHandler.removeCallbacks(mTimeoutRunnable) + // 停止右侧状态栏时钟 + mHandler.removeCallbacks(clockTask) } fun switchLight(isOpen: Boolean) { @@ -159,8 +190,8 @@ open class BaseActivity : AppCompatActivity() { val menuBinding = PopupRightStatusMenuBinding.inflate(LayoutInflater.from(this)) val popup = PopupWindow( menuBinding.root, - android.view.ViewGroup.LayoutParams.WRAP_CONTENT, - android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 120.dp, + 101.dp, true ) menuBinding.tvEnvSwitch.setOnClickListener { diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/serial/SerialPortManager.kt b/app/src/main/java/com/shuwei/intelligent/shelves/serial/SerialPortManager.kt index 9d7aa5c..4e9b041 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/serial/SerialPortManager.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/serial/SerialPortManager.kt @@ -69,7 +69,7 @@ object SerialPortManager { } // 关闭串口 - fun close() { + suspend fun close() { scope.cancel() serialPort?.close() } diff --git a/app/src/main/res/layout/activity_log_list.xml b/app/src/main/res/layout/activity_log_list.xml index db2d8fe..c508914 100644 --- a/app/src/main/res/layout/activity_log_list.xml +++ b/app/src/main/res/layout/activity_log_list.xml @@ -1,6 +1,11 @@ - + + @@ -39,8 +44,55 @@ android:text="查找1001指令"/> + + + + + + + - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_right_status_menu.xml b/app/src/main/res/layout/popup_right_status_menu.xml new file mode 100644 index 0000000..44366ce --- /dev/null +++ b/app/src/main/res/layout/popup_right_status_menu.xml @@ -0,0 +1,32 @@ + + + + + + + + + +