Selaa lähdekoodia

refactor(common): 优化崩溃处理器和离线命令服务

- 移除未使用的 FileNotFoundException 导入
- 修复 CrashHandler 单例模式的双重检查锁定实现
- 使用 try-with-resources 语句简化文件流管理
- 添加详细的崩溃日志写入错误日志记录

- 重构 OfflineCmdServiceHelper 使用 CopyOnWriteArrayList 替代
- 提取各页面语音命令为常量列表避免重复创建对象
- 简化页面语音命令注册和移除方法的实现逻辑

- 重构 TaskListActivity 语音命令分发逻辑
- 使用映射表和解析函数替代大量重复的条件判断
- 提取语音命令常量配置便于维护

- 在 WelcomeVM 中使用 try-with-resources 管理资源
- 修改 NetworkModule 构造函数参数命名规范
- 修复 AppManager logout 方法中的循环清理逻辑
- 从本地属性文件读取仓库认证凭据避免提交到版本控制
徐勤民 1 päivä sitten
vanhempi
commit
c77b59cee4

+ 5 - 10
app/src/main/java/com/nova/brain/glass/common/CrashHandler.java

@@ -6,7 +6,6 @@ import android.os.SystemClock;
 import android.util.Log;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -51,7 +50,9 @@ public class CrashHandler implements Thread.UncaughtExceptionHandler {
     public static CrashHandler getInstance() {
         if (mCrashHandler == null) {
             synchronized (CrashHandler.class) {
-                mCrashHandler = new CrashHandler();
+                if (mCrashHandler == null) {
+                    mCrashHandler = new CrashHandler();
+                }
             }
         }
         return mCrashHandler;
@@ -135,17 +136,11 @@ public class CrashHandler implements Thread.UncaughtExceptionHandler {
         if (!dir.exists()) {
             dir.mkdir();
         }
-        FileOutputStream fos = null;
-        try {
-            fos = new FileOutputStream(path + fileName, true);
+        try (FileOutputStream fos = new FileOutputStream(path + fileName, true)) {
             fos.write(text.getBytes());
             fos.flush();
-            fos.close();
-
-        } catch (FileNotFoundException e) {
-            e.printStackTrace();
         } catch (IOException e) {
-            e.printStackTrace();
+            Log.e(TAG, "写入崩溃日志失败: " + fileName, e);
         }
         return fileName;
     }

+ 82 - 228
app/src/main/java/com/nova/brain/glass/helper/OfflineCmdServiceHelper.kt

@@ -1,92 +1,81 @@
 package com.nova.brain.glass.helper
 
-import com.luck.picture.lib.config.PictureSelectionConfig.listener
 import com.rokid.security.glass3.open.sdk.GlassSdk
 import com.rokid.security.glass3.sdk.base.data.offlineCmd.bean.VoiceAction
 import com.rokid.security.glass3.sdk.base.data.offlineCmd.listener.IVoiceCallback
 import com.rokid.security.system.server.offlineCmd.IOfflineCmdService
 import com.xuqm.base.common.LogHelper
-import java.util.concurrent.Executors
+import java.util.concurrent.CopyOnWriteArrayList
 
 data class OfflineCmdBean(val text: String, val pinyin: String)
 
 object OfflineCmdServiceHelper {
-    private var listenerList = mutableListOf<OfflineCmdListener>()
-    private val initExecutor = Executors.newSingleThreadExecutor()
-    @Volatile
-    private var initialized = false
-    @Volatile
-    private var initializing = false
-
-    private val list = mutableListOf<OfflineCmdBean>().apply {
-        // 首页
-        add(OfflineCmdBean("任务列表", "ren wu lie biao"))
-        add(OfflineCmdBean("查看任务", "cha kan ren wu"))
-        add(OfflineCmdBean("查看任务列表", "cha kan ren wu lie biao"))
-        // 任务列表
-        add(OfflineCmdBean("下一页", "xia yi ye"))
-        add(OfflineCmdBean("上一页", "shang yi ye"))
-
-        add(OfflineCmdBean("退出", "tui chu"))
-        add(OfflineCmdBean("返回", "fan hui"))
-
-        add(OfflineCmdBean("同意", "tong yi"))
-        add(OfflineCmdBean("驳回", "bo hui"))
-        add(OfflineCmdBean("通过", "tong guo"))
-        add(OfflineCmdBean("合格", "he ge"))
-        add(OfflineCmdBean("不合格", "bu he ge"))
-        add(OfflineCmdBean("取消", "qu xiao"))
-
-        add(OfflineCmdBean("查看第一项任务", "cha kan di yi xiang ren wu"))
-        add(OfflineCmdBean("第一个", "di yi ge"))
-
-        add(OfflineCmdBean("查看第二项任务", "cha kan di er xiang ren wu"))
-        add(OfflineCmdBean("第二个", "di er ge"))
-
-        add(OfflineCmdBean("查看第三项任务", "cha kan di san xiang ren wu"))
-        add(OfflineCmdBean("第三个", "di san ge"))
-
-        add(OfflineCmdBean("查看第四项任务", "cha kan di si xiang ren wu"))
-        add(OfflineCmdBean("第四个", "di si ge"))
-
-        add(OfflineCmdBean("查看第五项任务", "cha kan di wu xiang ren wu"))
-        add(OfflineCmdBean("第五个", "di wu ge"))
-
-        add(OfflineCmdBean("查看第六项任务", "cha kan di liu xiang ren wu"))
-        add(OfflineCmdBean("第六个", "di liu ge"))
-
-        add(OfflineCmdBean("查看第七项任务", "cha kan di qi xiang ren wu"))
-        add(OfflineCmdBean("第七个", "di qi ge"))
-
-        add(OfflineCmdBean("查看第八项任务", "cha kan di ba xiang ren wu"))
-        add(OfflineCmdBean("第八个", "di ba ge"))
-
-        add(OfflineCmdBean("查看第九项任务", "cha kan di jiu xiang ren wu"))
-        add(OfflineCmdBean("第九个", "di jiu ge"))
-
-        add(OfflineCmdBean("查看第十项任务", "cha kan di shi xiang ren wu"))
-        add(OfflineCmdBean("第十个", "di shi ge"))
-
-        //喷涂
-        add(OfflineCmdBean("开始", "kai shi"))
-        add(OfflineCmdBean("开始任务", "kai shi ren wu"))
-        add(OfflineCmdBean("重拍", "chong pai"))
-        add(OfflineCmdBean("继续拍摄", "ji xu pai she"))
-        add(OfflineCmdBean("人工更正结果", "ren gong geng zheng jie guo"))
-        add(OfflineCmdBean("人工更正", "ren gong geng zheng"))
-        add(OfflineCmdBean("更正", "geng zheng"))
-        add(OfflineCmdBean("结束任务", "jie shu ren wu"))
-        add(OfflineCmdBean("补充照片", "bu chong zhao pian"))
-        add(OfflineCmdBean("确认提交", "que ren ti jiao"))
-        //
-    }
+    // CopyOnWriteArrayList:遍历时不需要加锁,add/remove 不会引发 ConcurrentModificationException
+    private val listenerList = CopyOnWriteArrayList<OfflineCmdListener>()
 
-    // 所有页面通用关键词
+    // 所有页面通用关键词(常量,只分配一次)
     private val COMMON_CMDS = listOf(
         OfflineCmdBean("退出", "tui chu"),
         OfflineCmdBean("返回", "fan hui")
     )
 
+    // 各页面独有命令列表(常量,避免每次 onResume/onPause 重复创建对象)
+    private val CMDS_TASK_LIST = listOf(
+        OfflineCmdBean("下一页", "xia yi ye"),
+        OfflineCmdBean("上一页", "shang yi ye"),
+        OfflineCmdBean("查看第一项任务", "cha kan di yi xiang ren wu"),
+        OfflineCmdBean("第一个", "di yi ge"),
+        OfflineCmdBean("查看第二项任务", "cha kan di er xiang ren wu"),
+        OfflineCmdBean("第二个", "di er ge"),
+        OfflineCmdBean("查看第三项任务", "cha kan di san xiang ren wu"),
+        OfflineCmdBean("第三个", "di san ge"),
+        OfflineCmdBean("查看第四项任务", "cha kan di si xiang ren wu"),
+        OfflineCmdBean("第四个", "di si ge"),
+        OfflineCmdBean("查看第五项任务", "cha kan di wu xiang ren wu"),
+        OfflineCmdBean("第五个", "di wu ge"),
+        OfflineCmdBean("查看第六项任务", "cha kan di liu xiang ren wu"),
+        OfflineCmdBean("第六个", "di liu ge")
+    )
+    private val CMDS_INSPECTION = listOf(
+        OfflineCmdBean("驳回", "bo hui"),
+        OfflineCmdBean("通过", "tong guo"),
+        OfflineCmdBean("同意", "tong yi")
+    )
+    private val CMDS_SPRAYING = listOf(
+        OfflineCmdBean("开始任务", "kai shi ren wu")
+    )
+    private val CMDS_SPRAYING_FINISH = listOf(
+        OfflineCmdBean("补充照片", "bu chong zhao pian"),
+        OfflineCmdBean("确认提交", "que ren ti jiao")
+    )
+    private val CMDS_SPRAYING_MANUAL_RESULT = listOf(
+        OfflineCmdBean("合格", "he ge"),
+        OfflineCmdBean("不合格", "bu he ge"),
+        OfflineCmdBean("取消", "qu xiao")
+    )
+    private val CMDS_SPRAYING_OCR = listOf(
+        OfflineCmdBean("重拍", "chong pai")
+    )
+    private val CMDS_SPRAYING_RESULT = listOf(
+        OfflineCmdBean("重拍", "chong pai"),
+        OfflineCmdBean("继续拍摄", "ji xu pai she"),
+        OfflineCmdBean("人工更正结果", "ren gong geng zheng jie guo"),
+        OfflineCmdBean("更正", "geng zheng"),
+        OfflineCmdBean("结束任务", "jie shu ren wu")
+    )
+    private val CMDS_INSPECTION_RESULT = listOf(
+        OfflineCmdBean("重拍照", "chong pai zhao"),
+        OfflineCmdBean("结束任务", "jie shu ren wu"),
+        OfflineCmdBean("继续任务", "ji xu ren wu")
+    )
+    private val CMDS_INSPECTION_MISSING = listOf(
+        OfflineCmdBean("补充单证", "bu chong dan zheng"),
+        OfflineCmdBean("继续提交", "ji xu ti jiao")
+    )
+    private val CMDS_INSPECTION_COMPLETE = listOf(
+        OfflineCmdBean("完成任务", "wan cheng ren wu")
+    )
+
     private var service: IOfflineCmdService? = null
 
     private fun registerBeans(beans: List<OfflineCmdBean>) {
@@ -131,24 +120,8 @@ object OfflineCmdServiceHelper {
         this.listenerList.remove(listener)
     }
 
-    fun addListenerList(){
-        // 任务列表独有关键词(通用关键词已在 init 注册,无需重复)
-        registerBeans(listOf(
-            OfflineCmdBean("下一页", "xia yi ye"),
-            OfflineCmdBean("上一页", "shang yi ye"),
-            OfflineCmdBean("查看第一项任务", "cha kan di yi xiang ren wu"),
-            OfflineCmdBean("第一个", "di yi ge"),
-            OfflineCmdBean("查看第二项任务", "cha kan di er xiang ren wu"),
-            OfflineCmdBean("第二个", "di er ge"),
-            OfflineCmdBean("查看第三项任务", "cha kan di san xiang ren wu"),
-            OfflineCmdBean("第三个", "di san ge"),
-            OfflineCmdBean("查看第四项任务", "cha kan di si xiang ren wu"),
-            OfflineCmdBean("第四个", "di si ge"),
-            OfflineCmdBean("查看第五项任务", "cha kan di wu xiang ren wu"),
-            OfflineCmdBean("第五个", "di wu ge"),
-            OfflineCmdBean("查看第六项任务", "cha kan di liu xiang ren wu"),
-            OfflineCmdBean("第六个", "di liu ge")
-        ))
+    fun addListenerList() {
+        registerBeans(CMDS_TASK_LIST)
     }
 
     fun removeAll(){
@@ -158,167 +131,48 @@ object OfflineCmdServiceHelper {
     // addListenerFo: 无独有关键词,通用关键词已在 init 注册
     fun addListenerFo(){ }
 
-    fun addListenerInspection(){
-        // 巡检页独有关键词
-        registerBeans(listOf(
-            OfflineCmdBean("驳回", "bo hui"),
-            OfflineCmdBean("通过", "tong guo"),
-            OfflineCmdBean("同意", "tong yi")
-        ))
-    }
+    fun addListenerInspection() = registerBeans(CMDS_INSPECTION)
 
-    fun addListenerSpraying(){
-        // 喷涂页独有关键词
-        registerBeans(listOf(
-            OfflineCmdBean("开始任务", "kai shi ren wu")
-        ))
-    }
+    fun addListenerSpraying() = registerBeans(CMDS_SPRAYING)
 
-    fun addListenerSprayingFinish(){
-        // 喷涂完成页独有关键词
-        registerBeans(listOf(
-            OfflineCmdBean("补充照片", "bu chong zhao pian"),
-            OfflineCmdBean("确认提交", "que ren ti jiao")
-        ))
-    }
+    fun addListenerSprayingFinish() = registerBeans(CMDS_SPRAYING_FINISH)
 
-    fun addListenerSprayingManualResulth(){
-        // 喷涂人工更正页独有关键词
-        registerBeans(listOf(
-            OfflineCmdBean("合格", "he ge"),
-            OfflineCmdBean("不合格", "bu he ge"),
-            OfflineCmdBean("取消", "qu xiao")
-        ))
-    }
+    fun addListenerSprayingManualResulth() = registerBeans(CMDS_SPRAYING_MANUAL_RESULT)
 
-    fun addListenerSprayingOCR(){
-        // 喷涂OCR页独有关键词
-        registerBeans(listOf(
-            OfflineCmdBean("重拍", "chong pai")
-        ))
-    }
+    fun addListenerSprayingOCR() = registerBeans(CMDS_SPRAYING_OCR)
 
-    fun addListenerSprayingResult(){
-        // 喷涂结果页独有关键词
-        registerBeans(listOf(
-            OfflineCmdBean("重拍", "chong pai"),
-            OfflineCmdBean("继续拍摄", "ji xu pai she"),
-            OfflineCmdBean("人工更正结果", "ren gong geng zheng jie guo"),
-            OfflineCmdBean("更正", "geng zheng"),
-            OfflineCmdBean("结束任务", "jie shu ren wu")
-        ))
-    }
+    fun addListenerSprayingResult() = registerBeans(CMDS_SPRAYING_RESULT)
 
     // ---- 各页面离开时移除独有关键词(退出/返回为公共关键词,不移除)----
 
-    fun removeListenerList(){
-        removeBeans(listOf(
-            OfflineCmdBean("下一页", "xia yi ye"),
-            OfflineCmdBean("上一页", "shang yi ye"),
-            OfflineCmdBean("查看第一项任务", "cha kan di yi xiang ren wu"),
-            OfflineCmdBean("第一个", "di yi ge"),
-            OfflineCmdBean("查看第二项任务", "cha kan di er xiang ren wu"),
-            OfflineCmdBean("第二个", "di er ge"),
-            OfflineCmdBean("查看第三项任务", "cha kan di san xiang ren wu"),
-            OfflineCmdBean("第三个", "di san ge"),
-            OfflineCmdBean("查看第四项任务", "cha kan di si xiang ren wu"),
-            OfflineCmdBean("第四个", "di si ge"),
-            OfflineCmdBean("查看第五项任务", "cha kan di wu xiang ren wu"),
-            OfflineCmdBean("第五个", "di wu ge"),
-            OfflineCmdBean("查看第六项任务", "cha kan di liu xiang ren wu"),
-            OfflineCmdBean("第六个", "di liu ge")
-        ))
-    }
+    fun removeListenerList() = removeBeans(CMDS_TASK_LIST)
 
     // addListenerFo 无独有关键词,无需对应 remove 方法
 
-    fun removeListenerInspection(){
-        removeBeans(listOf(
-            OfflineCmdBean("驳回", "bo hui"),
-            OfflineCmdBean("通过", "tong guo"),
-            OfflineCmdBean("同意", "tong yi")
-        ))
-    }
+    fun removeListenerInspection() = removeBeans(CMDS_INSPECTION)
 
-    fun removeListenerSpraying(){
-        removeBeans(listOf(
-            OfflineCmdBean("开始任务", "kai shi ren wu")
-        ))
-    }
+    fun removeListenerSpraying() = removeBeans(CMDS_SPRAYING)
 
-    fun removeListenerSprayingFinish(){
-        removeBeans(listOf(
-            OfflineCmdBean("补充照片", "bu chong zhao pian"),
-            OfflineCmdBean("确认提交", "que ren ti jiao")
-        ))
-    }
+    fun removeListenerSprayingFinish() = removeBeans(CMDS_SPRAYING_FINISH)
 
-    fun removeListenerSprayingManualResulth(){
-        removeBeans(listOf(
-            OfflineCmdBean("合格", "he ge"),
-            OfflineCmdBean("不合格", "bu he ge"),
-            OfflineCmdBean("取消", "qu xiao")
-        ))
-    }
+    fun removeListenerSprayingManualResulth() = removeBeans(CMDS_SPRAYING_MANUAL_RESULT)
 
-    fun removeListenerSprayingOCR(){
-        removeBeans(listOf(
-            OfflineCmdBean("重拍", "chong pai")
-        ))
-    }
+    fun removeListenerSprayingOCR() = removeBeans(CMDS_SPRAYING_OCR)
 
-    fun removeListenerSprayingResult(){
-        removeBeans(listOf(
-            OfflineCmdBean("重拍", "chong pai"),
-            OfflineCmdBean("继续拍摄", "ji xu pai she"),
-            OfflineCmdBean("人工更正结果", "ren gong geng zheng jie guo"),
-            OfflineCmdBean("更正", "geng zheng"),
-            OfflineCmdBean("结束任务", "jie shu ren wu")
-        ))
-    }
+    fun removeListenerSprayingResult() = removeBeans(CMDS_SPRAYING_RESULT)
 
     // ---- Inspection 页面关键词 ----
 
-    fun addListenerInspectionResult(){
-        registerBeans(listOf(
-            OfflineCmdBean("重拍照", "chong pai zhao"),
-            OfflineCmdBean("结束任务", "jie shu ren wu"),
-            OfflineCmdBean("继续任务", "ji xu ren wu")
-        ))
-    }
+    fun addListenerInspectionResult() = registerBeans(CMDS_INSPECTION_RESULT)
 
-    fun removeListenerInspectionResult(){
-        removeBeans(listOf(
-            OfflineCmdBean("重拍照", "chong pai zhao"),
-            OfflineCmdBean("结束任务", "jie shu ren wu"),
-            OfflineCmdBean("继续任务", "ji xu ren wu")
-        ))
-    }
+    fun removeListenerInspectionResult() = removeBeans(CMDS_INSPECTION_RESULT)
 
-    fun addListenerInspectionMissing(){
-        registerBeans(listOf(
-            OfflineCmdBean("补充单证", "bu chong dan zheng"),
-            OfflineCmdBean("继续提交", "ji xu ti jiao")
-        ))
-    }
+    fun addListenerInspectionMissing() = registerBeans(CMDS_INSPECTION_MISSING)
 
-    fun removeListenerInspectionMissing(){
-        removeBeans(listOf(
-            OfflineCmdBean("补充单证", "bu chong dan zheng"),
-            OfflineCmdBean("继续提交", "ji xu ti jiao")
-        ))
-    }
+    fun removeListenerInspectionMissing() = removeBeans(CMDS_INSPECTION_MISSING)
 
-    fun addListenerInspectionComplete(){
-        registerBeans(listOf(
-            OfflineCmdBean("完成任务", "wan cheng ren wu")
-        ))
-    }
+    fun addListenerInspectionComplete() = registerBeans(CMDS_INSPECTION_COMPLETE)
 
-    fun removeListenerInspectionComplete(){
-        removeBeans(listOf(
-            OfflineCmdBean("完成任务", "wan cheng ren wu")
-        ))
-    }
+    fun removeListenerInspectionComplete() = removeBeans(CMDS_INSPECTION_COMPLETE)
 
 }

+ 51 - 55
app/src/main/java/com/nova/brain/glass/ui/TaskListActivity.kt

@@ -24,6 +24,33 @@ class TaskListActivity :
     override fun fullscreen(): Boolean = true
     private var pageStartPosition = 0
 
+    companion object {
+        // 序数词到索引的映射
+        private val ORDINAL_INDEX = mapOf(
+            "第一" to 0, "第二" to 1, "第三" to 2, "第四" to 3,
+            "第五" to 4, "第六" to 5, "第七" to 6, "第八" to 7,
+            "第九" to 8, "第十" to 9
+        )
+        // 单字序数词(口语化)到索引的映射
+        private val SHORT_ORDINAL_INDEX = mapOf(
+            "第一个" to 0, "第一条" to 0, "第一项" to 0,
+            "第二个" to 1, "第二条" to 1, "第二项" to 1,
+            "第三个" to 2, "第三条" to 2, "第三项" to 2,
+            "第四个" to 3, "第四条" to 3, "第四项" to 3,
+            "第五个" to 4, "第五条" to 4, "第五项" to 4,
+            "第六个" to 5, "第六条" to 5, "第六项" to 5,
+            "第七个" to 6, "第七条" to 6, "第七项" to 6,
+            "第八个" to 7, "第八条" to 7, "第八项" to 7,
+            "第九个" to 8, "第九条" to 8, "第九项" to 8,
+            "第十个" to 9, "第十条" to 9, "第十项" to 9
+        )
+        private val TASK_PREFIXES = listOf("查看", "打开", "处理")
+        private val TASK_SUFFIXES = listOf("任务", "条任务", "项任务", "个任务")
+        private val NEXT_PAGE_CMDS = setOf("下一页", "翻页", "查看下一页", "继续翻页", "继续下一页")
+        private val PREV_PAGE_CMDS = setOf("上一页", "查看上一页", "继续上一页")
+        private val EXIT_CMDS = setOf("退出", "返回", "退回")
+    }
+
     override fun initView(savedInstanceState: Bundle?) {
         super.initView(savedInstanceState)
         window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@@ -48,65 +75,34 @@ class TaskListActivity :
 
     private val offlineCmdListener = object : OfflineCmdListener {
         override fun onOfflineCmd(cmd: String) {
-            runOnUiThread {
-                when (cmd) {
-                    "退出","返回","退回"->{
-                        finish()
-                    }
-                    "下一页", "翻页", "查看下一页", "继续翻页", "继续下一页" -> {
-                        "--------------->".log()
-                        toNext()
-                    }
-                    "上一页", "查看上一页", "继续上一页" -> {
-                        toPre()
-                    }
-                    "查看第一条任务", "查看第一项任务", "查看第一个任务",
-                    "第一个", "第一条", "第一项",
-                    "打开第一条任务", "打开第一项任务", "打开第一个任务",
-                    "处理第一条任务", "处理第一项任务", "处理第一个任务" -> {
-                        openVisibleTask(0)
-                    }
-                    "查看第二条任务", "查看第二项任务", "第二个", "第二条", "第二项",
-                    "打开第二条任务", "打开第二项任务", "处理第二条任务", "处理第二项任务" -> {
-                        openVisibleTask(1)
-                    }
-                    "查看第三条任务", "查看第三项任务", "第三个", "第三条", "第三项",
-                    "打开第三条任务", "打开第三项任务", "处理第三条任务", "处理第三项任务" -> {
-                        openVisibleTask(2)
-                    }
-                    "查看第四条任务", "查看第四项任务", "第四个", "第四条", "第四项",
-                    "打开第四条任务", "打开第四项任务", "处理第四条任务", "处理第四项任务" -> {
-                        openVisibleTask(3)
-                    }
-                    "查看第五条任务", "查看第五项任务", "第五个", "第五条", "第五项",
-                    "打开第五条任务", "打开第五项任务", "处理第五条任务", "处理第五项任务" -> {
-                        openVisibleTask(4)
-                    }
-                    "查看第六条任务", "查看第六项任务", "第六个", "第六条", "第六项",
-                    "打开第六条任务", "打开第六项任务", "处理第六条任务", "处理第六项任务" -> {
-                        openVisibleTask(5)
-                    }
-                    "查看第七条任务", "查看第七项任务", "第七个", "第七条", "第七项",
-                    "打开第七条任务", "打开第七项任务", "处理第七条任务", "处理第七项任务" -> {
-                        openVisibleTask(6)
-                    }
-                    "查看第八条任务", "查看第八项任务", "第八个", "第八条", "第八项",
-                    "打开第八条任务", "打开第八项任务", "处理第八条任务", "处理第八项任务" -> {
-                        openVisibleTask(7)
-                    }
-                    "查看第九条任务", "查看第九项任务", "第九个", "第九条", "第九项",
-                    "打开第九条任务", "打开第九项任务", "处理第九条任务", "处理第九项任务" -> {
-                        openVisibleTask(8)
-                    }
-                    "查看第十条任务", "查看第十项任务", "第十个", "第十条", "第十项",
-                    "打开第十条任务", "打开第十项任务", "处理第十条任务", "处理第十项任务" -> {
-                        openVisibleTask(9)
-                    }
-                }
+            runOnUiThread { dispatchVoiceCmd(cmd) }
+        }
+    }
+
+    private fun dispatchVoiceCmd(cmd: String) {
+        when {
+            cmd in EXIT_CMDS -> finish()
+            cmd in NEXT_PAGE_CMDS -> {
+                "--------------->".log()
+                toNext()
             }
+            cmd in PREV_PAGE_CMDS -> toPre()
+            else -> resolveTaskIndex(cmd)?.let { openVisibleTask(it) }
         }
     }
 
+    /**
+     * 将语音命令解析为任务序号(0-based),无法识别则返回 null。
+     * 支持格式:「第N个/条/项」、「查看/打开/处理 + 第N条/项/个 + 任务(可省)」
+     */
+    private fun resolveTaskIndex(cmd: String): Int? {
+        SHORT_ORDINAL_INDEX[cmd]?.let { return it }
+        val prefix = TASK_PREFIXES.firstOrNull { cmd.startsWith(it) } ?: return null
+        val body = cmd.removePrefix(prefix)
+        val ordinalEntry = ORDINAL_INDEX.entries.firstOrNull { body.startsWith(it.key) } ?: return null
+        return ordinalEntry.value
+    }
+
     private fun toPre() {
         val layoutManager = binding.baseRecyclerView.layoutManager as? LinearLayoutManager
         if (layoutManager != null) {

+ 11 - 10
app/src/main/java/com/nova/brain/glass/viewmodel/WelcomeVM.kt

@@ -42,18 +42,19 @@ class WelcomeVM : BaseViewModel() {
             .subscribeOn(Schedulers.io())
             .subscribe({ body ->
                 val sb = StringBuilder("SSE 流式响应:\n")
-                try {
-                    val reader = body.charStream().buffered()
-                    var line: String?
-                    while (reader.readLine().also { line = it } != null) {
-                        val l = line!!
-                        if (l.isNotEmpty()) {
-                            sb.appendLine(l)
-                            result.postValue(sb.toString())
+                body.charStream().buffered().use { reader ->
+                    try {
+                        var line: String?
+                        while (reader.readLine().also { line = it } != null) {
+                            val l = line!!
+                            if (l.isNotEmpty()) {
+                                sb.appendLine(l)
+                                result.postValue(sb.toString())
+                            }
                         }
+                    } catch (e: Exception) {
+                        result.postValue("SSE 读取异常: ${e.message}")
                     }
-                } catch (e: Exception) {
-                    result.postValue("SSE 读取异常: ${e.message}")
                 }
             }, { e ->
                 result.postValue("SSE 失败: ${e.message}")

+ 8 - 2
base/build.gradle

@@ -85,8 +85,14 @@ afterEvaluate {
                 allowInsecureProtocol true
                 url("http://xuqinmin.com.cn:8081/repository/android-releases/")
                 credentials {
-                    username = "admin"
-                    password = "xuqinmin1022"
+                    // 从 local.properties 读取,避免凭据提交到版本控制
+                    def props = new Properties()
+                    def localPropertiesFile = rootProject.file("local.properties")
+                    if (localPropertiesFile.exists()) {
+                        localPropertiesFile.withInputStream { props.load(it) }
+                    }
+                    username = props.getProperty("nexus.username", "")
+                    password = props.getProperty("nexus.password", "")
                 }
             }
         }

+ 2 - 5
base/src/main/java/com/xuqm/base/common/AppManager.java

@@ -65,13 +65,10 @@ public class AppManager {
 
     /**
      * finish最后一个之外的所有页面
-     *
-     * @return
      */
     public void logout() {
-        if (activityStack.size() < 1)
-            return;
-        for (int i = 0; i < activityStack.size() - 1; i++) {
+        // 原 for 循环在 popActivity 后 size 动态缩小,导致只清理约一半;改为 while 确保全部清理
+        while (activityStack.size() > 1) {
             Activity activity = activityStack.firstElement();
             if (activity == null) break;
             popActivity(activity);

+ 7 - 9
base/src/main/java/com/xuqm/base/di/module/NetworkModule.java

@@ -25,16 +25,16 @@ import retrofit2.converter.gson.GsonConverterFactory;
 
 @Module
 public class NetworkModule {
-    private String BaseUrl = "https://xuqinmin.com/";
-    private final List<Interceptor> interceptor = new ArrayList<>();
+    private final String BaseUrl;
+    private final List<Interceptor> interceptor;
 
     public NetworkModule() {
+        this("https://xuqinmin.com/");
     }
 
-    public NetworkModule(String baseUrl, Interceptor... interceptor) {
+    public NetworkModule(String baseUrl, Interceptor... interceptors) {
         BaseUrl = baseUrl;
-        this.interceptor.clear();
-        this.interceptor.addAll(Arrays.asList(interceptor));
+        interceptor = interceptors.length > 0 ? Arrays.asList(interceptors) : new ArrayList<>();
     }
 
     @Provides
@@ -59,10 +59,8 @@ public class NetworkModule {
                 .readTimeout(60, TimeUnit.SECONDS)
                 .writeTimeout(60, TimeUnit.SECONDS);
 //        builder.addNetworkInterceptor(httpLoggingInterceptor);
-        if (0 != interceptor.size()) {
-            for (Interceptor interceptor1 : this.interceptor) {
-                builder.addInterceptor(interceptor1);
-            }
+        for (Interceptor interceptor1 : this.interceptor) {
+            builder.addInterceptor(interceptor1);
         }
 
         return builder.cookieJar(persistentCookieJar)

+ 1 - 1
base/src/main/java/com/xuqm/base/dialog/loading/LoadingDialog.java

@@ -119,7 +119,7 @@ public class LoadingDialog {
                 try {
                     mDialog.dismiss();
                 } catch (Exception e) {
-                    // TODO: handle exception
+                    LogHelper.e(LoadingDialog.class.getSimpleName(), e);
                 }
             }
         }