Author SHA1 Message Date
mazengfeiandClaude Sonnet 4.6 e8735db27b fix: 优化人脸采集日志及提示界面交互
- LoginByFaceActivity: 新增人脸采集关键节点调试日志
- PlcSettingActivity: 添加点击提示区域关闭提示布局功能,格式化代码
- activity_plc_setting.xml: 给提示 TextView 添加 id,字体调整为 32sp

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-06-01 18:10:16 +08:00
mazengfei e1e4bc5123 feat(plc): 添加餐盘平台复位功能和无餐盘提示界面
- 在PLC设置页面添加餐盘平台复位按钮
- 实现平台复位功能,先执行平台上升再返回营业状态
- 添加无餐盘提示界面,包含图片和文字提示
- 修改启动方法支持显示提示界面参数传递
- 在人脸识别登录页面检测到无餐盘时自动显示提示界面
2026-04-16 18:06:47 +08:00
mazengfei 18f45e7821 修改uat环境域名端口 2026-04-15 16:53:45 +08:00
mazengfei 1826ac0f60 fix(login): 修复人脸登录后餐盘状态判断逻辑
- 当检测到餐盘已空时跳转到餐盘设置页面
- 当检测到剩余餐盘时继续执行初始化操作
- 添加餐盘状态检查的日志记录功能
2026-04-14 15:02:36 +08:00
mazengfei d81381ae6f fix(login): 修复人脸识别登录后的流程处理
- 移除出盘完成日志的重复打印
- 添加餐盘已空时的页面跳转功能
2026-04-14 14:59:09 +08:00
mazengfei f35411a8a0 refactor(login): 优化人脸识别登录页面的串口操作实现
- 使用正确的实例引用调用PlcHelper的openSerialPort方法
- 在activity销毁时添加串口资源释放操作
- 改进代码格式化以提高可读性
- 确保串口资源在页面关闭时得到正确清理
2026-04-14 14:13:54 +08:00
mazengfei 93b46e38d4 refactor(login): 优化餐盘出盘及剩余状态提示逻辑
- 出盘后改为直接查询剩余餐盘状态以决定提示音播放
- result为false时播放无盘提示音并跳转首页
- result为true时播放出盘成功音效,继续查询剩余餐盘
- 新增checkRemainingPlate方法,查询餐盘状态并播放相应提示音
- 错误时跳过播放,确保流程顺畅跳转首页
- 删除之前注释的旧版餐盘检测逻辑代码
2026-04-14 14:00:17 +08:00
mazengfei ba1a364ee8 临时版本 2026-04-14 11:50:39 +08:00
mazengfei 50b1023a44 fix(login): 修复人脸识别登录界面返回时串口连接超时问题
- 每次回到识别界面时重新打开串口连接
- 防止PLC SDK内部连接超时导致出盘失败
- 添加串口开启成功和失败的日志记录
2026-04-13 18:17:21 +08:00
mazengfeiandClaude Sonnet 4.6 385ada4a0d fix(face): 修复短时间内再次识别无法触发的问题
新增 RecognizeViewModel.resetFaceState(),在 resetRecognizeState() 时
同时清空粘性 LiveData 和 FaceHelper 内部 recognizeInfoMap,
防止旧 trackId 状态残留导致识别流程无法重新触发。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 15:57:50 +08:00
mazengfei ae708eeeed 优化 2026-04-08 15:41:42 +08:00
mazengfeiandClaude Sonnet 4.6 2886e7d4a3 fix: 优化等待弹窗交互、人脸数据刷新及相机暂停逻辑
- BaseActivity: 等待对话框允许点击外部区域关闭
- LoginByFaceActivity: 恢复暂停相机时的预览帧处理
- PlcSettingActivity: 人脸数据更新成功后刷新人脸列表

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 10:41:25 +08:00
lvmeng 66a0c399ec feat(face): 添加人脸识别距离检测和环境切换时的人脸数据重置功能
- 在FaceHelper中添加人脸宽度小于200像素时的过滤逻辑,避免远距离识别人脸
- 注释掉LoginByFaceActivity中的重复识别状态判断逻辑
- 在PlcSettingActivity中添加协程支持用于异步处理人脸数据清除操作
- 实现环境切换时清空所有人脸数据并重新获取用户人脸缓存的功能
- 修改UserViewModel中回调逻辑,当第一页无数据时返回false而非true
2026-04-02 19:11:51 +08:00
lvmeng 4cbe318ab2 feat(plc): 添加环境切换功能
- 在PLC设置页面增加环境切换按钮
- 集成EnvSwitchDialog对话框组件
- 实现切换环境按钮点击事件处理
- 配置环境切换界面布局和样式
- 添加环境相关的UI元素和交互逻辑
2026-03-31 18:12:52 +08:00
lvmengandClaude Sonnet 4.6 1cbad71eaf feat(dialog): 添加环境切换对话框并修复包名引用
- 新增 BaseDialog 抽象基类,统一对话框样式和行为
- 新增 EnvSwitchDialog 支持 TEST/UAT/PROD 三环境切换
- 修复 BaseDialog 和 EnvSwitchDialog 的包名(com.shuwei.intelligent.shelves → com.sw.platecabinet)
- 新增 View.hideKeyboard() 扩展函数到 CommonExt.kt
- 重构 GlobalData 环境 URL 常量命名(TEST_URL/PRO_URL → TEST_BASE_URL/UAT_BASE_URL/PROD_BASE_URL)
- MyApp 启动时从 SpTool 读取已保存的环境配置
- SpTool 新增 baseUrl 持久化支持
- 修正 drawable 资源颜色引用为硬编码值

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 18:12:22 +08:00
mazengfei 0a193ed84e refactor(base): 优化协程作用域和空安全处理
- 将自定义CoroutineScope替换为lifecycleScope以避免内存泄漏
- 添加runCatching包装器处理潜在异常并记录错误日志
- 使用安全调用操作符替代强制调用防止空指针异常
- 在CountDownTimer中正确管理生命周期以避免内存泄漏
- 修复设备信息获取中的数组越界风险
- 统一异步操作的线程切换方式
- 保护Fragment中的_binding访问权限
- 改进相机助手对象引用的安全性检查
2026-03-31 17:12:14 +08:00
mazengfei df9212d2dd 完善吐盘逻辑,调整设置界面,隐藏无关设置 2026-03-31 10:38:45 +08:00
mazengfei cc16fcecff feat(ui): 添加PLC控制设置界面
- 新增PLC设置界面布局文件,包含连接控制、出盘操作、平台控制等功能模块
- 在AndroidManifest.xml中注册PlcSettingActivity组件
- 为初始化和人脸识别登录页面添加右键双击打开PLC设置的功能
- 实现完整的PLC设置活动类,集成串口通信、自动模式、出盘、平台升降等控制功能
- 添加参数设置输入验证和状态信息实时显示功能
- 实现故障复位和状态刷新等关键操作接口
2026-03-30 18:17:49 +08:00
mazengfei 83f9654987 refactor(build): 排除 lib_face 中的 android-serialport 依赖
- 在 lib_face 模块中排除 com.licheedev:android-serialport 依赖
- 防止依赖冲突和重复打包问题
2026-03-30 15:04:41 +08:00
mazengfei 536b1b98b4 feat(login): 集成PLC控制系统实现餐盘柜硬件交互
- 添加plc_aar_104.aar依赖库支持PLC硬件通信
- 新增PlcCallback、PlcHelper和PlcStatus工具类封装PLC操作接口
- 实现PLC状态查询功能替换原有的模拟检测逻辑
- 集成出盘操作流程,支持真实硬件控制
- 优化餐盘检测逻辑,增加PLC通信异常处理机制
- 统一回调处理确保所有操作在主线程执行
2026-03-30 14:06:10 +08:00
lvmeng 7d2bf08023 feat(app): 重构应用为发盘机app,界面布局优化、移除无用代码
- 将应用名称从 SmartPlateCabinet 更改为 SmartTakePlate
- 更新初始化页面背景图片为 bg_collect_page3
- 替换 View 组件为 Space 组件以优化布局权重分配
- 调整取盘按钮尺寸和文字大小提升用户体验
- 简化人脸识别登录页面布局结构
- 更新应用图标资源为 logo_launcher
- 移除未使用的权限声明和活动注册
- 优化人脸识别相关 API 接口调用
- 调整时间显示逻辑仅保留时间部分
- 移除扫描枪事件监听相关功能代码
- 重构人脸识别数据更新逻辑
- 移除余额不足对话框相关代码
2026-03-27 15:48:04 +08:00
mazengfei 86c3c07e42 隐藏测试代码 2026-03-12 11:10:26 +08:00
lvmengandClaude Sonnet 4.6 aa96455f66 refactor(face): 优化人脸特征存储与增量同步逻辑
- FaceDao 新增 queryAllByUserName 查询用户全部特征记录
- 改为查询所有记录后做去重判断,支持同一用户保存多条特征数据
- 将 Thread 替换为 lifecycleScope.launch(Dispatchers.IO) 协程
- 提取 getNewFaceEntity 方法消除重复代码
- 新增特征比对调试日志

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 16:02:13 +08:00
lvmengandClaude Sonnet 4.6 786f2a123c feat(face): 人脸库为空时自动进入新用户采集模式
- 新增 newUserCollect() 方法,人脸数据库记录为零时自动显示采集界面
- onResume 中异步查询人脸数量,据此决定进入采集模式或正常识别模式
- 修复 isFirstOpen 依赖 1000ms 延迟的竞态问题,改为立即置 false
- 顺带修改 FunActivity 按钮文案为「清除并拉取全量数据」

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:01:39 +08:00
lvmeng f2a6de55b9 优化默认值 2026-03-10 10:47:24 +08:00
lvmeng e51864d400 优化默认值 2026-03-10 10:45:16 +08:00
lvmeng e63d3f7624 修改会员字段名称 2026-03-10 08:32:18 +08:00
lvmengandClaude Sonnet 4.6 0a110020b9 refactor(face): 重构倒计时工具,统一使用 CountDownUtil 实例化方案
- CountDownUtil 由 object 单例改为 class,各调用方持有独立实例,互不干扰
- 修复 onCompletion 取消时误触发 onFinish 的 bug
- LoginByFaceActivity 新增 recognizeCountDownUtil / leftCountDownUtil 两个实例
- 移除 CountDownManager 及其所有调用,消除两套倒计时并存的混乱

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 15:50:36 +08:00
lvmeng f99538890c 代码优化 2026-03-06 15:30:25 +08:00
lvmeng 6d8ca7c83f 人脸增量接口改为间隔10秒执行 2026-03-05 16:21:45 +08:00
mazengfei c6bcdc03ba 增量时间戳为0不再限制 2026-03-05 12:38:48 +08:00
lvmeng 3b88b011a3 全量人脸数据改为首次打开app获取 2026-03-05 09:43:41 +08:00
mazengfei b96f9872cb 优化 2026-03-04 14:56:16 +08:00
lvmengandClaude Sonnet 4.6 eef13ff56e refactor(activity): 将 refreshFaceList 移至 insert 判断块外,确保删除操作后也触发刷新
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 09:15:00 +08:00
lvmengandClaude Sonnet 4.6 6c937c5f5f fix(activity): 修复 queryUserCount 在主线程执行 Room 查询导致崩溃的问题
- DB 查询改用 withContext(Dispatchers.IO) 切换至 IO 线程
- clearFace/clearFaceAndGetAll 操作完成后自动刷新用户计数
- 删除未使用的 CoroutineDispatcher import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 09:07:48 +08:00
lvmeng 513f4dbaea 增加查询用户数量 2026-03-04 09:03:32 +08:00
lvmengandClaude Sonnet 4.6 037816c303 fix(activity): 修复协程冗余 start() 调用并收敛方法可见性
- 移除 lifecycleScope.launch 后的 .start(),launch 已自动启动协程
- createBlackNV21Frame 和 emptyFrame 加 private 修饰符,收敛对外可见性

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 10:20:37 +08:00
lvmengandClaude Sonnet 4.6 dca9496bee refactor(activity): 全面优化 LoginByFaceActivity 代码质量
- 移除静态 instance 引用,消除潜在内存泄漏风险
- Observer {} 显式包装统一改为 Kotlin lambda 写法
- 匿名 Debouncer 改为成员变量 openDebouncer(修复防抖失效 bug)
- Thread.start() 替换为 lifecycleScope.launch(Dispatchers.IO)
- Arrays.fill() 替换为 Kotlin ByteArray.fill(),移除 Java import
- 清理死字段:regDebouncer、faceUseSwitch、faceFailThreshold、tempUserId
- Java getter 语法统一改为 Kotlin 属性访问
- 移除 protected override、无效 handleLoginSuccess override 等冗余代码
- 顶部添加 @file:Suppress("DEPRECATION") 统一处理废弃 API 警告

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 10:18:39 +08:00
lvmengandClaude Sonnet 4.6 08f1963054 refactor(network): 优化 ApiClient 代码,清理死代码并统一懒加载初始化
- 移除未使用的 DEVICE_BASE_URL 中间变量,直接引用 GlobalData.appBaseUrl
- okHttpClient 改为 private,收敛可见性
- retrofit 改为 by lazy,与 apiService 保持一致
- 简化日志拦截器 lambda 写法
- 清除所有注释掉的废弃代码

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 09:19:49 +08:00
mazengfei 4ea95092f5 优化 2026-02-28 16:54:57 +08:00
lvmeng cc88dccaa6 识别成功未提示及采集时间长问题优化 2026-02-28 10:03:45 +08:00
mazengfei e979322697 优化 2026-02-05 18:16:38 +08:00
lvmeng 1e6ea1517c 人脸识别+采集分开处理 2026-02-05 15:05:45 +08:00
lvmeng 2ee4d2d320 人脸识别+采集分开处理 2026-02-05 13:32:45 +08:00
80 changed files with 3381 additions and 2517 deletions
Generated
+1 -1
View File
@@ -1 +1 @@
SmartPlateCabinet
SmartTakePlate
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
<option name="progress" value="1.0" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="selectedTabId" value="Firebase Crashlytics" />
</component>
</project>
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-22T07:28:00.026342Z">
<DropdownSelection timestamp="2026-03-18T06:09:00.079142300Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.128:5555;connection=c168ec92" />
<DeviceId pluginId="Default" identifier="serial=192.168.1.161:5555;connection=2c537abb" />
</handle>
</Target>
</DropdownSelection>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>
+5 -2
View File
@@ -18,7 +18,7 @@ android {
compileSdk = 35
defaultConfig {
applicationId = "com.sw.platecabinet"
applicationId = "com.sw.take.plate"
minSdk = 24
targetSdk = 35
versionCode = 1
@@ -59,7 +59,10 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(project(":lib_face"))
implementation(project(":lib_face")) {
exclude(group = "com.licheedev", module = "android-serialport")
}
implementation(files("libs/plc_aar_104.aar"))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Binary file not shown.
+10 -11
View File
@@ -10,7 +10,8 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
@@ -21,9 +22,9 @@
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_logo512"
android:icon="@drawable/logo_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_logo512"
android:roundIcon="@drawable/logo_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.SmartPlateCabinet"
android:networkSecurityConfig="@xml/network_security_config"
@@ -38,19 +39,13 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".activity.UnBindDialogActivity"
android:exported="false"
android:theme="@style/DialogActivity" />
<activity
android:name=".activity.InitActivity"
android:exported="false"
android:launchMode="singleTask">
</activity>
<activity
android:name=".activity.LoginByPwdActivity"
android:exported="false"
android:launchMode="singleTask"/>
<activity
android:name=".activity.LoginByFaceActivity"
android:exported="true"
@@ -69,6 +64,10 @@
android:name=".activity.MainActivity"
android:exported="false">
</activity>
<activity
android:name=".activity.PlcSettingActivity"
android:exported="false" />
<!-- <activity android:name="com.sw.platecabinet.activity.FunActivity" />-->
</application>
</manifest>
@@ -33,7 +33,14 @@ object GlobalData {
/**
* 具体业务baseurl
*/
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
var appBaseUrl: String = ""
/**
* 具体业务 BaseUrl
*/
const val TEST_BASE_URL: String = "http://192.168.1.201:14801"
const val UAT_BASE_URL: String = "https://dev.yixiong-tech.com:8083"
const val PROD_BASE_URL: String = "https://api.dm.yixiong-tech.com:8443"
/**
* 设备id
@@ -61,4 +68,5 @@ object GlobalKey {
const val KEY_FIRST_RUN = "firstRun"
const val KEY_USER_INFO = "userInfoKey"
const val PARAM_EQUIPMENT_INFO = "equipmentUserInfo"
const val KEY_BASE_URL = "baseUrlKey"
}
+16 -3
View File
@@ -1,17 +1,18 @@
package com.sw.platecabinet
import android.annotation.SuppressLint
import android.util.Log
import com.sw.plate.App
import com.sw.plate.utils.AppUtil
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.utils.CrashHandler
import com.sw.platecabinet.utils.SpTool
import timber.log.Timber
class MyApp : App() {
companion object {
const val DEBUG: Boolean = true
var instance: MyApp? = null
var plateList: List<EquipmentUserInfo>? = null
// var plateList: List<EquipmentUserInfo>? = null
}
override fun onCreate() {
@@ -28,12 +29,24 @@ class MyApp : App() {
/**
* 初始化全局数据
*/
@SuppressLint("LogNotTimber")
private fun initGlobalData() {
var deviceId = AppUtil.getUDID(this)
val deviceId = AppUtil.getUDID(this)
// deviceId = "be154831-3466-3ba2-a2ea-57652c919fed"
Log.d("MyApp", "initialize: deviceId=$deviceId")
GlobalData.deviceId = deviceId
val url = SpTool.baseUrl
if (url.isNotBlank()) {
GlobalData.appBaseUrl = url
} else {
GlobalData.appBaseUrl =
if ("14d2dad5-7d33-3ced-8a59-f14cb1ef2d21" == deviceId)
GlobalData.TEST_BASE_URL
else
GlobalData.PROD_BASE_URL
}
// TODO: 临时测试----------------
// if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) {
// GlobalData.deviceId = "79814f20-f253-397c-9da7-8430ffa5ef00"
@@ -1,45 +1,39 @@
package com.sw.platecabinet.activity
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.view.WindowInsetsController
import android.view.WindowManager
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.App
import com.sw.plate.utils.Base64
import com.sw.plate.utils.ScanGunKeyEventHelper
import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.ext.clickWithCoroutines
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.IntervalExecutor
import com.sw.platecabinet.utils.PermissionHelper
import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.SpTool
import com.sw.platecabinet.view.CustomLoadingDialog
import com.sw.platecabinet.viewmodel.SettingViewModel
import com.sw.platecabinet.viewmodel.UserViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
/**
@@ -51,15 +45,14 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
protected lateinit var context: Context
private var timeJob: Job? = null
private var mDialogWaiting: CustomLoadingDialog? = null
private val permissionHelpers = mutableMapOf<Int, PermissionHelper>()
protected var keyEventHelper: ScanGunKeyEventHelper? = null
// 管理员对应的viewmodel
val settingViewModel by viewModels<SettingViewModel>()
// private val permissionHelpers = mutableMapOf<Int, PermissionHelper>()
// protected var keyEventHelper: ScanGunKeyEventHelper? = null
// 用户对应的viewModel
protected val userViewModel by viewModels<UserViewModel>()
public val recognizeViewModel by viewModels<RecognizeViewModel>()
private var startTime: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -80,62 +73,62 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
updateTime()
registerDataChange()
initialize()
registerKeyEvent()
// registerKeyEvent()
val durationTime = System.currentTimeMillis() - startTime
Timber.d("启动时间:$durationTime")
}
/**
* 监听扫描枪扫描事件
*/
protected fun registerKeyEvent() {
keyEventHelper =
ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener {
override fun onScanSuccess(barcode: String?) {
Timber.d("onScanSuccess barcode = $barcode")
if (barcode == null) return
handleScanKeyInfo(barcode)
}
})
}
// /**
// * 监听扫描枪扫描事件
// */
// protected fun registerKeyEvent() {
// keyEventHelper =
// ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener {
// override fun onScanSuccess(barcode: String?) {
// Timber.d("onScanSuccess barcode = $barcode")
// if (barcode == null) return
// handleScanKeyInfo(barcode)
// }
// })
// }
/**
* 处理扫描枪数据
*/
protected open fun handleScanKeyInfo(scanInfo: String) {
// settingViewModel.findByPlateNumber(plateNumber = scanInfo)
var plateNumber = scanInfo
if (scanInfo.length > 6) {
plateNumber = scanInfo.take(6)
}
Log.d("TAG", "handleScanKeyInfo: " + Gson().toJson(MyApp.plateList))
if (MyApp.plateList == null) {
initPlateList()
}
//柜子未关联该餐盘,直接找空位置放入盘子
val item = MyApp.plateList?.firstOrNull { it.plateNumber.isNullOrBlank() }
if (item == null) {
//柜子已满
MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL)
return
}
//找到位置,放入餐盘,清除已有id和时间
item.plateNumber = plateNumber
item.faceId = null
item.updateTime = null
PlateUtils.open(this, item.equipmentBoxCode, true)
SpTool.savePlateData(MyApp.plateList!!)
//// settingViewModel.findByPlateNumber(plateNumber = scanInfo)
// var plateNumber = scanInfo
// if (scanInfo.length > 6) {
// plateNumber = scanInfo.take(6)
// }
// Log.d("TAG", "handleScanKeyInfo: " + Gson().toJson(MyApp.plateList))
// if (MyApp.plateList == null) {
// initPlateList()
// }
// //柜子未关联该餐盘,直接找空位置放入盘子
// val item = MyApp.plateList?.firstOrNull { it.plateNumber.isNullOrBlank() }
// if (item == null) {
// //柜子已满
// MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL)
// return
// }
// //找到位置,放入餐盘,清除已有id和时间
// item.plateNumber = plateNumber
// item.faceId = null
// item.updateTime = null
// PlateUtils.open(this, item.equipmentBoxCode, true)
// SpTool.savePlateData(MyApp.plateList!!)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (keyEventHelper != null) {
if (keyEventHelper!!.isScanGunEvent(event)) {
keyEventHelper!!.analysisKeyEvent(event)
return true
}
}
return super.dispatchKeyEvent(event)
}
// override fun dispatchKeyEvent(event: KeyEvent): Boolean {
// if (keyEventHelper != null) {
// if (keyEventHelper!!.isScanGunEvent(event)) {
// keyEventHelper!!.analysisKeyEvent(event)
// return true
// }
// }
// return super.dispatchKeyEvent(event)
// }
fun updateTime() {
headerBinding?.let {
@@ -149,12 +142,10 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
onRightDoubleClick()
}
)
val scope = CoroutineScope(Dispatchers.Main)
timeJob = scope.launch {
timeJob = lifecycleScope.launch {
DateTimeUtils.realTimeChineseDateFlow()
.collect { (date, time) ->
it.tvLeftDate.text = date
it.tvRightTime.text = time
.collect { date ->
it.tvRightTime.text = date
}
}
}
@@ -171,36 +162,36 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
* 右侧时间双击
*/
open fun onRightDoubleClick() {
MainActivity.start(context, pageType = PageType.SETTING_LIST)
//MainActivity.start(context, pageType = PageType.SETTING_LIST)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val helper = permissionHelpers[requestCode]
val allGranted = PermissionHelper.handlePermissionResult(
this,
requestCode,
permissions,
grantResults,
permissionHelper = helper
)
if (allGranted) {
permissionHelpers.remove(requestCode)
}
}
/**
* 注册PermissionHelper以便处理结果
*/
fun registerPermissionHelper(helper: PermissionHelper) {
permissionHelpers[helper.requestCode] = helper
}
// override fun onRequestPermissionsResult(
// requestCode: Int,
// permissions: Array<out String>,
// grantResults: IntArray
// ) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
//
// val helper = permissionHelpers[requestCode]
// val allGranted = PermissionHelper.handlePermissionResult(
// this,
// requestCode,
// permissions,
// grantResults,
// permissionHelper = helper
// )
//
// if (allGranted) {
// permissionHelpers.remove(requestCode)
// }
// }
//
// /**
// * 注册PermissionHelper以便处理结果
// */
// fun registerPermissionHelper(helper: PermissionHelper) {
// permissionHelpers[helper.requestCode] = helper
// }
override fun onResume() {
super.onResume()
@@ -265,6 +256,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
mDialogWaiting = CustomLoadingDialog(this, view, R.style.MyDialog)
mDialogWaiting!!.show()
mDialogWaiting!!.setCancelable(true)
mDialogWaiting!!.setCanceledOnTouchOutside(true)
}
}
@@ -289,125 +281,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
*/
protected open fun registerDataChange() {
Timber.d("registerDataChange")
// lifecycleScope.launch {
// viewModel.showLoading.collect {
// Timber.d("registerDataChange 用户 showLoading = $it")
// if (it) {
// showWaitingDialog("")
// } else {
// hideWaitingDialog()
// }
// }
// }
// lifecycleScope.launch {
// viewModel.currentUserInfo.collect {
// if (it == null) return@collect
//// it.showBalanceNotEnoughDialog = true
// handleLoginSuccess(it, false)
// }
// }
// lifecycleScope.launch {
// settingViewModel.showLoading.collect {
// Timber.d("registerDataChange 管理员 showLoading = $it")
// if (it) {
// showWaitingDialog("")
// } else {
// hideWaitingDialog()
// }
// }
// }
// lifecycleScope.launch {
// settingViewModel.searchUserInfo.collect {
// Timber.d("registerDataChange 管理员 currentUserInfo = $it")
// if (it == null) return@collect
// handleLoginSuccess(it, true)
// }
// }
// lifecycleScope.launch {
// settingViewModel.equipmentList.collect {
// loadEquipmentList(it)
// }
// }
}
// private fun loadEquipmentList(items: List<EquipmentUserInfo>) {
// if (items.isEmpty()) return
// val unbindList = items.filter { !it.isBound() }
// if (unbindList.isEmpty()) {
// MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL)
// } else {
// val firstInfo = unbindList[0]
// val currentUserInfo = settingViewModel.searchUserInfo.value
// firstInfo.plateNumber = currentUserInfo?.plateNumber ?: ""
// MainActivity.start(
// context = context,
// pageType = PageType.BIND_PLATE,
// equipmentUserInfo = firstInfo
// )
// }
// }
/**
* 处理登录成功操作
* @param isAdmin true 管理员 执行绑盘此操作 false 用户,提示错误
*/
open fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
// Timber.d("handleLoginSuccess isAdmin = $isAdmin")
// equipmentUserInfo.isIntercept = false
// val cardBalance = equipmentUserInfo.cardBalance ?: 0.toDouble()
// val balanceIsNotEnough = cardBalance <= 0.toDouble()
// if (balanceIsNotEnough && equipmentUserInfo.showBalanceNotEnoughDialog) {
// //提示余额不足弹窗
// equipmentUserInfo.isIntercept = true
// BalanceNotEnoughDialog(this).show()
// return
// }
// viewModel.resetUserInfo()
//// if (equipmentUserInfo.isOtherEquipment()) {
//// MainActivity.start(
//// context,
//// pageType = PageType.PLATE_TIP,
//// equipmentUserInfo = equipmentUserInfo,
//// isAdmin
//// )
//// return
//// }
// if (equipmentUserInfo.equipmentBoxCode?.isNotEmpty() == true) {
// SerialApi.openPlate(
// equipmentUserInfo.equipmentBoxCode!!.toInt(),
// object : SerialPortManager.SendCallback {
// override fun onSuccess() {
// MainActivity.start(
// context,
// pageType = PageType.PLATE_TIP,
// isAdmin = isAdmin
// )
// }
//
// override fun onFail(e: Exception?) {
// ToastUtils.showToast("柜门打开失败")
// }
// })
// } else {
// if (isAdmin) {
// settingViewModel.getEquipmentList {
// loadEquipmentList(it)
// }
// } else {
// Timber.d("无餐盘信息")
// MainActivity.start(
// context,
// pageType = PageType.PLATE_TIP,
// equipmentUserInfo = equipmentUserInfo,
// isAdmin = isAdmin
// )
// }
// }
}
override fun onDestroy() {
timeJob?.cancel()
keyEventHelper?.onDestroy()
// keyEventHelper?.onDestroy()
super.onDestroy()
}
@@ -417,8 +296,8 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
// private val initialDelay = 5 * 60 * 1000L
// private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 5 * 60 * 1000L
private val dealyMillis = 5 * 60 * 1000L
private val initialDelay = 1 * 1000L
private val dealyMillis = 10 * 1000L
fun startFaceTask(block: (List<String?>) -> Unit) {
faceTaskJob =
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
@@ -429,29 +308,19 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
private var faceTimestamp = 0L
//private var taskPageNo = 1
private fun getFaceIncrementList(pageNo: Int = 1, action:(List<String?>)-> Unit) {
private fun getFaceIncrementList(pageNo: Int = 1, action: (List<String?>) -> Unit) {
val timestamp = SpTool.lastFaceTimestamp
if (timestamp == 0L) {
return
}
settingViewModel.getFaceIncrementList(
userViewModel.getFaceIncrementList(
pageNo = pageNo,
timestamp = timestamp
) { list ->
runOnUiThread {
if (pageNo == 1 && list.isEmpty()) {
//未查询到增量数据
return@runOnUiThread
}
updateFaceData(list)
val userIdList = list.map { it.userId }
action(userIdList)
//if (list.size >= settingViewModel.PAGE_SIZE) {
// faceTimestamp = list.last().faceUpdateTimestamp ?: 0
// taskPageNo++
// getFaceIncrementList(taskPageNo)
// return@runOnUiThread
//}
if (list.isNotEmpty()) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
}
@@ -460,30 +329,27 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
}
public val recognizeViewModel by viewModels<RecognizeViewModel>()
private fun updateFaceData(list: List<UserFaceModel>) {
Thread {
lifecycleScope.launch(Dispatchers.IO) {
val faceList = mutableListOf<FaceEntity>()
val faceDao = FaceDatabase.getInstance(this).faceDao()
val faceDao = FaceDatabase.getInstance(this@BaseActivity).faceDao()
try {
list.forEach { model ->
if (model.faceDeleted == true) {
//删除数据
faceDao.deleteFaceById(model.userId)
} else {
val faceData = faceDao.queryByUserName(model.userId)
if (faceData == null) {
//保存数据
val faceEntity = FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.isMember) "1" else "2"
it.cardNo = model.cardNo
}
faceList.add(faceEntity)
// 获取该用户所有已存特征,判断当前特征是否已存在
val existList = faceDao.queryAllByUserName(model.userId)
val featureMatches =
existList.map { Base64.encode(it.featureData) == model.faceFeatureStr }
Timber.d("userId=${model.userId}, 已存记录数=${existList.size}, 各条特征是否与服务器一致=$featureMatches")
val alreadyExists = featureMatches.any { it }
if (!alreadyExists) {
//当前特征不存在,保存
runCatching { getNewFaceEntity(model) }
.onSuccess { faceList.add(it) }
.onFailure { Timber.e(it, "人脸特征解码失败: userId=${model.userId}") }
}
}
}
@@ -492,34 +358,62 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
try {
if (faceList.isNotEmpty()) {
FaceDatabase.getInstance(this).faceDao().insert(faceList)
recognizeViewModel.refreshFaceList()
FaceDatabase.getInstance(this@BaseActivity).faceDao().insert(faceList)
}
recognizeViewModel.refreshFaceList()
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
}
fun initPlateList() {
// SpTool.putString(SpTool.PLATE_BOX_DATA, "")
// var list = SpTool.getPlateData()
val list: MutableList<EquipmentUserInfo> = mutableListOf()
repeat(22) { index ->
list.add(
EquipmentUserInfo(equipmentBoxCode = "${index + 1}")
)
private fun getNewFaceEntity(model: UserFaceModel): FaceEntity {
return FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.member) "1" else "2"
it.cardNo = model.cardNo
}
MyApp.plateList = list
}
fun clearAllFace(block: () -> Unit) {
Thread {
lifecycleScope.launch(Dispatchers.IO) {
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
faceDao.deleteAll()
faceDao.resetId()
runOnUiThread { block() }
}.start()
recognizeViewModel.refreshFaceList()
withContext(Dispatchers.Main) { block() }
}
}
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var activityCallback: ((intent: Intent?) -> Unit)? = null
fun requestPermission(permission: String, callback: (isGranted: Boolean) -> Unit) {
this.permissionCallback = callback
requestPermissionLauncher.launch(permission)
}
// 权限请求回调
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
permissionCallback?.invoke(isGranted)
}
fun startActivity(intent: Intent, callback: (Intent?) -> Unit) {
this.activityCallback = callback
startActivityLauncher.launch(intent)
}
// activity页面返回的回调
private val startActivityLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
activityCallback?.invoke(it.data)
}
}
@@ -7,6 +7,7 @@ import com.sw.plate.utils.arcface.FaceApi
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.databinding.ActivityDeviceInitBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -25,32 +26,14 @@ class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
}
override fun initialize() {
// val isSuccess = deviceViewModel.checkEquipmentInfo()
// if (isSuccess) {
// goLoginActivity()
// return
// }
// binding.ivQrCode.setImageBitmap(
// QRCodeUtil.generateQRCode(
// content = GlobalData.deviceId,
// size = 200
// )
// )
// binding.btnInit.setOnClickListener {
// deviceViewModel.getDeviceToken()
// }
// binding.btnInit.gone()
// binding.ivQrCode.invisible()
// binding.root.postDelayed({
// goLoginActivity()
// }, 1000)
initPlateList()
showWaitingDialog("加载中……")
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val count = FaceApi().queryFaceCount()
if (count == 0) {
//val count = FaceApi().queryFaceCount()
//if (count == 0) {
if (SpTool.firstGetFace) {
userViewModel.getUserFaceCache { status, msg ->
SpTool.firstGetFace = false
getDeviceConfig()
}
} else {
@@ -0,0 +1,152 @@
//package com.sw.platecabinet.activity
//
//import androidx.activity.viewModels
//import androidx.lifecycle.lifecycleScope
//import com.sw.plate.utils.Base64
//import com.sw.plate.utils.ToastUtils
//import com.sw.plate.utils.arcface.facedb.FaceDatabase
//import com.sw.platecabinet.MyApp
//import com.sw.platecabinet.databinding.ActivityFunBinding
//import com.sw.platecabinet.databinding.ItemTitleTimeBinding
//import com.sw.platecabinet.dialog.CustomDialog
//import com.sw.platecabinet.ext.clickWithDebounce
//import com.sw.platecabinet.utils.PlateUtils
//import com.sw.platecabinet.utils.SpTool
//import com.sw.platecabinet.viewmodel.UserViewModel
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.delay
//import kotlinx.coroutines.launch
//import kotlinx.coroutines.withContext
//import kotlin.getValue
//
//class FunActivity : BaseActivity<ActivityFunBinding>() {
//
// override fun inflateTitleBinding(): ItemTitleTimeBinding? {
// return binding.includeHeader
// }
//
// override fun inflateViewBinding() = ActivityFunBinding.inflate(layoutInflater)
//
// override fun initialize() {
// binding.btnClearFace.clickWithDebounce { clearFace() }
// binding.btnClearAndGetAll.clickWithDebounce { clearFaceAndGetAll() }
// binding.btnOpenAllPlate.clickWithDebounce {
// lifecycleScope.launch {
// openAllPlate()
// }
// }
// binding.btnPutPlate.clickWithDebounce { putPlate() }
//
// queryUserCount()
//
// binding.btnTestFeature.clickWithDebounce {
// testFeature()
// }
// }
// private fun testFeature() {
// userViewModel.getUserFace(1, 1) {list ->
// lifecycleScope.launch {
// withContext(Dispatchers.IO) {
// if (list.isEmpty()) return@withContext
// val userId = list[0].userId
// val base64Feature = list[0].faceFeatureStr
// val faceDao = FaceDatabase.getInstance(this@FunActivity).faceDao()
// val entityList = faceDao.queryAllByUserName(userId)
// val resultList = entityList.map { base64Feature == Base64.encode(it.featureData) }
// val isEqual = resultList.any { true }
// ToastUtils.showToast("比较结果:$isEqual")
// }
// }
// }
// }
//
// private fun clearFace() {
// loadClearDialog("请确认是否清除本地所有数据?") {
// showWaitingDialog("加载中……")
// clearAllFace {
// binding.root.postDelayed(
// {
// hideWaitingDialog()
// ToastUtils.showToast("已清除")
// queryUserCount()
// }, 500
// )
// }
// }
// }
//
// private fun clearFaceAndGetAll() {
// loadClearDialog("请确认是否清除本地所有数据,并重新拉取全量人脸数据?") {
// showWaitingDialog("加载中……")
// clearAllFace {
// userViewModel.getUserFaceCache { status, msg ->
// hideWaitingDialog()
// ToastUtils.showToast(
// if (status) "请求成功,$msg" else "请求失败,$msg"
// )
// queryUserCount()
// }
// }
// }
// }
//
// private suspend fun openAllPlate() {
// MyApp.plateList?.let { plateList ->
// val boxCodeList = plateList.filter { it.equipmentBoxCode.isNullOrBlank().not() }
// .map { it.equipmentBoxCode }
// boxCodeList.forEach { boxCode ->
// withContext(Dispatchers.Default) {
// PlateUtils.openDirect(boxCode)
// delay(500)
// }
// }
// }
// }
//
// private fun putPlate() {
// showWaitingDialog("加载中……")
// MyApp.plateList?.forEachIndexed { index, item ->
// val num = index + 1
// val realNum = if (num < 10) "0${num}" else "${num}"
// item.plateNumber = realNum + realNum + realNum
// item.faceId = null
// item.updateTime = null
// }
// SpTool.savePlateData(MyApp.plateList!!)
// binding.root.postDelayed({
// hideWaitingDialog()
// ToastUtils.showToast("已完成")
// }, 1000)
// }
//
// /**
// * 加载清除数据弹窗
// */
// private fun loadClearDialog(msg: String, callback: () -> Unit) {
// CustomDialog(
// activity = this,
// content = msg,
// onConfirm = {
// callback()
// }).show()
// }
//
// override fun onLeftDoubleClick() {
// finish()
// }
//
// override fun onRightDoubleClick() {
//
// }
//
// private fun queryUserCount() {
// lifecycleScope.launch {
// val faceDao = FaceDatabase.getInstance(this@FunActivity).faceDao()
// val memberUserCount = withContext(Dispatchers.IO) { faceDao.getFaceCount("1") }
// val tempUserCount = withContext(Dispatchers.IO) { faceDao.getFaceCount("2") }
// binding.tvMemberUserCount.text = "会员用户:${memberUserCount}人"
// binding.tvTempUserCount.text = "临时用户:${tempUserCount}人"
// }
// }
//
//}
@@ -41,4 +41,8 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
finish()
}
override fun onRightDoubleClick() {
PlcSettingActivity.start(this)
}
}
File diff suppressed because it is too large Load Diff
@@ -1,62 +0,0 @@
//package com.sw.platecabinet.activity
//
//import android.content.Intent
//import com.sw.plate.utils.ToastUtils
//import com.sw.platecabinet.databinding.ActivityLoginByPwdBinding
//import com.sw.platecabinet.databinding.ItemTitleTimeBinding
//import com.sw.platecabinet.dialog.BalanceNotEnoughDialog
//import com.sw.platecabinet.model.request.LoginParam
//import com.sw.platecabinet.model.response.EquipmentUserInfo
//import com.sw.platecabinet.utils.KeyboardUtils
//
///**
// * 密码登录
// */
//class LoginByPwdActivity : BaseActivity<ActivityLoginByPwdBinding>() {
//
// override fun inflateViewBinding(): ActivityLoginByPwdBinding {
// return ActivityLoginByPwdBinding.inflate(layoutInflater)
// }
//
// override fun inflateTitleBinding(): ItemTitleTimeBinding {
// return binding.includeHeader
// }
//
// override fun initialize() {
// binding.btnLogin.setOnClickListener {
// val phone = binding.etPhone.text.toString()
// val pwd = binding.etPwd.text.toString()
// if (phone.isEmpty() || pwd.isEmpty()) {
// ToastUtils.showToast("手机或校验码不能为空")
// return@setOnClickListener
// }
// val loginParam = LoginParam(
// //equipmentId = equipmentId,
// phone = phone,
// password = pwd
// )
// viewModel.loginWithPwd(loginParam)
// KeyboardUtils.hideKeyboard(this)
// }
// binding.tvFaceRec.setOnClickListener {
// val intent = Intent(this, LoginByFaceActivity::class.java)
// startActivity(intent)
// }
// }
//
// override fun onResume() {
// super.onResume()
// viewModel.resetUserInfo()
// }
//
// override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
// super.handleLoginSuccess(equipmentUserInfo, isAdmin)
//// val cardBalance = equipmentUserInfo.cardBalance?:0.toDouble()
//// val balanceIsNotEnough = cardBalance <= 0.toDouble()
// if (equipmentUserInfo.isIntercept) {
// return
// }
// LoginByFaceActivity.goInitActivity()
// finish()
// }
//}
@@ -2,14 +2,11 @@ package com.sw.platecabinet.activity
import android.content.Context
import android.content.Intent
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ActivityMainBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.dialog.CustomDialog
import com.sw.platecabinet.fragment.PlateCabinetFullFragment
import com.sw.platecabinet.fragment.PlateOpenFragment
import com.sw.platecabinet.fragment.SettingListFragment
import com.sw.platecabinet.utils.FragmentHelper
typealias Callback = (String) -> Unit
@@ -65,7 +62,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
pageType = param?.let { PageType.valueOf(param) } ?: PageType.SETTING_LIST
fragmentHelper = FragmentHelper(supportFragmentManager, R.id.fragment_container)
val page = when (pageType) {
PageType.SETTING_LIST -> SettingListFragment()
// PageType.SETTING_LIST -> SettingListFragment()
// PageType.BIND_PLATE -> BindPlateFragment.newInstance(equipmentUserInfo)
// PageType.PLATE_TIP -> PlateOpenFragment.newInstance(equipmentUserInfo, isAdmin)
PageType.PLATE_TIP -> PlateOpenFragment.newInstance(isScanCode)
@@ -86,7 +83,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onRightDoubleClick() {
if (pageType == PageType.SETTING_LIST) {
loadClearDialog()
// startActivity(Intent(this, FunActivity::class.java))
}
}
@@ -104,25 +101,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
super.registerDataChange()
}
/**
* 加载清除数据弹窗
*/
private fun loadClearDialog() {
CustomDialog(
activity = this,
content = "请确认是否清除本地所有数据,并重新拉取全量人脸数据?",
onConfirm = {
showWaitingDialog("加载中……")
clearAllFace {
userViewModel.getUserFaceCache { status, msg ->
hideWaitingDialog()
ToastUtils.showToast(
if (status) "请求成功,$msg" else "请求失败,$msg"
)
}
}
}).show()
}
}
/**
@@ -0,0 +1,362 @@
package com.sw.platecabinet.activity
import android.content.Context
import android.content.Intent
import android.view.inputmethod.InputMethodManager
import androidx.lifecycle.lifecycleScope
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.faceserver.FaceServer
import com.sw.platecabinet.databinding.ActivityPlcSettingBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.dialog.EnvSwitchDialog
import com.sw.platecabinet.ext.clickWithDebounce
import com.sw.platecabinet.utils.mego.PlcCallback
import com.sw.platecabinet.utils.mego.PlcHelper
import com.sw.platecabinet.utils.mego.PlcStatus
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* PLC 控制设置界面
* 通过右侧时间双击(onRightDoubleClick)打开,提供全套 PLC 控制操作入口
*/
class PlcSettingActivity : BaseActivity<ActivityPlcSettingBinding>() {
companion object {
/** Intent Extra 键:是否显示 tips 界面 */
private const val EXTRA_SHOW_TIPS = "extra_show_tips"
/**
* 启动 PlcSettingActivity
* @param showTips 是否显示 tips 界面(暂无餐盘提示)
*/
fun start(context: Context, showTips: Boolean = false) {
val intent = Intent(context, PlcSettingActivity::class.java)
intent.putExtra(EXTRA_SHOW_TIPS, showTips)
context.startActivity(intent)
}
}
override fun inflateViewBinding(): ActivityPlcSettingBinding {
return ActivityPlcSettingBinding.inflate(layoutInflater)
}
override fun inflateTitleBinding(): ItemTitleTimeBinding {
return binding.includeHeader
}
override fun initialize() {
// 根据传入参数控制 tips 界面显示
val showTips = intent.getBooleanExtra(EXTRA_SHOW_TIPS, false)
binding.layoutTips.visibility =
if (showTips) android.view.View.VISIBLE else android.view.View.GONE
initConnectButtons()
initOutPlateButton()
initPlatformButtons()
initPlatformResetButton()
initAlarmButton()
initParamButtons()
initStatusButton()
binding.btnSwitchEnv.setOnClickListener {
EnvSwitchDialog(context = this, onEnvChanged = {
showWaitingDialog("重置人脸数据中……")
lifecycleScope.launch {
withContext(Dispatchers.IO) {
FaceServer.getInstance().clearAllFaces()
}
}
userViewModel.getUserFaceCache(pageNo = 1, callback = { state, errMsg ->
hideWaitingDialog()
if (state) {
recognizeViewModel.refreshFaceList()
ToastUtils.showToast("人脸数据已更新")
} else {
ToastUtils.showToast(errMsg)
}
})
}).show()
}
binding.tvTips.clickWithDebounce {
binding.layoutTips.visibility = android.view.View.GONE
}
}
// ==================== 连接控制 ====================
/**
* 初始化连接控制区按钮:打开串口、打开自动模式
*/
private fun initConnectButtons() {
binding.btnOpenSerialPort.clickWithDebounce {
showWaitingDialog("正在打开串口……")
PlcHelper.getInstance(this).openSerialPort(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("打开串口成功:$result")
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("打开串口失败:$message")
}
})
}
binding.btnOpenAutoMode.clickWithDebounce {
showWaitingDialog("正在打开自动模式……")
PlcHelper.getInstance(this).openAutoMode(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("打开自动模式成功:$result")
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("打开自动模式失败:$message")
}
})
}
}
// ==================== 出盘操作 ====================
/**
* 初始化出盘操作按钮
*/
private fun initOutPlateButton() {
binding.btnOutPlate.clickWithDebounce {
showWaitingDialog("正在出盘……")
PlcHelper.getInstance(this).outPlate(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("出盘成功:$result")
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("出盘失败:$message")
}
})
}
}
// ==================== 平台控制 ====================
/**
* 初始化平台控制区按钮:平台下降、平台上升、返回营业
*/
private fun initPlatformButtons() {
binding.btnPlatformDown.clickWithDebounce {
showWaitingDialog("正在执行平台下降……")
PlcHelper.getInstance(this).platformDown(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("平台下降成功:$result")
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("平台下降失败:$message")
}
})
}
binding.btnPlatformUp.clickWithDebounce {
showWaitingDialog("正在执行平台上升……")
PlcHelper.getInstance(this).platformUp(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("平台上升成功:$result")
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("平台上升失败:$message")
}
})
}
binding.btnBackToBusiness.clickWithDebounce {
showWaitingDialog("正在返回营业状态……")
PlcHelper.getInstance(this).backToBusiness(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("返回营业成功:$result")
finish()
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("返回营业失败:$message")
}
})
}
}
// ==================== 故障处理 ====================
/**
* 初始化平台复位按钮:先执行平台上升,成功后自动执行返回营业
*/
private fun initPlatformResetButton() {
binding.btnPlatformReset.clickWithDebounce {
showWaitingDialog("正在复位平台……")
PlcHelper.getInstance(this).platformUp(object : PlcCallback<String> {
override fun onSuccess(result: String) {
// 平台上升完成,继续执行返回营业
PlcHelper.getInstance(this@PlcSettingActivity)
.backToBusiness(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("平台复位成功")
finish()
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("返回营业失败:$message")
}
})
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("平台上升失败:$message")
}
})
}
}
/**
* 初始化故障复位按钮
*/
private fun initAlarmButton() {
binding.btnAlarmReset.clickWithDebounce {
showWaitingDialog("正在故障复位……")
PlcHelper.getInstance(this).alarmReset(object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("故障复位成功:$result")
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("故障复位失败:$message")
}
})
}
}
// ==================== 参数设置 ====================
/**
* 初始化参数设置按钮
* 读取参数类型和参数值输入框内容,调用 setParams
*/
private fun initParamButtons() {
binding.btnSetParams.clickWithDebounce {
val paramType = binding.etParamType.text.toString().trim()
val paramValueStr = binding.etParamValue.text.toString().trim()
if (paramType.isEmpty()) {
ToastUtils.showToast("请输入参数类型")
return@clickWithDebounce
}
if (paramValueStr.isEmpty()) {
ToastUtils.showToast("请输入参数值")
return@clickWithDebounce
}
val paramValue = paramValueStr.toIntOrNull()
if (paramValue == null) {
ToastUtils.showToast("参数值须为整数")
return@clickWithDebounce
}
// 隐藏键盘
hideKeyboard()
showWaitingDialog("正在设置参数……")
PlcHelper.getInstance(this).setParams(
paramType,
paramValue,
object : PlcCallback<String> {
override fun onSuccess(result: String) {
hideWaitingDialog()
ToastUtils.showToast("参数设置成功:$result")
}
override fun onError(message: String) {
hideWaitingDialog()
ToastUtils.showToast("参数设置失败:$message")
}
}
)
}
}
// ==================== 状态显示 ====================
/**
* 初始化状态刷新按钮,获取 PLC 状态并格式化展示
*/
private fun initStatusButton() {
binding.btnRefreshStatus.clickWithDebounce {
showWaitingDialog("正在获取状态……")
PlcHelper.getInstance(this).getStatus(object : PlcCallback<PlcStatus> {
override fun onSuccess(result: PlcStatus) {
hideWaitingDialog()
binding.tvStatusInfo.text = buildStatusText(result)
}
override fun onError(message: String) {
hideWaitingDialog()
binding.tvStatusInfo.text = "获取状态失败:$message"
ToastUtils.showToast("获取状态失败:$message")
}
})
}
}
/**
* 将 PlcStatus 格式化为多行可读文本
*/
private fun buildStatusText(status: PlcStatus): String {
return """
空盘状态:${if (status.isEmptyPlate) "是(无餐盘)" else "否(有餐盘)"}
平台下位:${if (status.isPlateformDown) "是" else "否"}
平台上位:${if (status.isPlateformUp) "是" else "否"}
取盘红外:${if (status.isTakePlateInfrared) "已触发" else "未触发"}
报警代码:${if (status.alarmCode == 0) "正常(0" else "异常(${status.alarmCode}"}
""".trimIndent()
}
// ==================== 生命周期 ====================
/**
* 左侧日期双击 → 关闭设置界面
*/
override fun onLeftDoubleClick() {
finish()
}
/**
* 右侧时间双击 → 不做操作,防止循环打开
*/
override fun onRightDoubleClick() {
// 空实现,避免在设置页再次触发跳转
}
// ==================== 工具方法 ====================
/**
* 隐藏软键盘
*/
private fun hideKeyboard() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(currentFocus?.windowToken, 0)
}
}
@@ -1,97 +0,0 @@
package com.sw.platecabinet.activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.sw.platecabinet.R
import kotlin.math.ceil
class TestActivity : AppCompatActivity() {
private lateinit var parentRecyclerView: RecyclerView
private lateinit var pageAdapter: PageAdapter
private val itemsPerPage = 22 // 每行2个,每列11个,共22个
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
parentRecyclerView = findViewById(R.id.parentRecyclerView)
parentRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
// 假设有100个数据项
val allItems = (1..30).map { "Item $it" }
val pages = allItems.chunked(itemsPerPage)
pageAdapter = PageAdapter(pages)
parentRecyclerView.adapter = pageAdapter
}
private fun convertToColumnFirst(original: List<String>, cols: Int): List<String> {
val rows = ceil(original.size.toDouble() / cols).toInt()
return List(original.size) { pos ->
val row = pos % rows
val col = pos / rows
val originalPos = col + row * cols
original.getOrElse(originalPos) { "" }
}
}
inner class PageAdapter(private val pages: List<List<String>>) :
RecyclerView.Adapter<PageAdapter.PageViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.page_item_layout, parent, false)
return PageViewHolder(view)
}
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
holder.bind(pages[position])
}
override fun getItemCount(): Int = pages.size
inner class PageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val childRecyclerView: RecyclerView =
itemView.findViewById(R.id.childRecyclerView)
fun bind(items: List<String>) {
childRecyclerView.layoutManager = GridLayoutManager(itemView.context, 2)
childRecyclerView.adapter = ChildAdapter(items)
}
}
}
inner class ChildAdapter(private val items: List<String>) :
RecyclerView.Adapter<ChildAdapter.ItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_bind_view, parent, false)
return ItemViewHolder(view)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(position, items[position])
}
override fun getItemCount(): Int = items.size
inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val llRoot: LinearLayout = itemView.findViewById(R.id.ll_root)
private val tvNum: TextView = itemView.findViewById(R.id.tv_num)
fun bind(position: Int, item: String) {
tvNum.text = item
llRoot.setBackgroundResource(if (position % 3 == 0) R.drawable.grid_item_bind else R.drawable.grid_item_unbind)
}
}
}
}
@@ -1,57 +0,0 @@
//package com.sw.platecabinet.activity
//
//import android.graphics.Color
//import android.view.Gravity
//import android.view.ViewGroup
//import androidx.core.graphics.drawable.toDrawable
//import androidx.core.view.WindowCompat
//import com.sw.platecabinet.GlobalKey
//import com.sw.platecabinet.databinding.ActivityUnbindDialogBinding
//import com.sw.platecabinet.databinding.ItemTitleTimeBinding
//import com.sw.platecabinet.ext.dp
//import com.sw.platecabinet.model.response.EquipmentUserInfo
//
///**
// * 解绑确认弹窗界面
// */
//class UnBindDialogActivity : BaseActivity<ActivityUnbindDialogBinding>() {
// private var equipmentUserInfo: EquipmentUserInfo? = null
//
// override fun inflateViewBinding(): ActivityUnbindDialogBinding {
// return ActivityUnbindDialogBinding.inflate(layoutInflater)
// }
//
// override fun inflateTitleBinding(): ItemTitleTimeBinding? {
// return null
// }
//
// override fun initialize() {
// window?.apply {
// WindowCompat.setDecorFitsSystemWindows(this, false)
// setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
//
// attributes = attributes.apply {
// y = 314.dp
// width = (context.resources.displayMetrics.widthPixels * 0.8).toInt()
// height = ViewGroup.LayoutParams.MATCH_PARENT
// gravity = Gravity.CENTER
// }
// }
// equipmentUserInfo =
// intent.getParcelableExtra<EquipmentUserInfo>(GlobalKey.PARAM_EQUIPMENT_INFO)
// if (equipmentUserInfo == null) return
// binding.tvTipInfo.text = "即将解除【${equipmentUserInfo!!.name}】的餐盘"
// binding.tvCancel.setOnClickListener {
// finish()
// }
// binding.tvConfirm.setOnClickListener {
// setResult(RESULT_OK)
// finish()
// }
// }
//
// override fun finish() {
// super.finish()
// overridePendingTransition(0, 0) // 确保退出也无动画
// }
//}
@@ -1,36 +1,36 @@
package com.sw.platecabinet.dialog
import android.app.Dialog
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Window
import androidx.fragment.app.FragmentActivity
import com.sw.platecabinet.databinding.DialogBalanceNotEnoughBinding
import com.sw.platecabinet.ext.dp
open class BalanceNotEnoughDialog(
private var activity: FragmentActivity
) : Dialog(activity) {
private lateinit var binding: DialogBalanceNotEnoughBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
binding = DialogBalanceNotEnoughBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.run {
attributes = attributes.apply {
width = 480.dp
}
setBackgroundDrawable(ColorDrawable())
setCancelable(false)
}
addListener()
}
private fun addListener() {
binding.tvConfirm.setOnClickListener { dismiss() }
}
}
//package com.sw.platecabinet.dialog
//
//import android.app.Dialog
//import android.graphics.drawable.ColorDrawable
//import android.os.Bundle
//import android.view.Window
//import androidx.fragment.app.FragmentActivity
//import com.sw.platecabinet.databinding.DialogBalanceNotEnoughBinding
//import com.sw.platecabinet.ext.dp
//
//open class BalanceNotEnoughDialog(
// private var activity: FragmentActivity
//) : Dialog(activity) {
//
// private lateinit var binding: DialogBalanceNotEnoughBinding
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// requestWindowFeature(Window.FEATURE_NO_TITLE)
// binding = DialogBalanceNotEnoughBinding.inflate(layoutInflater)
// setContentView(binding.root)
// window?.run {
// attributes = attributes.apply {
// width = 480.dp
// }
// setBackgroundDrawable(ColorDrawable())
// setCancelable(false)
// }
// addListener()
// }
//
// private fun addListener() {
// binding.tvConfirm.setOnClickListener { dismiss() }
// }
//
//}
@@ -0,0 +1,38 @@
package com.sw.platecabinet.dialog
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.sw.platecabinet.R
import com.sw.platecabinet.ext.dp
import com.sw.platecabinet.ext.hideKeyboard
abstract class BaseDialog(
context: Context,
var defWidth: Int = 400.dp,
var defHeight: Int = 300.dp
) : Dialog(context, R.style.MyDialog) {
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,69 @@
package com.sw.platecabinet.dialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.databinding.DialogEnvSwitchBinding
import com.sw.platecabinet.ext.dp
import com.sw.platecabinet.utils.SpTool
/**
* 环境切换弹窗
*
* 提供 TEST / UAT / PROD 三套环境的切换功能,
* 初始化时自动根据 [GlobalData.appBaseUrl] 选中当前环境,
* 确认后更新 [GlobalData.appBaseUrl] 并通过 [onEnvChanged] 回调通知外部。
*
* @param context 上下文
* @param onEnvChanged 确认切换后的回调,参数为新的 baseUrl
*/
class EnvSwitchDialog(
context: Context,
private val onEnvChanged: (newBaseUrl: String) -> Unit = {}
) : BaseDialog(
context,
defWidth = 600.dp,
defHeight = WindowManager.LayoutParams.WRAP_CONTENT
) {
private lateinit var binding: DialogEnvSwitchBinding
override fun getRootView(): View {
binding = DialogEnvSwitchBinding.inflate(LayoutInflater.from(context))
return binding.root
}
override fun initView() {
// 根据当前 appBaseUrl 预选对应 RadioButton,不匹配则不选
when (GlobalData.appBaseUrl) {
GlobalData.TEST_BASE_URL -> binding.rbTest.isChecked = true
GlobalData.UAT_BASE_URL -> binding.rbUat.isChecked = true
GlobalData.PROD_BASE_URL -> binding.rbProd.isChecked = true
}
// 取消按钮
binding.btnCancel.setOnClickListener { dismiss() }
// 确认按钮
binding.btnConfirm.setOnClickListener {
// 未选中任何选项时提示用户
if (binding.rgEnv.checkedRadioButtonId == -1) {
ToastUtils.showToast("请选择环境")
return@setOnClickListener
}
val newUrl = when (binding.rgEnv.checkedRadioButtonId) {
binding.rbTest.id -> GlobalData.TEST_BASE_URL
binding.rbUat.id -> GlobalData.UAT_BASE_URL
binding.rbProd.id -> GlobalData.PROD_BASE_URL
else -> return@setOnClickListener
}
GlobalData.appBaseUrl = newUrl
SpTool.baseUrl = newUrl
onEnvChanged(newUrl)
dismiss()
}
}
}
@@ -1,38 +1,38 @@
package com.sw.platecabinet.dialog
import android.app.Dialog
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Window
import androidx.fragment.app.FragmentActivity
import com.sw.platecabinet.databinding.DialogUserBindRemindBinding
import com.sw.platecabinet.ext.dp
open class UserBindRemindDialog(
private var activity: FragmentActivity,
var content:String
) : Dialog(activity) {
private lateinit var binding: DialogUserBindRemindBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
binding = DialogUserBindRemindBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.run {
attributes = attributes.apply {
width = 480.dp
}
setBackgroundDrawable(ColorDrawable())
setCancelable(false)
}
binding.tvBindContent.text = content
addListener()
}
private fun addListener() {
binding.tvConfirm.setOnClickListener { dismiss() }
}
}
//package com.sw.platecabinet.dialog
//
//import android.app.Dialog
//import android.graphics.drawable.ColorDrawable
//import android.os.Bundle
//import android.view.Window
//import androidx.fragment.app.FragmentActivity
//import com.sw.platecabinet.databinding.DialogUserBindRemindBinding
//import com.sw.platecabinet.ext.dp
//
//open class UserBindRemindDialog(
// private var activity: FragmentActivity,
// var content:String
//) : Dialog(activity) {
//
// private lateinit var binding: DialogUserBindRemindBinding
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// requestWindowFeature(Window.FEATURE_NO_TITLE)
// binding = DialogUserBindRemindBinding.inflate(layoutInflater)
// setContentView(binding.root)
// window?.run {
// attributes = attributes.apply {
// width = 480.dp
// }
// setBackgroundDrawable(ColorDrawable())
// setCancelable(false)
// }
// binding.tvBindContent.text = content
// addListener()
// }
//
// private fun addListener() {
// binding.tvConfirm.setOnClickListener { dismiss() }
// }
//
//}
@@ -1,8 +1,10 @@
package com.sw.platecabinet.ext
import android.content.Context
import android.content.res.Resources
import android.util.TypedValue
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.annotation.Dimension
/**
@@ -63,4 +65,12 @@ fun View.invisible() {
fun View.gone() {
visibility = View.GONE
}
/**
* 隐藏软键盘
*/
fun View.hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
}
@@ -69,4 +69,36 @@ fun View.clickWithCoroutines(
lastClickTime = currentTime
}
}
}
//fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) {
// var job: Job? = null
// setOnClickListener {
// job?.cancel()
// job = CoroutineScope(Dispatchers.Main).launch {
// delay(delay)
// action()
// }
// }
//}
/**
* 极简版防重复点击扩展函数
* @param delay 防抖时间(默认300ms
* @param action 点击执行逻辑
*/
fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) {
setOnClickListener {
// 用View的tag存储是否可点击的状态(默认可点击)
if (tag as? Boolean ?: true) {
tag = false // 标记为不可点击
action() // 立即执行点击逻辑
// 启动协程,延迟后恢复可点击状态
CoroutineScope(Dispatchers.Main).launch {
delay(delay)
tag = true // 恢复可点击
}
}
}
}
@@ -8,21 +8,16 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.sw.platecabinet.R
import com.sw.platecabinet.view.CustomLoadingDialog
import com.sw.platecabinet.viewmodel.SettingViewModel
import kotlinx.coroutines.launch
abstract class BaseFragment<VB : ViewBinding>(
private val bindingInflater: (inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) -> VB
) : Fragment() {
private var _binding: VB? = null
protected var _binding: VB? = null
protected val binding get() = _binding!!
private var mDialogWaiting: CustomLoadingDialog? = null
protected val viewModel by viewModels<SettingViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
@@ -39,15 +34,7 @@ abstract class BaseFragment<VB : ViewBinding>(
* 注册数据变化监听
*/
open fun registerDataChange() {
lifecycleScope.launch {
viewModel.showLoading.collect {
if (it) {
showWaitingDialog("")
} else {
hideWaitingDialog()
}
}
}
}
abstract fun initialize()
@@ -11,6 +11,7 @@ import timber.log.Timber
class PlateCabinetFullFragment :
BaseFragment<FragmentPlateCabinetFullBinding>(FragmentPlateCabinetFullBinding::inflate) {
private var totalTimeInMillis: Long = Constants.AUTO_CLOSE_TIME * 1000
private var countDownTimer: CountDownTimer? = null
override fun initialize() {
Timber.d("initialize")
@@ -18,15 +19,22 @@ class PlateCabinetFullFragment :
}
fun initCountTime() {
object : CountDownTimer(totalTimeInMillis, 1000) {
countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.tvAutoClose.text = "${(millisUntilFinished / 1000).toInt() + 1}秒后返回主屏"
_binding?.tvAutoClose?.text = "${(millisUntilFinished / 1000).toInt() + 1}秒后返回主屏"
}
override fun onFinish() {
activity?.finish()
}
}.start()
}
countDownTimer?.start()
}
override fun onDestroyView() {
countDownTimer?.cancel()
countDownTimer = null
super.onDestroyView()
}
}
@@ -7,7 +7,6 @@ import com.sw.platecabinet.Constants
import com.sw.platecabinet.R
import com.sw.platecabinet.activity.InitActivity
import com.sw.platecabinet.databinding.FragmentPlateOpenBinding
import com.sw.platecabinet.model.response.EquipmentUserInfo
/**
* 餐盘柜提示界面
@@ -21,6 +20,7 @@ class PlateOpenFragment :
// private var isAdmin: Boolean = false
private var isScanCode = false
private var countDownTimer: CountDownTimer? = null
companion object {
const val IS_SCAN_CODE = "isScanCode"
// @JvmStatic
@@ -76,19 +76,23 @@ class PlateOpenFragment :
}
fun initCountTime() {
object : CountDownTimer(totalTimeInMillis, 1000) {
countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.tvAutoClose.text = ((millisUntilFinished / 1000).toInt() + 1).toString()
_binding?.tvAutoClose?.text = ((millisUntilFinished / 1000).toInt() + 1).toString()
}
override fun onFinish() {
val ctx = context ?: return
activity?.finish()
// if (!isAdmin) {
val intent = Intent(context, InitActivity::class.java)
startActivity(intent)
// }
ctx.startActivity(Intent(ctx, InitActivity::class.java))
}
}.start()
}
countDownTimer?.start()
}
override fun onDestroyView() {
countDownTimer?.cancel()
countDownTimer = null
super.onDestroyView()
}
}
@@ -1,241 +1,241 @@
package com.sw.platecabinet.fragment
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.comn.SerialApi
import com.sw.plate.utils.comn.SerialPortManager
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R
import com.sw.platecabinet.activity.BaseActivity
import com.sw.platecabinet.activity.MainActivity
import com.sw.platecabinet.activity.PageType
import com.sw.platecabinet.adapter.GenericItemAdapter
import com.sw.platecabinet.adapter.GenericPageAdapter
import com.sw.platecabinet.adapter.GridSpacingItemDecoration
import com.sw.platecabinet.adapter.dpToPx
import com.sw.platecabinet.databinding.FragmentSettingListBinding
import com.sw.platecabinet.databinding.ItemBindViewBinding
import com.sw.platecabinet.ext.formatNumber
import com.sw.platecabinet.ext.maskName
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.utils.ListArrangementUtil
import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
/**
* 餐盘柜绑定的用户列表界面
*/
class SettingListFragment :
BaseFragment<FragmentSettingListBinding>(FragmentSettingListBinding::inflate) {
/**
* 需要横向滚动的adapter
*/
private var pageAdapter: GenericPageAdapter<EquipmentUserInfo, ItemBindViewBinding>? = null
/**
* 垂直滚动的adapter
*/
private var itemAdapter: GenericItemAdapter<EquipmentUserInfo, ItemBindViewBinding>? = null
override fun registerDataChange() {
super.registerDataChange()
// lifecycleScope.launch {
// viewModel.equipmentList.collect { it ->
// Timber.d("registerDateChange updateAdapter it = $it")
// updateAdapter(it)
// }
// }
}
override fun initialize() {
binding.mainRecyclerView.apply {
if (GlobalData.arrayCross > 2) {
layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
pageAdapter = createPageAdapter()
adapter = pageAdapter
} else {
layoutManager = GridLayoutManager(context, 2)
// 添加间距装饰(12dp)
addItemDecoration(
GridSpacingItemDecoration(
spanCount = 2,
spacing = requireContext().dpToPx(12),
includeEdge = true // 包含边缘间距
)
)
itemAdapter = createItemAdapter()
adapter = itemAdapter
}
}
}
override fun onResume() {
super.onResume()
// viewModel.getEquipmentList{
// updateAdapter(it)
// }
MyApp.plateList?.let {
updateAdapter(it)
}
}
// private fun getPlateData(block: (List<EquipmentUserInfo>) -> Unit) {
// MyApp.plateList?.let {
// block(it)
// }
//// viewModel.getEquipmentList { items ->
//// block(items)
//package com.sw.platecabinet.fragment
//
//import android.view.View
//import androidx.core.view.isVisible
//import androidx.lifecycle.lifecycleScope
//import androidx.recyclerview.widget.GridLayoutManager
//import androidx.recyclerview.widget.LinearLayoutManager
//import androidx.recyclerview.widget.RecyclerView
//import com.sw.inbound.utils.DateTimeUtils
//import com.sw.plate.utils.ToastUtils
//import com.sw.plate.utils.comn.SerialApi
//import com.sw.plate.utils.comn.SerialPortManager
//import com.sw.platecabinet.GlobalData
//import com.sw.platecabinet.MyApp
//import com.sw.platecabinet.R
//import com.sw.platecabinet.activity.BaseActivity
//import com.sw.platecabinet.activity.MainActivity
//import com.sw.platecabinet.activity.PageType
//import com.sw.platecabinet.adapter.GenericItemAdapter
//import com.sw.platecabinet.adapter.GenericPageAdapter
//import com.sw.platecabinet.adapter.GridSpacingItemDecoration
//import com.sw.platecabinet.adapter.dpToPx
//import com.sw.platecabinet.databinding.FragmentSettingListBinding
//import com.sw.platecabinet.databinding.ItemBindViewBinding
//import com.sw.platecabinet.ext.formatNumber
//import com.sw.platecabinet.ext.maskName
//import com.sw.platecabinet.model.response.EquipmentUserInfo
//import com.sw.platecabinet.utils.ListArrangementUtil
//import com.sw.platecabinet.utils.PlateUtils
//import com.sw.platecabinet.utils.SpTool
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.launch
//import kotlinx.coroutines.withContext
//import timber.log.Timber
//
///**
// * 餐盘柜绑定的用户列表界面
// */
//class SettingListFragment :
// BaseFragment<FragmentSettingListBinding>(FragmentSettingListBinding::inflate) {
//
// /**
// * 需要横向滚动的adapter
// */
// private var pageAdapter: GenericPageAdapter<EquipmentUserInfo, ItemBindViewBinding>? = null
//
// /**
// * 垂直滚动的adapter
// */
// private var itemAdapter: GenericItemAdapter<EquipmentUserInfo, ItemBindViewBinding>? = null
//
// override fun registerDataChange() {
// super.registerDataChange()
//// lifecycleScope.launch {
//// viewModel.equipmentList.collect { it ->
//// Timber.d("registerDateChange updateAdapter it = $it")
//// updateAdapter(it)
//// }
//// }
// }
private fun createItemAdapter(): GenericItemAdapter<EquipmentUserInfo, ItemBindViewBinding> {
return GenericItemAdapter(
items = emptyList(),
bindingInflater = ItemBindViewBinding::inflate,
bindCallback = { item, position ->
setItemInfo(item, position)
}
)
}
private fun createPageAdapter(): GenericPageAdapter<EquipmentUserInfo, ItemBindViewBinding> {
return GenericPageAdapter(
pages = emptyList(),
pageLayoutId = R.layout.page_item_layout,
itemBindingInflater = ItemBindViewBinding::inflate,
itemBindCallback = { item, position ->
setItemInfo(item, position)
}
)
}
private fun ItemBindViewBinding.setItemInfo(
item: EquipmentUserInfo,
position: Int
) {
// if (item.isPlaceholder()) {
// this.llRoot.isVisible = false
// return
// }
this.llRoot.isVisible = true
this.tvNum.text = formatNumber(item.equipmentBoxCode ?: "")
if (item.plateNumber.isNullOrBlank().not()) {
// val date = DateTimeUtils.parseDateTime(item.updateTime)
// val timeInMillis = date?.time ?: 0L
// val isOldTime = DateTimeUtils.isMoreThanHoursFromNow(timeInMillis)
this.llRoot.setBackgroundResource(R.drawable.grid_item_bind)
this.tvNum.setTextColor(resources.getColor(R.color.bind_4E535D))
// this.tvLastTime.setTextColor(
// if (isOldTime) resources.getColor(R.color.bind_time_old) else resources.getColor(
// R.color.bind_585868
// )
// )
this.llOpen.setBackgroundResource(R.drawable.grid_button_bind)
this.tvOpen.setTextColor(resources.getColor(R.color.bind_FFCC99))
this.llBindInfo.visibility = View.VISIBLE
this.tvUnbind.visibility = View.GONE
// this.tvName.text = item.name.maskName()
this.tvName.text = item.plateNumber
// this.tvLastTime.text = DateTimeUtils.getTimeAgo(date)
} else {
this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
this.tvNum.setTextColor(resources.getColor(R.color.bind_FFCC99))
this.llOpen.setBackgroundResource(R.drawable.grid_button_unbind)
this.tvOpen.setTextColor(resources.getColor(R.color.unbind_32283C))
this.llBindInfo.visibility = View.GONE
this.tvUnbind.visibility = View.VISIBLE
// this.tvUnbind.text = "未绑定"
}
// this.llRoot.setOnClickListener {
//// Timber.d("itemClick llRoot ${item.name}, position = $position")
// if (item.isBound()) {
// MainActivity.start(
// requireContext(),
// pageType = PageType.UNBIND_PLATE,
// equipmentUserInfo = item
// )
//
// override fun initialize() {
// binding.mainRecyclerView.apply {
// if (GlobalData.arrayCross > 2) {
// layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
// pageAdapter = createPageAdapter()
// adapter = pageAdapter
// } else {
// MainActivity.start(
// requireContext(),
// pageType = PageType.BIND_PLATE,
// equipmentUserInfo = item
// layoutManager = GridLayoutManager(context, 2)
// // 添加间距装饰(12dp)
// addItemDecoration(
// GridSpacingItemDecoration(
// spanCount = 2,
// spacing = requireContext().dpToPx(12),
// includeEdge = true // 包含边缘间距
// )
// )
// itemAdapter = createItemAdapter()
// adapter = itemAdapter
// }
// }
this.llOpen.setOnClickListener {
// item.faceId = null
PlateUtils.open(
fragmentActivity = requireActivity(),
code = item.equipmentBoxCode,
isScanCode = false,
isSetting = true
)
}
}
fun updateAdapter(items: List<EquipmentUserInfo>) {
val itemsPerPage = 2 * GlobalData.arrayVertical // 每行2个,每列11个,共22个
if (GlobalData.arrayCross > 2) {
lifecycleScope.launch(Dispatchers.Default) {
// 多recyclerview
var pages = if (items.size > itemsPerPage) {
if (GlobalData.arrayMode == 0) {
val pageList = items.chunked(itemsPerPage)
pageList.mapIndexed { index, it ->
var itemList: MutableList<EquipmentUserInfo> = it.toMutableList()
if (index == pageList.size - 1) {
// 补全剩余item
itemList = (itemList + List(itemsPerPage - itemList.size) {
EquipmentUserInfo()
}) as MutableList<EquipmentUserInfo>
}
ListArrangementUtil.convertToColumnFirst(
itemList,
GlobalData.arrayVertical
)
}
} else {
val newItems =
ListArrangementUtil.horizontalSortPageItem(items, GlobalData.arrayCross)
newItems.chunked(itemsPerPage)
}
} else {
listOf(
items
)
}
withContext(Dispatchers.Main) {
pageAdapter?.updatePages(pages)
}
}
} else {
// 单 recyclerview
lifecycleScope.launch(Dispatchers.Default) {
var pages = items
if (GlobalData.arrayMode == 0) {
pages = ListArrangementUtil.verticalSortItem(pages)
}
withContext(Dispatchers.Main) {
itemAdapter?.updateData(pages)
}
}
}
binding.mainRecyclerView.scrollToPosition(0)
}
}
//
// }
//
// override fun onResume() {
// super.onResume()
//// viewModel.getEquipmentList{
//// updateAdapter(it)
//// }
// MyApp.plateList?.let {
// updateAdapter(it)
// }
// }
//
//// private fun getPlateData(block: (List<EquipmentUserInfo>) -> Unit) {
//// MyApp.plateList?.let {
//// block(it)
//// }
////// viewModel.getEquipmentList { items ->
////// block(items)
////// }
//// }
//
// private fun createItemAdapter(): GenericItemAdapter<EquipmentUserInfo, ItemBindViewBinding> {
// return GenericItemAdapter(
// items = emptyList(),
// bindingInflater = ItemBindViewBinding::inflate,
// bindCallback = { item, position ->
// setItemInfo(item, position)
// }
// )
// }
//
// private fun createPageAdapter(): GenericPageAdapter<EquipmentUserInfo, ItemBindViewBinding> {
// return GenericPageAdapter(
// pages = emptyList(),
// pageLayoutId = R.layout.page_item_layout,
// itemBindingInflater = ItemBindViewBinding::inflate,
// itemBindCallback = { item, position ->
// setItemInfo(item, position)
// }
// )
// }
//
// private fun ItemBindViewBinding.setItemInfo(
// item: EquipmentUserInfo,
// position: Int
// ) {
//// if (item.isPlaceholder()) {
//// this.llRoot.isVisible = false
//// return
//// }
// this.llRoot.isVisible = true
// this.tvNum.text = formatNumber(item.equipmentBoxCode ?: "")
// if (item.plateNumber.isNullOrBlank().not()) {
//// val date = DateTimeUtils.parseDateTime(item.updateTime)
//// val timeInMillis = date?.time ?: 0L
//// val isOldTime = DateTimeUtils.isMoreThanHoursFromNow(timeInMillis)
//
// this.llRoot.setBackgroundResource(R.drawable.grid_item_bind)
// this.tvNum.setTextColor(resources.getColor(R.color.bind_4E535D))
//// this.tvLastTime.setTextColor(
//// if (isOldTime) resources.getColor(R.color.bind_time_old) else resources.getColor(
//// R.color.bind_585868
//// )
//// )
// this.llOpen.setBackgroundResource(R.drawable.grid_button_bind)
// this.tvOpen.setTextColor(resources.getColor(R.color.bind_FFCC99))
// this.llBindInfo.visibility = View.VISIBLE
// this.tvUnbind.visibility = View.GONE
//
//// this.tvName.text = item.name.maskName()
// this.tvName.text = item.plateNumber
//
//// this.tvLastTime.text = DateTimeUtils.getTimeAgo(date)
// } else {
// this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
// this.tvNum.setTextColor(resources.getColor(R.color.bind_FFCC99))
// this.llOpen.setBackgroundResource(R.drawable.grid_button_unbind)
// this.tvOpen.setTextColor(resources.getColor(R.color.unbind_32283C))
// this.llBindInfo.visibility = View.GONE
// this.tvUnbind.visibility = View.VISIBLE
//// this.tvUnbind.text = "未绑定"
// }
//// this.llRoot.setOnClickListener {
////// Timber.d("itemClick llRoot ${item.name}, position = $position")
//// if (item.isBound()) {
//// MainActivity.start(
//// requireContext(),
//// pageType = PageType.UNBIND_PLATE,
//// equipmentUserInfo = item
//// )
//// } else {
//// MainActivity.start(
//// requireContext(),
//// pageType = PageType.BIND_PLATE,
//// equipmentUserInfo = item
//// )
//// }
//// }
// this.llOpen.setOnClickListener {
//// item.faceId = null
// PlateUtils.open(
// fragmentActivity = requireActivity(),
// code = item.equipmentBoxCode,
// isScanCode = false,
// isSetting = true
// )
// }
// }
//
// fun updateAdapter(items: List<EquipmentUserInfo>) {
// val itemsPerPage = 2 * GlobalData.arrayVertical // 每行2个,每列11个,共22个
// if (GlobalData.arrayCross > 2) {
// lifecycleScope.launch(Dispatchers.Default) {
// // 多recyclerview
// var pages = if (items.size > itemsPerPage) {
// if (GlobalData.arrayMode == 0) {
// val pageList = items.chunked(itemsPerPage)
// pageList.mapIndexed { index, it ->
// var itemList: MutableList<EquipmentUserInfo> = it.toMutableList()
// if (index == pageList.size - 1) {
// // 补全剩余item
// itemList = (itemList + List(itemsPerPage - itemList.size) {
// EquipmentUserInfo()
// }) as MutableList<EquipmentUserInfo>
// }
// ListArrangementUtil.convertToColumnFirst(
// itemList,
// GlobalData.arrayVertical
// )
// }
// } else {
// val newItems =
// ListArrangementUtil.horizontalSortPageItem(items, GlobalData.arrayCross)
// newItems.chunked(itemsPerPage)
// }
// } else {
// listOf(
// items
// )
// }
// withContext(Dispatchers.Main) {
// pageAdapter?.updatePages(pages)
// }
// }
// } else {
// // 单 recyclerview
// lifecycleScope.launch(Dispatchers.Default) {
// var pages = items
// if (GlobalData.arrayMode == 0) {
// pages = ListArrangementUtil.verticalSortItem(pages)
// }
// withContext(Dispatchers.Main) {
// itemAdapter?.updateData(pages)
// }
// }
// }
// binding.mainRecyclerView.scrollToPosition(0)
// }
//}
@@ -1,19 +1,17 @@
package com.sw.platecabinet.model
import com.sw.platecabinet.model.response.EquipmentUserInfo
/**
* 错误信息 code == 200 成功
*/
data class ErrorInfo(
val code: String = "00000",
val msg: String = "",
val equipmentUserInfo: EquipmentUserInfo? = null
) {
fun isSuccess(): Boolean {
return code == "00000"
}
}
//data class ErrorInfo(
// val code: String = "00000",
// val msg: String = "",
// val equipmentUserInfo: EquipmentUserInfo? = null
//) {
// fun isSuccess(): Boolean {
// return code == "00000"
// }
//}
data class DeviceConfig(
var arcsoftAppId: String? = null,
@@ -1,72 +1,72 @@
package com.sw.platecabinet.model.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class EquipmentInfo(
@SerializedName("appPackageLocalUrl")
val appPackageLocalUrl: String? = "",
@SerializedName("appPackageUrl")
val appPackageUrl: String? = "",
@SerializedName("arcsoftActiveKey")
val arcsoftActiveKey: String? = "",
@SerializedName("arcsoftAppId")
val arcsoftAppId: String? = "",
@SerializedName("arcsoftSdkKey")
val arcsoftSdkKey: String? = "",
@SerializedName("arrayCross")
val arrayCross: Int? = 0,
@SerializedName("arrayMode")
val arrayMode: String? = "",
@SerializedName("arrayVertical")
val arrayVertical: Int? = 0,
@SerializedName("canteenId")
val canteenId: String? = "",
@SerializedName("canteenName")
val canteenName: String? = "",
@SerializedName("clientServerIp")
val clientServerIp: String? = "",
@SerializedName("createBy")
val createBy: String? = "",
@SerializedName("createTime")
val createTime: String? = "",
@SerializedName("customerName")
val customerName: String? = "",
@SerializedName("equipmentCode")
val equipmentCode: String? = "",
@SerializedName("equipmentName")
val equipmentName: String? = "",
@SerializedName("equipmentName_dictText")
val equipmentNameDictText: String? = "",
@SerializedName("id")
val id: String? = "",
@SerializedName("isSync")
val isSync: Int? = 0,
@SerializedName("mqName")
val mqName: String? = "",
@SerializedName("mqPassword")
val mqPassword: String? = "",
@SerializedName("orgCode")
val orgCode: String? = "",
@SerializedName("owningTrack")
val owningTrack: Int? = 0,
@SerializedName("owningTrackOrder")
val owningTrackOrder: Int? = 0,
@SerializedName("screenHtml")
val screenHtml: String? = "",
@SerializedName("serviceRequestAddress")
val serviceRequestAddress: String? = "",
@SerializedName("status")
val status: Int? = 0,
@SerializedName("sysOrgCode")
val sysOrgCode: String? = "",
@SerializedName("updateBy")
val updateBy: String? = "",
@SerializedName("updateTime")
val updateTime: String? = "",
@SerializedName("zhstServerIp")
val zhstServerIp: String? = ""
) : Parcelable
//package com.sw.platecabinet.model.response
//
//
//import android.os.Parcelable
//import com.google.gson.annotations.SerializedName
//import kotlinx.parcelize.Parcelize
//
//@Parcelize
//data class EquipmentInfo(
// @SerializedName("appPackageLocalUrl")
// val appPackageLocalUrl: String? = "",
// @SerializedName("appPackageUrl")
// val appPackageUrl: String? = "",
// @SerializedName("arcsoftActiveKey")
// val arcsoftActiveKey: String? = "",
// @SerializedName("arcsoftAppId")
// val arcsoftAppId: String? = "",
// @SerializedName("arcsoftSdkKey")
// val arcsoftSdkKey: String? = "",
// @SerializedName("arrayCross")
// val arrayCross: Int? = 0,
// @SerializedName("arrayMode")
// val arrayMode: String? = "",
// @SerializedName("arrayVertical")
// val arrayVertical: Int? = 0,
// @SerializedName("canteenId")
// val canteenId: String? = "",
// @SerializedName("canteenName")
// val canteenName: String? = "",
// @SerializedName("clientServerIp")
// val clientServerIp: String? = "",
// @SerializedName("createBy")
// val createBy: String? = "",
// @SerializedName("createTime")
// val createTime: String? = "",
// @SerializedName("customerName")
// val customerName: String? = "",
// @SerializedName("equipmentCode")
// val equipmentCode: String? = "",
// @SerializedName("equipmentName")
// val equipmentName: String? = "",
// @SerializedName("equipmentName_dictText")
// val equipmentNameDictText: String? = "",
// @SerializedName("id")
// val id: String? = "",
// @SerializedName("isSync")
// val isSync: Int? = 0,
// @SerializedName("mqName")
// val mqName: String? = "",
// @SerializedName("mqPassword")
// val mqPassword: String? = "",
// @SerializedName("orgCode")
// val orgCode: String? = "",
// @SerializedName("owningTrack")
// val owningTrack: Int? = 0,
// @SerializedName("owningTrackOrder")
// val owningTrackOrder: Int? = 0,
// @SerializedName("screenHtml")
// val screenHtml: String? = "",
// @SerializedName("serviceRequestAddress")
// val serviceRequestAddress: String? = "",
// @SerializedName("status")
// val status: Int? = 0,
// @SerializedName("sysOrgCode")
// val sysOrgCode: String? = "",
// @SerializedName("updateBy")
// val updateBy: String? = "",
// @SerializedName("updateTime")
// val updateTime: String? = "",
// @SerializedName("zhstServerIp")
// val zhstServerIp: String? = ""
//) : Parcelable
@@ -1,90 +1,90 @@
package com.sw.platecabinet.model.response
import android.os.Parcelable
import android.text.TextUtils
//import com.google.gson.annotations.SerializedName
import com.sw.platecabinet.GlobalData
import kotlinx.parcelize.Parcelize
/**
* 设备绑定用户信息
*/
@Parcelize
data class EquipmentUserInfo(
//package com.sw.platecabinet.model.response
//
//import android.os.Parcelable
//import android.text.TextUtils
////import com.google.gson.annotations.SerializedName
//import com.sw.platecabinet.GlobalData
//import kotlinx.parcelize.Parcelize
//
///**
// * 设备绑定用户信息
// */
//@Parcelize
//data class EquipmentUserInfo(
//// /**
//// * 主键
//// */
//// val id: Long? = 0,
//// /**
//// *
//// * 设备Id
//// */
//// val equipmentId: String? = "",
//// /**
//// * 设备编号
//// */
//// var equipmentCode: String? = "",
// /**
// * 主键
// * 设备盒子编号
// */
// val id: Long? = 0,
// var equipmentBoxCode: String? = "",
// /**
// * 会员Id
// */
// var faceId: String? = "",
// /**
// * 餐盘编号
// */
// var plateNumber: String? = "",
//// /**
//// * 设备名称
//// */
//// val equipmentName: String? = "",
//
//// val orderNo: String? = "",
// /**
// *
// * 设备Id
// * 更新时间
// */
// val equipmentId: String? = "",
// /**
// * 设备编号
// */
// var equipmentCode: String? = "",
/**
* 设备盒子编号
*/
var equipmentBoxCode: String? = "",
/**
* 会员Id
*/
var faceId: String? = "",
/**
* 餐盘编号
*/
var plateNumber: String? = "",
// /**
// * 设备名称
// */
// val equipmentName: String? = "",
// val orderNo: String? = "",
/**
*
* 更新时间
*/
var updateTime: String? = "",
// val name: String? = "",
// val phone: String? = "",
// val faceUrl: String? = "",
// val mealTime: String? = "",
// val mealTimeInterval: String? = "",
// val openTime: String? = "",
// val openTimeInterval: String? = "",
// val eatCount: Int? = 0,
// var updateTime: String? = "",
//
// var cardBalance: Double? = 0.toDouble(),
// /**
// * 用于cardBalance后续拦截判断
// */
// var showBalanceNotEnoughDialog: Boolean = false,
// var isIntercept: Boolean = false
) : Parcelable {
// /**
// * 是否已绑定
// */
// fun isBound(): Boolean {
// return faceId.isNullOrBlank().not()
// }
// /**
// * 是否是其他设备
// */
// fun isOtherEquipment(): Boolean {
// return equipmentCode != null && equipmentCode != "" && GlobalData.globalEquipmentCode != equipmentCode
// }
// fun hasEquipmentBox(): Boolean {
// return !TextUtils.isEmpty(equipmentBoxCode)
// }
//// val name: String? = "",
//// val phone: String? = "",
//// val faceUrl: String? = "",
//// val mealTime: String? = "",
//// val mealTimeInterval: String? = "",
//// val openTime: String? = "",
//// val openTimeInterval: String? = "",
//// val eatCount: Int? = 0,
////
//// var cardBalance: Double? = 0.toDouble(),
//
// fun isPlaceholder(): Boolean {
// return equipmentBoxCode.isNullOrBlank()
// }
}
//// /**
//// * 用于cardBalance后续拦截判断
//// */
//// var showBalanceNotEnoughDialog: Boolean = false,
//// var isIntercept: Boolean = false
//) : Parcelable {
//
//// /**
//// * 是否已绑定
//// */
//// fun isBound(): Boolean {
//// return faceId.isNullOrBlank().not()
//// }
//
//// /**
//// * 是否是其他设备
//// */
//// fun isOtherEquipment(): Boolean {
//// return equipmentCode != null && equipmentCode != "" && GlobalData.globalEquipmentCode != equipmentCode
//// }
//
//// fun hasEquipmentBox(): Boolean {
//// return !TextUtils.isEmpty(equipmentBoxCode)
//// }
////
//// fun isPlaceholder(): Boolean {
//// return equipmentBoxCode.isNullOrBlank()
//// }
//}
@@ -1,85 +1,85 @@
package com.sw.platecabinet.model.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
/**
* 搜索会员信息结果
*/
@Parcelize
data class SearchResult(
@SerializedName("endRow")
val endRow: Int? = 0,
@SerializedName("hasNextPage")
val hasNextPage: Boolean? = false,
@SerializedName("hasPreviousPage")
val hasPreviousPage: Boolean? = false,
@SerializedName("isFirstPage")
val isFirstPage: Boolean? = false,
@SerializedName("isLastPage")
val isLastPage: Boolean? = false,
@SerializedName("list")
val list: List<Member>? = listOf(),
@SerializedName("navigateFirstPage")
val navigateFirstPage: Int? = 0,
@SerializedName("navigateLastPage")
val navigateLastPage: Int? = 0,
@SerializedName("navigatePages")
val navigatePages: Int? = 0,
@SerializedName("navigatepageNums")
val navigatepageNums: List<Int?>? = listOf(),
@SerializedName("nextPage")
val nextPage: Int? = 0,
@SerializedName("pageNum")
val pageNum: Int? = 0,
@SerializedName("pageSize")
val pageSize: Int? = 0,
@SerializedName("pages")
val pages: Int? = 0,
@SerializedName("prePage")
val prePage: Int? = 0,
@SerializedName("size")
val size: Int? = 0,
@SerializedName("startRow")
val startRow: Int? = 0,
@SerializedName("total")
val total: Int? = 0
) : Parcelable {
@Parcelize
data class Member(
// @SerializedName("cardBalance")
// val cardBalance: String? = "",
// @SerializedName("cardCode")
// val cardCode: String? = "",
// @SerializedName("consumptionCount")
// val consumptionCount: Int? = 0,
// @SerializedName("consumptionTotal")
// val consumptionTotal: String? = "",
// @SerializedName("createTime")
// val createTime: String? = "",
// @SerializedName("firstTopUpTime")
// val firstTopUpTime: String? = "",
// @SerializedName("id")
// val id: Int? = 0,
// @SerializedName("integralBalance")
// val integralBalance: Int? = 0,
// @SerializedName("lastConsumptionTime")
// val lastConsumptionTime: String? = "",
// @SerializedName("memberFrom")
// val memberFrom: Int? = 0,
// @SerializedName("name")
//package com.sw.platecabinet.model.response
//
//
//import android.os.Parcelable
//import com.google.gson.annotations.SerializedName
//import kotlinx.parcelize.Parcelize
//
///**
// * 搜索会员信息结果
// */
//@Parcelize
//data class SearchResult(
// @SerializedName("endRow")
// val endRow: Int? = 0,
// @SerializedName("hasNextPage")
// val hasNextPage: Boolean? = false,
// @SerializedName("hasPreviousPage")
// val hasPreviousPage: Boolean? = false,
// @SerializedName("isFirstPage")
// val isFirstPage: Boolean? = false,
// @SerializedName("isLastPage")
// val isLastPage: Boolean? = false,
// @SerializedName("list")
// val list: List<Member>? = listOf(),
// @SerializedName("navigateFirstPage")
// val navigateFirstPage: Int? = 0,
// @SerializedName("navigateLastPage")
// val navigateLastPage: Int? = 0,
// @SerializedName("navigatePages")
// val navigatePages: Int? = 0,
// @SerializedName("navigatepageNums")
// val navigatepageNums: List<Int?>? = listOf(),
// @SerializedName("nextPage")
// val nextPage: Int? = 0,
// @SerializedName("pageNum")
// val pageNum: Int? = 0,
// @SerializedName("pageSize")
// val pageSize: Int? = 0,
// @SerializedName("pages")
// val pages: Int? = 0,
// @SerializedName("prePage")
// val prePage: Int? = 0,
// @SerializedName("size")
// val size: Int? = 0,
// @SerializedName("startRow")
// val startRow: Int? = 0,
// @SerializedName("total")
// val total: Int? = 0
//) : Parcelable {
// @Parcelize
// data class Member(
//// @SerializedName("cardBalance")
//// val cardBalance: String? = "",
//// @SerializedName("cardCode")
//// val cardCode: String? = "",
//// @SerializedName("consumptionCount")
//// val consumptionCount: Int? = 0,
//// @SerializedName("consumptionTotal")
//// val consumptionTotal: String? = "",
//// @SerializedName("createTime")
//// val createTime: String? = "",
//// @SerializedName("firstTopUpTime")
//// val firstTopUpTime: String? = "",
//// @SerializedName("id")
//// val id: Int? = 0,
//// @SerializedName("integralBalance")
//// val integralBalance: Int? = 0,
//// @SerializedName("lastConsumptionTime")
//// val lastConsumptionTime: String? = "",
//// @SerializedName("memberFrom")
//// val memberFrom: Int? = 0,
//// @SerializedName("name")
//// val name: String? = "",
//// @SerializedName("phone")
//// val phone: String? = "",
//// @SerializedName("rewardBalance")
//// val rewardBalance: String? = "",
//// @SerializedName("topUpBalance")
//// val topUpBalance: String? = ""
// val id: String? = "",
// val faceId: String? = "",
// val name: String? = "",
// @SerializedName("phone")
// val phone: String? = "",
// @SerializedName("rewardBalance")
// val rewardBalance: String? = "",
// @SerializedName("topUpBalance")
// val topUpBalance: String? = ""
val id: String? = "",
val faceId: String? = "",
val name: String? = "",
val phone: String? = "",
) : Parcelable
}
// ) : Parcelable
//}
@@ -2,7 +2,6 @@ package com.sw.platecabinet.model.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
/**
@@ -38,5 +37,5 @@ data class UserFaceModel(
/**
* 是否会员
*/
val isMember: Boolean
val member: Boolean
) : Parcelable
@@ -12,37 +12,29 @@ import timber.log.Timber
import java.util.concurrent.TimeUnit
object ApiClient {
//private const val BASE_URL = "https://vip.shuziweidao.com"
// private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889/"
private var DEVICE_BASE_URL = GlobalData.appBaseUrl
// private const val BASE_URL = "http://192.168.1.8:9092"
// private const val TIME_OUT = 30L // 超时时间(秒)
private const val TIME_OUT = 60L // 超时时间(秒)
val okHttpClient = OkHttpClient.Builder()
private const val TIME_OUT = 60L // 超时时间(秒)
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Timber.d("okhttp logger ==>${it}")
}).apply {
level = if (MyApp.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
.addNetworkInterceptor(HttpLoggingInterceptor { Timber.d("okhttp ==>${it}") }.apply {
level = if (MyApp.DEBUG) HttpLoggingInterceptor.Level.BODY
else HttpLoggingInterceptor.Level.NONE
})
.addInterceptor(RequestInterceptor())
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(DEVICE_BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
// .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 协程适配器
.build()
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(GlobalData.appBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val apiService: ApiService by lazy {
retrofit.create(ApiService::class.java)
}
}
}
@@ -2,173 +2,28 @@ package com.sw.platecabinet.network.api
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.BindParam
import com.sw.platecabinet.model.request.EquipmentParam
import com.sw.platecabinet.model.request.LoginParam
import com.sw.platecabinet.model.request.SearchParam
import com.sw.platecabinet.model.response.ApiResponse
import com.sw.platecabinet.model.response.EquipmentInfo
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel
import okhttp3.MultipartBody
import retrofit2.http.Body
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
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 = GlobalData.appVersion
// ): ApiResponse<String>
//
// /**
// *获取配置信息
// */
// @GET("equipment/stEquipment/queryByEquipmentCode")
// suspend fun getDeviceInfo(
// @Query("equipmentCode") equipmentCode: String,
// @Query("appVersion") appVersion: String = GlobalData.appVersion,
// @Header("X-Access-Token") token: String
// ): ApiResponse<EquipmentInfo>
//
// /**
// * 生成token
// */
// @GET//("/shuwei-zhct/scales/generateToken")
// suspend fun generateToken(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/generateToken",
// @Query("deviceId") deviceId: String,
// @Query("appVersion") appVersion: String,
// @Query("equipmentCode") equipmentCode: String
// ): ApiResponse<String>
/**
* 获取人脸数据
* 获取人脸缓存数据
*/
// @GET//("/shuwei-zhct/scales/getUserFaceCache")
// suspend fun getUserFaceCache(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/getUserFaceCache",
// @Query("appVersion") appVersion: String,
// @Query("equipmentCode") equipmentCode: String
// ): ApiResponse<List<UserFaceModel>>
@POST
suspend fun getUserFaceCache(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/list",
@Body param: Map<String, Int>
): ApiResponse<List<UserFaceModel>>
// /**
// * 餐盘用户信息获取
// */
// @POST//("/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin")
// suspend fun equipmentBoxLogin(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin",
// @Body param: LoginParam
// ): ApiResponse<EquipmentUserInfo>
/**
* 餐盘用户信息获取
*/
@POST//("/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin")
suspend fun equipmentBoxLogin(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/getPlateBoxUserInfo",
@Body param: LoginParam
): ApiResponse<EquipmentUserInfo>
/**
* 餐盘用户信息列表查询
*/
// @POST//("/shuwei-zhct/swEquipmentRelUser/list")
// suspend fun getEquipmentList(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/list",
// @Body param: EquipmentParam
// ): ApiResponse<List<EquipmentUserInfo>>
@GET
suspend fun getEquipmentList(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/getYxMemberRefPlateByEquipmentCode",
): ApiResponse<List<EquipmentUserInfo>>
// /**
// * 餐盘用户信息绑定解绑
// */
// @POST//("/shuwei-zhct/swEquipmentRelUser/addOrEdit")
// suspend fun bindEquipment(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/addOrEdit",
// @Body param: BindParam
// ): ApiResponse<EquipmentUserInfo?>
/**
* 餐盘绑定
*/
@POST
suspend fun plateBind(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/plateBinding",
@Body param: BindParam
): ApiResponse<EquipmentUserInfo?>
/**
* 餐盘解绑
*/
@POST
@FormUrlEncoded
suspend fun plateUnbind(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/plateUnbind",
@Field("id") id: Long?
): ApiResponse<Any?>
/**
* 用户信息模糊搜索
*/
@POST//("/shuwei-user/swclientUserInfoShop/selectList")
suspend fun searchUser(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-user/swclientUserInfoShop/selectList",
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getUserInfoByNameOrPhone",
@Body param: SearchParam
): ApiResponse<List<SearchResult.Member>?>
// /**
// * 通过餐盘号获取信息
// */
// @POST//("/shuwei-zhct/swEquipmentRelUser/findByPlateNumber")
// suspend fun findByPlateNumber(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/findByPlateNumber",
// @Body param: BindParam
// ): ApiResponse<EquipmentUserInfo>
/**
* 通过餐盘号获取信息
*/
@GET
suspend fun findByPlateNumber(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/getMemberRefPlateByPlateNumber",
@Query("plateNumber") plateNumber: String
): ApiResponse<EquipmentUserInfo>
/**
* 获取人脸增量数据
*
* @param timeDate 时间戳(毫秒级)
*/
// @GET//("/shuwei-zhct/scales/getHeartbeatInterface")
// suspend fun getHeartbeatInterface(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/getHeartbeatInterface",
// @Query("timeDate") timeDate: Long
// ): ApiResponse<List<UserFaceModel>>
/**
* 获取人脸增量数据
*/
@@ -193,10 +48,13 @@ interface ApiService {
@FormUrlEncoded
suspend fun addUserFace(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/addUserByFace",
@Field("featureChar") faceData:String,
@Field("url") imageUr:String
@Field("featureChar") faceData: String,
@Field("url") imageUr: String
): ApiResponse<UserFaceModel?>
/**
* 上传图片
*/
@Multipart
@POST
suspend fun uploadImage(
@@ -1,10 +1,7 @@
package com.sw.platecabinet.repository
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.SearchParam
import com.sw.platecabinet.model.response.ApiResponse
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.network.api.ApiService
import com.sw.platecabinet.utils.FileUtils
@@ -17,42 +14,6 @@ class RemoteRepository constructor(
private val apiService: ApiService
) : BaseRepository() {
// /**
// * 生成token
// */
// suspend fun getDeviceToken(qrcodeId: String): ApiResponse<String> {
// return safeApiCall {
// apiService.getDeviceToken(
// qrcodeId
// )
// }
// }
//
// /**
// * 获取设备信息
// */
// suspend fun getDeviceInfo(equipmentCode: String, token: String): ApiResponse<EquipmentInfo> {
// return safeApiCall {
// apiService.getDeviceInfo(
// equipmentCode,
// token = token
// )
// }
// }
//
// /**
// * 生成token
// */
// suspend fun generateToken(deviceId: String): ApiResponse<String> {
// return safeApiCall {
// apiService.generateToken(
// deviceId = deviceId,
// appVersion = GlobalData.appVersion,
// equipmentCode = GlobalData.globalEquipmentCode
// )
// }
// }
/**
* 获取人脸数据
*/
@@ -70,57 +31,6 @@ class RemoteRepository constructor(
}
}
// /**
// * 登录
// */
// suspend fun equipmentBoxLogin(param: LoginParam): ApiResponse<EquipmentUserInfo> {
// return safeApiCall { apiService.equipmentBoxLogin(param = param) }
// }
// /**
// * 获取设备绑定用户列表
// */
// suspend fun getEquipmentList(): ApiResponse<List<EquipmentUserInfo>> {
// return safeApiCall { apiService.getEquipmentList() }
// }
// /**
// * 餐盘用户信息绑定解绑
// */
// suspend fun bindEquipment(param: BindParam): ApiResponse<EquipmentUserInfo?> {
// return safeApiCall { apiService.bindEquipment(param = param) }
// }
// suspend fun plateBind(param: BindParam): ApiResponse<EquipmentUserInfo?> {
// return safeApiCall { apiService.plateBind(param = param) }
// }
// suspend fun plateUnbind(id: Long?): ApiResponse<Any?> {
// return safeApiCall { apiService.plateUnbind(id = id) }
// }
/**
* 用户信息模糊搜索
*/
suspend fun searchUser(param: SearchParam): ApiResponse<List<SearchResult.Member>?> {
return safeApiCall { apiService.searchUser(param = param) }
}
/**
* 通过餐盘编号获取绑定信息
*/
suspend fun findByPlateNumber(plateNumber: String): ApiResponse<EquipmentUserInfo> {
return safeApiCall { apiService.findByPlateNumber(plateNumber = plateNumber) }
}
// /**
// * 获取人脸增量数据
// *
// * @param timeDate 时间戳(毫秒级)
// */
// suspend fun getHeartbeatInterface(timeDate: Long): ApiResponse<List<UserFaceModel>> {
// return safeApiCall { apiService.getHeartbeatInterface(timeDate = timeDate) }
// }
suspend fun getFaceIncrementList(
pageNum: Long,
@@ -142,7 +52,7 @@ class RemoteRepository constructor(
return safeApiCall { apiService.getDeviceConfig() }
}
suspend fun addUserFace(faceData: String, imageUr:String): ApiResponse<UserFaceModel?> {
suspend fun addUserFace(faceData: String, imageUr: String): ApiResponse<UserFaceModel?> {
return safeApiCall { apiService.addUserFace(faceData = faceData, imageUr = imageUr) }
}
@@ -0,0 +1,79 @@
package com.sw.platecabinet.utils
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.util.concurrent.atomic.AtomicBoolean
/**
* 基于协程的倒计时工具类(实例化使用,各调用方持有独立实例,互不干扰)
* 修复:onCompletion 区分正常结束与取消,手动取消时不触发 onFinish
*/
class CountDownUtil {
private val isCounting = AtomicBoolean(false)
private var currentJob: Job? = null
/**
* 启动倒计时(若已在运行则忽略,需重启请先调用 cancelCountDown
* @param total 总秒数
* @param lifecycleScope Activity/Fragment 的 lifecycleScope
* @param onStart 启动回调(主线程)
* @param onTick 每秒回调剩余秒数(主线程)
* @param onFinish 正常结束回调(主线程,手动取消时不触发)
*/
fun startCountDown(
total: Int = 15,
lifecycleScope: CoroutineScope,
onStart: () -> Unit = {},
onTick: (Int) -> Unit = {},
onFinish: () -> Unit
) {
if (isCounting.get()) return
isCounting.set(true)
currentJob = flow {
for (i in total downTo 1) {
emit(i)
delay(1000L)
}
}
.flowOn(Dispatchers.IO)
.onStart {
withContext(Dispatchers.Main) { onStart() }
}
.onEach { remaining ->
withContext(Dispatchers.Main) { onTick(remaining) }
}
.onCompletion { cause ->
withContext(Dispatchers.Main) {
isCounting.set(false)
currentJob = null
// cause == null 为正常结束,有异常(含 CancellationException)时不触发 onFinish
if (cause == null) {
Log.d("CountDownUtil", "倒计时正常结束")
onFinish()
} else {
Log.d("CountDownUtil", "倒计时被取消:${cause.message}")
}
}
}
.catch { e ->
Log.e("CountDownUtil", "倒计时异常:${e.message}", e)
isCounting.set(false)
currentJob = null
}
.launchIn(lifecycleScope)
}
/**
* 取消倒计时(不触发 onFinish)
*/
fun cancelCountDown() {
currentJob?.cancel()
currentJob = null
isCounting.set(false)
Log.d("CountDownUtil", "倒计时已取消")
}
}
@@ -154,7 +154,7 @@ class CrashHandler private constructor(private val context: Context) :
// 设备信息
sb.append("Vendor: ${Build.MANUFACTURER}\n")
sb.append("Model: ${Build.MODEL}\n")
sb.append("CPU ABI: ${Build.SUPPORTED_ABIS[0]}\n")
sb.append("CPU ABI: ${Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"}\n")
// 其他信息
sb.append("Locale: ${Locale.getDefault()}\n")
@@ -28,10 +28,10 @@ object DateTimeUtils {
}
/**
* 获取带时间的完整中文格式(示例:2025年6月11日 星期三 14:30
* 获取带时间的完整中文格式(示例:2025年06月11日 星期三 14:30
*/
fun getChineseDateTimeString(date: Date = Date()): String {
return SimpleDateFormat("yyyy年Md日 EEEE HH:mm:ss", Locale.CHINA).format(date)
return SimpleDateFormat("yyyy年MM月dd日 EEEE HH:mm", Locale.CHINA).format(date)
}
// 使用线程安全的日期格式化(避免 SimpleDateFormat 的线程安全问题)
@@ -52,7 +52,7 @@ object DateTimeUtils {
*/
fun realTimeChineseDateFlow(intervalMillis: Long = 1000) = flow {
while (true) {
emit(getChineseDateTimePair())
emit(getChineseDateTimeString())
delay(intervalMillis)
}
}
@@ -1,6 +1,11 @@
package com.sw.platecabinet.utils
import timber.log.Timber
class Debouncer(private val delayMillis: Long) {
companion object {
const val TAG = "Debouncer"
}
private var lastActionTime = 0L
/**
@@ -8,13 +13,15 @@ class Debouncer(private val delayMillis: Long) {
* @param action 要执行的操作
* @return Boolean 是否执行了操作 (true=已执行, false=被防抖)
*/
fun debounce(action: () -> Unit): Boolean {
fun debounce(tag:String = TAG, action: () -> Unit): Boolean {
val currentTime = System.currentTimeMillis()
if (currentTime - lastActionTime >= delayMillis) {
Timber.tag(tag).d("collectFace,recognizeUserId防止重复操作:执行action回调")
lastActionTime = currentTime
action()
return true
}
Timber.tag(tag).d("collectFace,recognizeUserId防止重复操作-----------------------")
return false
}
@@ -1,88 +1,88 @@
package com.sw.platecabinet.utils
import com.sw.platecabinet.model.response.EquipmentUserInfo
import timber.log.Timber
import kotlin.math.ceil
/**
* 列表排序工具类
*/
object ListArrangementUtil {
/**
* page item水平排序
*/
fun horizontalSortPageItem(
list: List<EquipmentUserInfo>,
columns: Int,
defaultValue: EquipmentUserInfo = EquipmentUserInfo()
): List<EquipmentUserInfo> {
Timber.d("horizontalSortPageItem")
require(columns > 0) { "Columns must be positive" }
val filledList = list.padToMultiple(columns, defaultValue)
val chunks = filledList.chunked(columns)
return (0 until columns)
.windowed(2, 2, partialWindows = true)
.flatMap { group ->
when (group.size) {
2 -> chunks.flatMap { listOf(it[group[0]], it[group[1]]) }
1 -> chunks.flatMap {
listOf(it[group[0]], EquipmentUserInfo(equipmentBoxCode = null))
}.dropLast(1)
else -> error("Unexpected group size")
}
}
}
/**
* item 垂直排序
*/
fun verticalSortItem(list: List<EquipmentUserInfo>): List<EquipmentUserInfo> {
Timber.d("verticalSortItem")
val users = list.padToEvenSize()
val half = users.size / 2
return users.take(half)
.zip(users.drop(half)) { a, b -> listOf(a, b) }
.flatten()
}
/**
* page item垂直排序
*/
fun convertToColumnFirst(
original: List<EquipmentUserInfo>,
cols: Int
): List<EquipmentUserInfo> {
Timber.d("convertToColumnFirst")
require(cols > 0) { "Columns must be positive" }
val rows = ceil(original.size.toDouble() / cols).toInt()
return List(original.size) { pos ->
original.getOrNull(pos % rows * cols + pos / rows) ?: EquipmentUserInfo()
}
}
// 提取的扩展函数
private fun List<EquipmentUserInfo>.padToMultiple(
multiple: Int,
defaultValue: EquipmentUserInfo
): List<EquipmentUserInfo> {
return if (size % multiple != 0) {
this + List(multiple - (size % multiple)) { defaultValue }
} else {
this
}
}
private fun List<EquipmentUserInfo>.padToEvenSize(): List<EquipmentUserInfo> {
return if (size % 2 != 0) {
Timber.d("列表长度必须是偶数")
this + EquipmentUserInfo()
} else {
this
}
}
}
//package com.sw.platecabinet.utils
//
//import com.sw.platecabinet.model.response.EquipmentUserInfo
//import timber.log.Timber
//import kotlin.math.ceil
//
///**
// * 列表排序工具类
// */
//object ListArrangementUtil {
//
// /**
// * page item水平排序
// */
// fun horizontalSortPageItem(
// list: List<EquipmentUserInfo>,
// columns: Int,
// defaultValue: EquipmentUserInfo = EquipmentUserInfo()
// ): List<EquipmentUserInfo> {
// Timber.d("horizontalSortPageItem")
// require(columns > 0) { "Columns must be positive" }
//
// val filledList = list.padToMultiple(columns, defaultValue)
// val chunks = filledList.chunked(columns)
//
// return (0 until columns)
// .windowed(2, 2, partialWindows = true)
// .flatMap { group ->
// when (group.size) {
// 2 -> chunks.flatMap { listOf(it[group[0]], it[group[1]]) }
// 1 -> chunks.flatMap {
// listOf(it[group[0]], EquipmentUserInfo(equipmentBoxCode = null))
// }.dropLast(1)
// else -> error("Unexpected group size")
// }
// }
// }
//
// /**
// * item 垂直排序
// */
// fun verticalSortItem(list: List<EquipmentUserInfo>): List<EquipmentUserInfo> {
// Timber.d("verticalSortItem")
// val users = list.padToEvenSize()
// val half = users.size / 2
//
// return users.take(half)
// .zip(users.drop(half)) { a, b -> listOf(a, b) }
// .flatten()
// }
//
// /**
// * page item垂直排序
// */
// fun convertToColumnFirst(
// original: List<EquipmentUserInfo>,
// cols: Int
// ): List<EquipmentUserInfo> {
// Timber.d("convertToColumnFirst")
// require(cols > 0) { "Columns must be positive" }
//
// val rows = ceil(original.size.toDouble() / cols).toInt()
// return List(original.size) { pos ->
// original.getOrNull(pos % rows * cols + pos / rows) ?: EquipmentUserInfo()
// }
// }
//
// // 提取的扩展函数
// private fun List<EquipmentUserInfo>.padToMultiple(
// multiple: Int,
// defaultValue: EquipmentUserInfo
// ): List<EquipmentUserInfo> {
// return if (size % multiple != 0) {
// this + List(multiple - (size % multiple)) { defaultValue }
// } else {
// this
// }
// }
//
// private fun List<EquipmentUserInfo>.padToEvenSize(): List<EquipmentUserInfo> {
// return if (size % 2 != 0) {
// Timber.d("列表长度必须是偶数")
// this + EquipmentUserInfo()
// } else {
// this
// }
// }
//}
@@ -19,10 +19,15 @@ import java.io.StringReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LogFileUtil {
private static boolean isSaveLog = true;
public static final String FILE_STR_PATH = App.getContext().getExternalCacheDir().getAbsolutePath();
// 使用内部存储,不会被系统缓存清理策略影响
public static final String FILE_STR_PATH = new File(App.getContext().getFilesDir(), "logs").getAbsolutePath();
// 单线程执行器,保证日志串行写入,避免多线程并发竞争
private static final ExecutorService LOG_EXECUTOR = Executors.newSingleThreadExecutor();
public static final int SIZETYPE_B = 1;//获取文件大小单位为B的double值
public static final int SIZETYPE_KB = 2;//获取文件大小单位为KB的double值
@@ -610,24 +615,20 @@ public class LogFileUtil {
if (!isSaveLog) {
return;
}
new Thread() {
@Override
public void run() {
super.run();
StringBuilder sb = new StringBuilder();
for (String log : logs) {
sb.append("===")
.append((AppUtil.formatDateGetCurrentTime()))
.append("===")
.append("\n")
.append(log)
.append("\n");
}
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
LogFileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
// 提交到单线程队列,串行执行,避免并发写入竞争
LOG_EXECUTOR.execute(() -> {
StringBuilder sb = new StringBuilder();
for (String log : logs) {
sb.append("===")
.append((AppUtil.formatDateGetCurrentTime()))
.append("===")
.append("\n")
.append(log)
.append("\n");
}
}.start();
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
LogFileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
});
}
private static final String[][] MIME_MapTable = {
@@ -77,11 +77,7 @@ class PermissionHelper private constructor(
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", context.packageName, null)
}
if (context is Activity) {
context.startActivity(intent)
} else if (context is Fragment) {
context.startActivity(intent)
}
context.startActivity(intent)
}
}
@@ -1,13 +1,7 @@
package com.sw.platecabinet.utils
import androidx.fragment.app.FragmentActivity
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.comn.SerialApi
import com.sw.plate.utils.comn.SerialPortManager
import com.sw.platecabinet.activity.BaseActivity
import com.sw.platecabinet.activity.MainActivity
import com.sw.platecabinet.activity.PageType
import timber.log.Timber
object PlateUtils {
@@ -15,36 +9,58 @@ object PlateUtils {
fragmentActivity: FragmentActivity,
code: String?,
isScanCode: Boolean,
isSetting: Boolean = false
isSetting: Boolean = false,
callback: () -> Unit = {}
) {
val activity = fragmentActivity as BaseActivity<*>
Timber.d("itemClick llOpen equipmentBoxCode = ${code}")
val equipmentBoxCode: Int = code?.toInt() ?: -1
if (equipmentBoxCode < 0) {
ToastUtils.showToast("餐盘柜编号错误")
return
}
activity.showWaitingDialog("请稍等")
SerialApi.openPlate(equipmentBoxCode, object : SerialPortManager.SendCallback {
override fun onSuccess() {
activity.hideWaitingDialog()
if (isSetting.not()) {
MainActivity.start(
activity,
pageType = PageType.PLATE_TIP,
isScanCode = isScanCode
)
} else {
ToastUtils.showToast("柜门打开成功")
}
}
override fun onFail(e: Exception?) {
activity.hideWaitingDialog()
ToastUtils.showToast("柜门打开失败")
}
})
activity.window.decorView.postDelayed({
callback()
}, 1500)
// activity.showWaitingDialog("请稍等")
// Timber.d("itemClick llOpen equipmentBoxCode = ${code}")
// val equipmentBoxCode: Int = code?.toInt() ?: -1
// if (equipmentBoxCode < 0) {
// ToastUtils.showToast("餐盘柜编号错误")
// return
// }
// activity.showWaitingDialog("请稍等")
// SerialApi.openPlate(equipmentBoxCode, object : SerialPortManager.SendCallback {
// override fun onSuccess() {
// activity.hideWaitingDialog()
// if (isSetting.not()) {
// MainActivity.start(
// activity,
// pageType = PageType.PLATE_TIP,
// isScanCode = isScanCode
// )
// } else {
// ToastUtils.showToast("柜门打开成功")
// callback()
// }
// }
//
// override fun onFail(e: Exception?) {
// activity.hideWaitingDialog()
// ToastUtils.showToast("柜门打开失败")
// callback()
// }
// })
}
// fun openDirect(boxCode: String?) {
// val boxNumber = boxCode?.toIntOrNull() ?: -1
// if (boxNumber < 0) return
// SerialApi.openPlate(boxNumber, object : SerialPortManager.SendCallback {
// override fun onSuccess() {
//
// }
//
// override fun onFail(e: Exception?) {
//
// }
// })
// }
}
@@ -1,15 +1,16 @@
package com.sw.platecabinet.utils
import com.google.gson.Gson
import com.sw.inbound.utils.GsonUtils
import com.sw.inbound.utils.SPUtil
import com.sw.inbound.utils.SPUtil.Companion.getInstance
import com.sw.platecabinet.GlobalKey
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.model.response.EquipmentUserInfo
object SpTool {
const val LAST_FACE_TIMESTAMP: String = "faceTimestamp"
const val PLATE_BOX_DATA: String = "plateBoxBind"
const val IS_FIRST_GET_FACE = "isFirstGetFace"
private var spUtil: SPUtil? = null
init {
@@ -26,17 +27,29 @@ object SpTool {
spUtil?.put(LAST_FACE_TIMESTAMP, timestamp)
}
fun savePlateData(items: List<EquipmentUserInfo>) {
val jsonData = Gson().toJson(items)
putString(PLATE_BOX_DATA, jsonData)
}
// fun savePlateData(items: List<EquipmentUserInfo>) {
// val jsonData = Gson().toJson(items)
// putString(PLATE_BOX_DATA, jsonData)
// }
//
// fun getPlateData(): List<EquipmentUserInfo>? {
// val jsonData = spUtil?.get(PLATE_BOX_DATA, "")
// if (jsonData.isNullOrBlank()) {
// return null
// }
// return GsonUtils.fromJsonList(jsonData, EquipmentUserInfo::class.java)
// }
fun getPlateData(): List<EquipmentUserInfo>? {
val jsonData = spUtil?.get(PLATE_BOX_DATA, "")
if (jsonData.isNullOrBlank()) {
return null
var firstGetFace: Boolean
get() = spUtil?.get(IS_FIRST_GET_FACE, true)?:true
set(value) {
spUtil?.boolean(IS_FIRST_GET_FACE, value)
}
var baseUrl: String
get() = spUtil?.get(GlobalKey.KEY_BASE_URL, "") ?: ""
set(value) {
spUtil?.put(GlobalKey.KEY_BASE_URL, value)
}
return GsonUtils.fromJsonList(jsonData, EquipmentUserInfo::class.java)
}
}
@@ -0,0 +1,16 @@
package com.sw.platecabinet.utils.mego
/**
* PLC 操作统一回调接口
*/
interface PlcCallback<T> {
/**
* 操作成功回调(主线程)
*/
fun onSuccess(result: T)
/**
* 操作失败回调(主线程)
*/
fun onError(message: String)
}
@@ -0,0 +1,152 @@
package com.sw.platecabinet.utils.mego
import android.content.Context
import android.os.Handler
import android.os.Looper
import com.megoai.plc_aar.SDK
/**
* PLC 操作工具类(单例)
* 封装 SDK 所有接口,统一回调到主线程
*/
class PlcHelper private constructor(context: Context) {
private val sdk: SDK = SDK.getInstance(context)
private val mainHandler = Handler(Looper.getMainLooper())
companion object {
@Volatile
private var instance: PlcHelper? = null
/**
* 获取单例实例
*/
fun getInstance(context: Context): PlcHelper {
return instance ?: synchronized(this) {
instance ?: PlcHelper(context.applicationContext).also { instance = it }
}
}
}
// ==================== 连接与模式 ====================
/**
* 打开出盘串口(应用启动时调用)
*/
fun openSerialPort(callback: PlcCallback<String>) {
sdk.openPlcSerialPort { result ->
mainHandler.post { callback.onSuccess(result.toString()) }
}
}
/**
* 打开自动模式(应用启动时调用一次)
*/
fun openAutoMode(callback: PlcCallback<String>) {
sdk.actionOpenAutoMode { result ->
mainHandler.post { callback.onSuccess(result.toString()) }
}
}
/**
* 故障复位(出现 alarmCode 或补盘完成后调用)
*/
fun alarmReset(callback: PlcCallback<String>) {
sdk.actionAlarmReset { result ->
mainHandler.post { callback.onSuccess(result.toString()) }
}
}
// ==================== 出盘操作 ====================
/**
* 出盘
*/
fun outPlate(callback: PlcCallback<String>) {
sdk.actionOutPlate { result ->
mainHandler.post { callback.onSuccess(result.toString()) }
}
}
// ==================== 平台控制 ====================
/**
* 平台下降(开始补盘)
*/
fun platformDown(callback: PlcCallback<String>) {
sdk.actionPlatformDown { result ->
mainHandler.post { callback.onSuccess(result.toString()) }
}
}
/**
* 平台上升(补盘完成)
*/
fun platformUp(callback: PlcCallback<String>) {
sdk.actionPlatformDownStop { result ->
mainHandler.post { callback.onSuccess(result.toString()) }
}
}
/**
* 补盘完成返回营业(自动模式 + 故障复位)
*/
fun backToBusiness(callback: PlcCallback<String>) {
sdk.actionOpenAutoModeAndActionAlarmReset { result ->
mainHandler.post { callback.onSuccess(result.toString()) }
}
}
// ==================== 状态查询 ====================
/**
* 获取 PLC 状态(解析为 PlcStatus 对象)
*/
fun getStatus(callback: PlcCallback<PlcStatus>) {
sdk.getPlcStatus { json ->
mainHandler.post {
if (json != null) {
callback.onSuccess(PlcStatus.Companion.fromJson(json.toString()))
} else {
callback.onError("获取状态失败")
}
}
}
}
/**
* 获取 PLC 状态(原始 JSON 字符串)
*/
fun getStatusJson(callback: PlcCallback<String>) {
sdk.getPlcStatus { json ->
mainHandler.post {
if (json != null) callback.onSuccess(json.toString())
else callback.onError("获取状态失败")
}
}
}
// ==================== 参数设置 ====================
/**
* 设置 PLC 参数
* @param paramType 参数类型(如 "18FD" 表示主电机运行最长时间)
* @param paramValue 参数值(int,单位 0.1s
*/
fun setParams(paramType: String, paramValue: Int, callback: PlcCallback<String>) {
sdk.setPLCParams(
{ result -> mainHandler.post { callback.onSuccess(result.toString()) } },
paramType,
paramValue
)
}
// ==================== 生命周期 ====================
/**
* 释放资源,清除单例
*/
fun release() {
instance = null
}
}
@@ -0,0 +1,34 @@
package com.sw.platecabinet.utils.mego
import org.json.JSONObject
/**
* PLC 状态数据类
*/
data class PlcStatus(
val isEmptyPlate: Boolean = false, // 是否空盘
val isPlateformDown: Boolean = false, // 平台是否在下位
val isPlateformUp: Boolean = false, // 平台是否在上位
val isTakePlateInfrared: Boolean = false, // 取盘红外是否触发
val alarmCode: Int = 0 // 报警代码
) {
companion object {
/**
* 从 JSON 字符串解析状态
*/
fun fromJson(json: String): PlcStatus {
return try {
val obj = JSONObject(json)
PlcStatus(
isEmptyPlate = obj.optBoolean("isEmptyPlate", false),
isPlateformDown = obj.optBoolean("isPlateformDown", false),
isPlateformUp = obj.optBoolean("isPlateformUp", false),
isTakePlateInfrared = obj.optBoolean("isTakePlateInfrared", false),
alarmCode = obj.optInt("alarmCode", 0)
)
} catch (e: Exception) {
PlcStatus()
}
}
}
}
@@ -3,9 +3,7 @@ package com.sw.platecabinet.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.model.response.ApiResponse
import com.sw.platecabinet.model.response.EquipmentInfo
import com.sw.platecabinet.network.ApiClient
import com.sw.platecabinet.repository.RemoteRepository
import kotlinx.coroutines.flow.MutableStateFlow
@@ -65,12 +63,12 @@ abstract class BaseViewModel() : ViewModel() {
Timber.d("handleError ${exception.message}")
}
fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
// GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
// GlobalData.appBaseUrl="https://vip.shuziweidao.com"
// GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
// GlobalData.appId = equipmentInfo.arcsoftAppId!!
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
// GlobalData.restId = equipmentInfo.canteenId!!
}
// fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
//// GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
//// GlobalData.appBaseUrl="https://vip.shuziweidao.com"
//// GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
//// GlobalData.appId = equipmentInfo.arcsoftAppId!!
//// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
//// GlobalData.restId = equipmentInfo.canteenId!!
// }
}
@@ -1,250 +1,62 @@
package com.sw.platecabinet.viewmodel
import com.sw.platecabinet.GlobalData.globalEquipmentCode
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import java.io.File
/**
* 管理员操作viewmodel
*/
class SettingViewModel : BaseViewModel() {
// 设备绑定列表
// private val _equipmentList = MutableStateFlow<List<EquipmentUserInfo>>(emptyList())
// val equipmentList: StateFlow<List<EquipmentUserInfo>> = _equipmentList
private val _searchMemberList = MutableStateFlow<List<SearchResult.Member>>(emptyList())
val searchMemberList: StateFlow<List<SearchResult.Member>> = _searchMemberList
private val _searchUserInfo = MutableStateFlow<EquipmentUserInfo?>(null)
val searchUserInfo: StateFlow<EquipmentUserInfo?> = _searchUserInfo
var currentPage = 1
var isLoading = false
var canLoadMore = true
/**
* 绑定/解绑状态 <绑定/解绑, 错误信息,>
*/
// private val _bindStateChange = MutableStateFlow<Pair<Boolean?, ErrorInfo>>(null to ErrorInfo())
// val bindStateChange: StateFlow<Pair<Boolean?, ErrorInfo>> = _bindStateChange
/**
* 获取设备绑定用户信息列表
*/
// fun getEquipmentList(equipmentCode: String = globalEquipmentCode) {
// launchWithLoading {
// val param =
// EquipmentParam(appVersion = GlobalData.appVersion, equipmentCode = equipmentCode)
// val response = repository.getEquipmentList(param)
// if (parseResponse(response)) {
// _equipmentList.value = response.data ?: emptyList()
// }
// }
//// val list = mutableListOf<EquipmentUserInfo>()
//// for (i in 1 .. 30){
//// list.add(EquipmentUserInfo(id = i, equipmentBoxCode = i.toString()))
//// }
//// _equipmentList.value = list
//package com.sw.platecabinet.viewmodel
//
//import com.sw.platecabinet.model.response.UserFaceModel
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.withContext
//import java.io.File
//
///**
// * 管理员操作viewmodel
// */
//class SettingViewModel : BaseViewModel() {
//
// companion object {
// const val PAGE_SIZE = 100
// }
// fun getEquipmentList(block: (List<EquipmentUserInfo>) -> Unit) {
// launchWithLoading {
// val response = repository.getEquipmentList()
// if (parseResponse(response)) {
// val list = response.data ?: emptyList()
// block(list)
// }
// }
// }
//
// /**
// * 搜索会员信息列表
// * 获取人脸数据
// */
// fun getSearchMemberList(param:String, pageNum: Int = 1, pageSize: Int = 50) {
// if (TextUtils.isEmpty(param)) return
// currentPage = pageNum
// var name =""
// var phone =""
// val num = param.toIntOrNull()
// if (num != null) {
// phone = param
// } else {
// name = param
// }
// fun getFaceIncrementList(
// pageNo: Int = 1,
// pageSize: Int = PAGE_SIZE,
// timestamp: Long,
// block: (List<UserFaceModel>) -> Unit
// ) {
// launch {
// val searchParam = SearchParam(
//// appVersion = GlobalData.appVersion,
// name = name,
// phone = phone,
// pageNum = pageNum,
// pageSize = pageSize
// val response = repository.getFaceIncrementList(
// pageNum = pageNo.toLong(),
// pageSize = pageSize.toLong(),
// timestamp = timestamp
// )
// isLoading = true
// val response = repository.searchUser(searchParam)
// isLoading = false
// if (parseResponse(response)) {
// response.data?.let { list ->
// if (pageNum == 1) {
// _searchMemberList.value = list
// } else {
// _searchMemberList.value = _searchMemberList.value.plus(list)
// }
// if (list.size >= pageSize) currentPage++
// withContext(Dispatchers.Default) {
// val list: List<UserFaceModel> = response.data ?: emptyList()
// block(list)
// }
// }
// }
// }
// fun bindPlate(
// equipmentBoxCode: String,
// equipmentCode: String = globalEquipmentCode,
// memberId: Int,
// plateNumber: String
// ) {
// _bindStateChange.value = null to ErrorInfo()
// launchWithLoading {
// val bindParam = BindParam(
// appVersion = GlobalData.appVersion,
// equipmentBoxCode = equipmentBoxCode,
// equipmentCode = equipmentCode,
// memberId = memberId,
// plateNumber = plateNumber
// )
// val response = repository.bindEquipment(bindParam)
// if (parseResponse(response)) {
// var userInfo = response.data
// if (userInfo == null) {
// userInfo = EquipmentUserInfo()
// userInfo.equipmentBoxCode = equipmentBoxCode
// userInfo.plateNumber = plateNumber
// userInfo.equipmentCode = equipmentCode
// }
// _bindStateChange.value = true to ErrorInfo(equipmentUserInfo = userInfo)
// } else {
// _bindStateChange.value = true to ErrorInfo(response.code, response.msg.toString())
//
// /**
// * 添加人脸数据
// */
// fun addUserFace(faceData: String, imageFile: File, block: (Boolean, UserFaceModel?) -> Unit) {
// launch {
// val imageResp = repository.uploadImage(imageFile)
// val imageUrl = imageResp.data ?: ""
// if (imageUrl.isBlank()) {
// block(false, null)
// return@launch
// }
// val faceResp = repository.addUserFace(faceData, imageUrl)
// val status = parseResponse(faceResp)
// if (status.not()) {
// block(false, null)
// return@launch
// }
// block(true, faceResp.data)
// }
// }
// fun unbindPlate(
// equipmentBoxCode: String,
// equipmentCode: String = globalEquipmentCode
// ) {
// launchWithLoading {
// val bindParam = BindParam(
// appVersion = GlobalData.appVersion,
// equipmentBoxCode = equipmentBoxCode,
// equipmentCode = equipmentCode
// )
// val response = repository.bindEquipment(bindParam)
// if (parseResponse(response)) {
// _bindStateChange.value = false to ErrorInfo()
// } else {
// _bindStateChange.value = false to ErrorInfo(response.code, response.msg.toString())
// }
// }
// }
// fun unbindPlate(id: Long?, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
// block(null to ErrorInfo())
// launchWithLoading {
// val response = repository.plateUnbind(id)
// if (parseResponse(response)) {
// block(false to ErrorInfo())
// } else {
// block(false to ErrorInfo(response.code, response.msg.toString()))
// }
// }
// }
// fun bindPlate(bindParam: BindParam, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
// launchWithLoading {
//// val response = repository.plateBind(bindParam)
//// if (parseResponse(response)) {
//// var userInfo = response.data
//// if (userInfo == null) {
//// userInfo = EquipmentUserInfo()
//// userInfo.equipmentBoxCode = bindParam.equipmentBoxCode
//// userInfo.plateNumber = bindParam.plateNumber
//// userInfo.equipmentCode = bindParam.equipmentCode
//// }
//// block(true to ErrorInfo(equipmentUserInfo = userInfo))
//// } else {
//// block(true to ErrorInfo(response.code, response.msg.toString()))
//// }
// }
// }
fun findByPlateNumber(
plateNumber: String,
equipmentCode: String = globalEquipmentCode
) {
// _searchUserInfo.value = null
// launchWithLoading {
//// val bindParam = BindParam(plateNumber = plateNumber)
// val response = repository.findByPlateNumber(plateNumber)
// if (response.code == "00000") {
// _searchUserInfo.value = response.data
// } else {
// _searchUserInfo.value = EquipmentUserInfo(
// plateNumber = plateNumber,
// equipmentCode = equipmentCode,
// updateTime = DateTimeUtils.getDateTimeString()
// )
// }
// }
}
val PAGE_SIZE = 100
/**
* 获取人脸数据
*/
fun getFaceIncrementList(
pageNo: Int = 1,
pageSize: Int = PAGE_SIZE,
timestamp: Long,
block: (List<UserFaceModel>) -> Unit
) {
launch {
val response = repository.getFaceIncrementList(
pageNum = pageNo.toLong(),
pageSize = pageSize.toLong(),
timestamp = timestamp
)
if (parseResponse(response)) {
withContext(Dispatchers.Default) {
val list: List<UserFaceModel> = response.data ?: emptyList()
block(list)
}
}
}
}
/**
* 添加人脸数据
*/
fun addUserFace(faceData: String, imageFile: File, block: (Boolean, UserFaceModel?) -> Unit) {
launch {
val imageResp = repository.uploadImage(imageFile)
val imageUrl = imageResp.data?:""
if (imageUrl.isBlank()) {
block(false, null)
return@launch
}
val faceResp = repository.addUserFace(faceData, imageUrl)
val status = parseResponse(faceResp)
if (status.not()) {
block(false, null)
return@launch
}
block(true, faceResp.data)
}
}
}
//
//}
@@ -9,19 +9,15 @@ import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.FaceApi
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.GlobalData.globalEquipmentCode
import com.sw.platecabinet.GlobalKey
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.LoginParam
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
/**
* 用户viewmodel
@@ -32,28 +28,21 @@ class UserViewModel : BaseViewModel() {
const val PAGE_SIZE = 100
}
// 通过人脸获取到的用户信息
// private val _currentUserInfo = MutableStateFlow<EquipmentUserInfo?>(null)
// val currentUserInfo: StateFlow<EquipmentUserInfo?> = _currentUserInfo
private val faceApi: FaceApi = FaceApi()
/**
* 获取token
*/
// fun generateToken(deviceId: String = "SWSN:88:12:AC:4E:D9:CC") {
// launchWithLoading {
// val response = repository.generateToken(deviceId)
// if (parseResponse(response)) {
// // 缓存token
// SPUtil.getInstance().put(GlobalKey.KEY_TOKEN, response.data)
// // 首次运行获取人脸数据
// if (SPUtil.getInstance().get(GlobalKey.KEY_FIRST_RUN, false) != true) {
// getUserFaceCache()
// }
// }
// }
// }
fun getUserFace(
pageNo: Int,
pageSize: Int,
callback: (List<UserFaceModel>) -> Unit
) {
launchWithLoading {
val response = repository.getUserFaceCache(pageNum = pageNo, pageSize = pageSize)
val status = parseResponse(response)
if (status) callback(response.data ?: emptyList())
else emptyList<UserFaceModel>()
}
}
/**
* 获取人脸数据
@@ -75,13 +64,19 @@ class UserViewModel : BaseViewModel() {
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
val list: List<UserFaceModel> = response.data ?: emptyList()
if (pageNo == 1 && list.isEmpty()) {
callback(true, "暂无数据")
callback(false, "暂无数据")
return@launchWithLoading
}
val faceEntityList = list.map {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr)).apply {
userType = if (it.member) "1" else "2"
}
}
withContext(Dispatchers.Default) {
if (currentPageNo == 1) {
//第一页数据删除本地数据库
faceApi.clearFaceData()
}
faceApi.updateFaceData(pageNo, faceEntityList)
if (list.size >= pageSize) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
@@ -103,6 +98,7 @@ class UserViewModel : BaseViewModel() {
private var faceTimestamp = 0L
/**
* 激活人脸识别引擎
*/
@@ -143,40 +139,6 @@ class UserViewModel : BaseViewModel() {
})
}
/**
* 校验码登录
*/
fun loginWithPwd(loginParam: LoginParam) {
// launchWithLoading {
// val response = repository.equipmentBoxLogin(loginParam)
// if (parseResponse(response)) {
// _currentUserInfo.value = response.data
// }
// }
}
/**
* 通过用户id获取用户信息
*/
// fun getUserInfoById(memberId: String?, action: (EquipmentUserInfo?) -> Unit = {}) {
// _currentUserInfo.value = null
// launchWithLoading {
// val loginParam = LoginParam(faceId = memberId)
// val response = repository.equipmentBoxLogin(loginParam)
// if (parseResponse(response)) {
// _currentUserInfo.value = response.data
// action(response.data)
// }
// }
// }
/**
* 重置用户信息
*/
fun resetUserInfo() {
Timber.d("resetUserInfo")
// _currentUserInfo.value = null
}
fun getDeviceConfig(block: (DeviceConfig?) -> Unit) {
// Timber.tag(TAG).d("getDeviceConfig")
@@ -190,4 +152,50 @@ class UserViewModel : BaseViewModel() {
}
}
/**
* 获取人脸数据
*/
fun getFaceIncrementList(
pageNo: Int = 1,
pageSize: Int = PAGE_SIZE,
timestamp: Long,
block: (List<UserFaceModel>) -> Unit
) {
launch {
val response = repository.getFaceIncrementList(
pageNum = pageNo.toLong(),
pageSize = pageSize.toLong(),
timestamp = timestamp
)
if (parseResponse(response)) {
withContext(Dispatchers.Default) {
val list: List<UserFaceModel> = response.data ?: emptyList()
block(list)
}
}
}
}
/**
* 添加人脸数据
*/
fun addUserFace(faceData: String, imageFile: File, block: (Boolean, UserFaceModel?) -> Unit) {
launch {
val imageResp = repository.uploadImage(imageFile)
val imageUrl = imageResp.data ?: ""
if (imageUrl.isBlank()) {
block(false, null)
return@launch
}
val faceResp = repository.addUserFace(faceData, imageUrl)
val status = parseResponse(faceResp)
if (status.not()) {
block(false, null)
return@launch
}
block(true, faceResp.data)
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

+14
View File
@@ -0,0 +1,14 @@
<?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="@color/white" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#5C77F7" />
</shape>
</item>
</ripple>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+11
View File
@@ -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="#5C77F7" />
<corners android:radius="8dp" />
</shape>
</item>
</ripple>
+8 -22
View File
@@ -2,34 +2,20 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:width="600dp"
android:height="1024dp">
<bitmap android:src="@drawable/bg" />
android:width="1920px"
android:height="1080px">
<bitmap
android:scaleType="centerCrop"
android:src="@drawable/bg_collect_page3" />
</item>
<item
android:width="337dp"
android:height="322dp"
android:width="506dp"
android:height="483dp"
android:gravity="top|center_horizontal"
android:top="112dp">
android:top="150dp">
<bitmap
android:scaleType="fitCenter"
android:src="@drawable/img_init" />
</item>
<!-- TODO 临时测试 -->
<!-- <item-->
<!-- android:width="828dp"-->
<!-- android:height="1768dp">-->
<!-- <bitmap android:src="@drawable/bg" />-->
<!-- </item>-->
<!-- <item-->
<!-- android:width="337dp"-->
<!-- android:height="322dp"-->
<!-- android:gravity="top|center_horizontal"-->
<!-- android:top="360dp">-->
<!-- <bitmap-->
<!-- android:scaleType="fitCenter"-->
<!-- android:src="@drawable/img_init" />-->
<!-- </item>-->
</layer-list>
@@ -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="@color/white"/>
<corners android:radius="12dp"/>
</shape>
+3 -3
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12dp" />
<corners android:radius="16dp" />
<stroke
android:width="2dp"
android:color="#FFCC99" />
android:width="3dp"
android:color="#FFC08A6F" />
</shape>
+110
View File
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools">
<include
android:id="@+id/includeHeader"
layout="@layout/item_title_time" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center_vertical"
android:layout_marginTop="30dp">
<TextView
android:id="@+id/tvMemberUserCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textSize="16sp"
android:textColor="@color/white"
android:gravity="center"
tools:text="会员用户:100人"/>
<View
android:layout_width="1dp"
android:layout_height="30dp"
android:background="@color/white"/>
<TextView
android:id="@+id/tvTempUserCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textSize="16sp"
android:textColor="@color/white"
android:gravity="center"
tools:text="临时用户:100人"/>
</LinearLayout>
<TextView
android:id="@+id/btnClearFace"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="清除人脸数据"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnClearAndGetAll"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="清除并拉取全量数据"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnOpenAllPlate"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="打开全部柜子"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnPutPlate"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="模拟放置餐盘"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnTestFeature"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="人脸特征比较"
android:textColor="#FFCC99"
android:visibility="gone"/>
</LinearLayout>
+10 -16
View File
@@ -4,7 +4,7 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
android:background="@drawable/bg_collect_page3"
android:orientation="vertical"
tools:context=".activity.InitActivity">
@@ -12,10 +12,10 @@
android:id="@+id/includeHeader"
layout="@layout/item_title_time" />
<View
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"/>
android:layout_weight="1"/>
<LinearLayout
android:layout_width="match_parent"
@@ -26,34 +26,28 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="142dp"
android:layout_height="wrap_content"
android:src="@drawable/ic_init_text"
android:adjustViewBounds="true"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="345dp"
android:layout_marginTop="90dp"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:src="@drawable/ic_init_img"
android:adjustViewBounds="true" />
<!-- <ImageView-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="20dp"-->
<!-- android:src="@drawable/ic_init_press" />-->
<FrameLayout
android:id="@+id/takeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-35dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_width="360dp"
android:layout_height="110dp"
android:text="点击取盘"
android:textColor="#FFCC99"
android:textSize="24sp"
android:textSize="36sp"
android:layout_marginHorizontal="60dp"
android:layout_marginVertical="50dp"
android:paddingHorizontal="45dp"
@@ -63,7 +57,7 @@
</FrameLayout>
</LinearLayout>
<View
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
+106 -159
View File
@@ -1,178 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
android:fitsSystemWindows="false"
android:orientation="vertical"
tools:context=".activity.LoginByFaceActivity">
tools:context=".activity.LoginByFaceActivity"
tools:ignore="HardcodedText,ContentDescription,PxUsage">
<FrameLayout
android:layout_width="776px"
android:layout_height="582px"
android:layout_gravity="center">
<TextureView
android:id="@+id/dual_camera_texture_preview_rgb"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.sw.plate.utils.arcface.FaceRectView
android:id="@+id/dual_camera_face_rect_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:src="@drawable/bg_face_area" />
<include
android:id="@+id/includeHeader"
layout="@layout/item_title_time" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="165dp"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="95dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:visibility="visible">
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:maxLines="1"
android:text="请正视屏幕"
android:textColor="#FFF0C8B4"
android:textSize="48sp"
android:textStyle="bold" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="0dp"
android:paddingTop="1dp">
<TextView
android:id="@+id/tvSimilarValue"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="0dp"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="12sp"
tools:text="0.6183次" />
<TextureView
android:id="@+id/dual_camera_texture_preview_rgb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
<TextView
android:id="@+id/tv_to_init"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="#FFF0C8B4"
android:textSize="48sp"
tools:text="30" />
<com.sw.plate.utils.arcface.FaceRectView
android:id="@+id/dual_camera_face_rect_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
</FrameLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="0dp"
android:scaleType="centerCrop"
android:src="@drawable/bg_face"
android:visibility="visible" />
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="129dp"
android:text="请正视屏幕"
android:textColor="#FFCC99"
android:textSize="36sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvSimilarValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="26sp"
android:layout_marginTop="80dp"
tools:text="0.6183次"
android:layout_gravity="center_horizontal"
android:textColor="@color/white"/>
<!-- <LinearLayout-->
<!-- android:id="@+id/ll_to_pwd"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="208dp"-->
<!-- android:layout_marginTop="730dp"-->
<!-- android:background="@drawable/btn_outline"-->
<!-- android:gravity="center"-->
<!-- android:paddingHorizontal="45dp"-->
<!-- android:paddingVertical="18dp"-->
<!-- android:visibility="gone">-->
<!-- <TextView-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="密码登录"-->
<!-- android:textColor="#FFCC99"-->
<!-- android:textSize="24sp" />-->
<!-- </LinearLayout>-->
<TextView
android:id="@+id/tv_to_init"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="185dp"
android:gravity="center"
android:padding="12dp"
android:textColor="#FFCC99"
android:textSize="36sp"
tools:text="30" />
<!-- <LinearLayout-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:gravity="center_horizontal"-->
<!-- android:layout_gravity="bottom|center_horizontal"-->
<!-- android:layout_marginBottom="130dp"-->
<!-- android:orientation="vertical">-->
<!-- <LinearLayout-->
<!-- android:id="@+id/layoutState"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="50dp"-->
<!-- android:gravity="center_vertical"-->
<!-- android:orientation="horizontal"-->
<!-- android:visibility="invisible">-->
<!-- <ProgressBar-->
<!-- android:id="@+id/pbCollectLoading"-->
<!-- android:layout_width="32dp"-->
<!-- android:layout_height="32dp"-->
<!-- android:indeterminateTint="@color/white" />-->
<!-- <TextView-->
<!-- android:id="@+id/tvCollectState"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="10dp"-->
<!-- android:text="采集中......"-->
<!-- android:textColor="@color/white"-->
<!-- android:textSize="24sp" />-->
<!-- </LinearLayout>-->
<!-- <TextView-->
<!-- android:id="@+id/btnCollectFace"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="60dp"-->
<!-- android:background="@drawable/btn_outline"-->
<!-- android:gravity="center"-->
<!-- android:layout_marginTop="10dp"-->
<!-- android:paddingHorizontal="45dp"-->
<!-- android:text="开始采集"-->
<!-- android:textColor="#FFCC99"-->
<!-- android:textSize="24sp"-->
<!-- tools:ignore="HardcodedText" />-->
<!-- </LinearLayout>-->
<!-- <androidx.recyclerview.widget.RecyclerView-->
<!-- android:id="@+id/rvLogInfo"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:layout_gravity="bottom"-->
<!-- android:overScrollMode="never"-->
<!-- android:visibility="gone"-->
<!-- android:background="@color/black"-->
<!-- tools:listitem="@layout/list_item_log_info" />-->
<!-- <ImageView-->
<!-- android:id="@+id/btnLogState"-->
<!-- android:layout_width="60dp"-->
<!-- android:layout_height="60dp"-->
<!-- android:layout_gravity="bottom|end"-->
<!-- android:background="@drawable/shape_white_circle"-->
<!-- android:padding="10dp"-->
<!-- android:layout_marginEnd="10dp"-->
<!-- android:layout_marginBottom="10dp"-->
<!-- android:src="@drawable/ic_log_show"-->
<!-- android:textSize="16sp" />-->
</FrameLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/llCollectButton"
android:layout_width="wrap_content"
android:layout_height="265dp"
android:layout_gravity="center_horizontal|bottom"
android:gravity="center"
android:orientation="horizontal"
android:visibility="visible">
<TextView
android:id="@+id/btnRetry"
android:layout_width="360dp"
android:layout_height="110dp"
android:layout_marginHorizontal="20dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="再次重试"
android:textColor="#FFC08A6F"
android:textSize="36sp"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/btnCollect"
android:layout_width="360dp"
android:layout_height="110dp"
android:layout_marginHorizontal="20dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="新用户采集"
android:textColor="#FFC08A6F"
android:textSize="36sp"
tools:ignore="HardcodedText" />
</LinearLayout>
<ImageView
android:id="@+id/ivCollectPhoto"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="64dp"
android:adjustViewBounds="true"
android:visibility="gone"
tools:src="@drawable/logo_launcher" />
</FrameLayout>
+1 -1
View File
@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/bg"
android:background="@drawable/bg_collect_page3"
tools:context=".activity.MainActivity">
<include
@@ -0,0 +1,330 @@
<?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:background="@drawable/bg"
android:orientation="vertical"
tools:context=".activity.PlcSettingActivity">
<include
android:id="@+id/includeHeader"
layout="@layout/item_title_time" />
<RelativeLayout
android:id="@+id/layout_tips"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/img_error_tips"
tools:ignore="ContentDescription" />
<TextView android:id="@+id/tv_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="144dp"
android:text="暂无餐盘 请联系管理人员"
android:textColor="#fff0c8b4"
android:textSize="32sp" />
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clipToPadding="false"
android:paddingHorizontal="50dp"
android:paddingVertical="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- ===== 连接控制 ===== -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="连接控制"
android:textColor="#FFCC99"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/btnOpenSerialPort"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="打开串口"
android:textColor="#FFCC99"
android:textSize="22sp" />
<View
android:layout_width="20dp"
android:layout_height="1dp" />
<TextView
android:id="@+id/btnOpenAutoMode"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="打开自动模式"
android:textColor="#FFCC99"
android:textSize="22sp" />
</LinearLayout>
<!-- ===== 出盘操作 ===== -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="出盘操作"
android:textColor="#FFCC99"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnOutPlate"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="出盘"
android:textColor="#FFCC99"
android:textSize="22sp" />
<!-- ===== 平台控制 ===== -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="平台控制"
android:textColor="#FFCC99"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/btnPlatformDown"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="平台下降"
android:textColor="#FFCC99"
android:textSize="22sp" />
<View
android:layout_width="20dp"
android:layout_height="1dp" />
<TextView
android:id="@+id/btnPlatformUp"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="平台上升"
android:textColor="#FFCC99"
android:textSize="22sp" />
</LinearLayout>
<TextView
android:id="@+id/btnBackToBusiness"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="12dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="返回营业(补盘完成)"
android:textColor="#FFCC99"
android:textSize="22sp" />
<TextView
android:id="@+id/btnPlatformReset"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="12dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="餐盘平台复位"
android:textColor="#FFCC99"
android:textSize="22sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<!-- ===== 故障处理 ===== -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="故障处理"
android:textColor="#FFCC99"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnAlarmReset"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="故障复位"
android:textColor="#FFCC99"
android:textSize="22sp" />
<!-- ===== 参数设置 ===== -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="参数设置"
android:textColor="#FFCC99"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<EditText
android:id="@+id/etParamType"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="@drawable/btn_outline"
android:gravity="center"
android:hint="参数类型(如 18FD"
android:inputType="text"
android:paddingHorizontal="12dp"
android:textColor="#FFCC99"
android:textColorHint="#99885566"
android:textSize="18sp" />
<View
android:layout_width="20dp"
android:layout_height="1dp" />
<EditText
android:id="@+id/etParamValue"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="@drawable/btn_outline"
android:gravity="center"
android:hint="参数值(整数)"
android:inputType="number"
android:paddingHorizontal="12dp"
android:textColor="#FFCC99"
android:textColorHint="#99885566"
android:textSize="18sp" />
</LinearLayout>
<TextView
android:id="@+id/btnSetParams"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="12dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="设置参数"
android:textColor="#FFCC99"
android:textSize="22sp" />
<!-- ===== 状态显示 ===== -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="PLC 状态"
android:textColor="#FFCC99"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnRefreshStatus"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="刷新状态"
android:textColor="#FFCC99"
android:textSize="22sp" />
<TextView
android:id="@+id/tvStatusInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:background="@drawable/btn_outline"
android:paddingHorizontal="16dp"
android:paddingVertical="14dp"
android:text="暂无状态数据"
android:textColor="#FFCC99"
android:textSize="16sp"
tools:text="空盘:false | 平台下位:false | 平台上位:true | 取盘红外:false | 报警码:0" />
<View
android:layout_width="match_parent"
android:layout_height="30dp" />
</LinearLayout>
<!-- ===== 出盘操作 ===== -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="环境"
android:textColor="#FFCC99"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnSwitchEnv"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:background="@drawable/btn_outline"
android:gravity="center"
android:text="切换环境"
android:textColor="#FFCC99"
android:textSize="22sp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="600dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius12"
android:orientation="vertical"
android:paddingHorizontal="40dp"
android:paddingVertical="36dp">
<!-- 标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="环境切换"
android:textColor="#ff141428"
android:textSize="36sp"
android:textStyle="bold" />
<!-- 分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="24dp"
android:background="#FFDCDCF0" />
<!-- 环境选项 -->
<RadioGroup
android:id="@+id/rgEnv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="vertical">
<RadioButton
android:id="@+id/rbTest"
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="测试环境"
android:textColor="#ff141428"
android:textSize="28sp" />
<RadioButton
android:id="@+id/rbUat"
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="UAT 环境"
android:textColor="#ff141428"
android:textSize="28sp" />
<RadioButton
android:id="@+id/rbProd"
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="生产环境"
android:textColor="#ff141428"
android:textSize="28sp" />
</RadioGroup>
<!-- 分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="24dp"
android:background="#FFDCDCF0" />
<!-- 底部按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="88dp"
android:layout_marginTop="24dp"
android:gravity="center"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="20dp"
android:layout_weight="1"
android:background="@drawable/bg_cancel"
android:text="取消"
android:textColor="#5C77F7"
android:textSize="28sp" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:layout_weight="1"
android:background="@drawable/bg_confirm"
android:text="确认"
android:textColor="#FFFFFF"
android:textSize="28sp" />
</LinearLayout>
</LinearLayout>
+7 -6
View File
@@ -4,15 +4,16 @@
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="240dp"
android:layout_width="wrap_content"
android:layout_height="80dp"
android:gravity="center_vertical"
android:maxWidth="200dp"
android:background="@drawable/dialog_waiting">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="10dp"
android:indeterminateTint="@color/teal_200"
android:indeterminateTintMode="src_atop" />
@@ -20,10 +21,10 @@
android:id="@+id/tvTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginHorizontal="10dp"
android:textColor="@color/white"
android:text="请稍等"
android:textSize="22sp" />
android:textSize="26sp" />
</LinearLayout>
</FrameLayout>
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
+25 -11
View File
@@ -3,30 +3,44 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingHorizontal="24dp">
android:layout_height="100dp"
android:paddingHorizontal="65dp"
tools:ignore="HardcodedText,ContentDescription,UseCompoundDrawables">
<TextView
<LinearLayout
android:id="@+id/tvLeftDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="#FFCC99"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="2025年6月18日 星期三" />
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/logo_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="25dp"
android:textColor="#FFF0C8B4"
android:textSize="30sp"
android:text="欢迎光临数味餐厅" />
</LinearLayout>
<TextView
android:id="@+id/tvRightTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="#FFCC99"
android:textSize="18sp"
android:textColor="#FFF0C8B4"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvLeftDate"
app:layout_constraintBottom_toBottomOf="@id/tvLeftDate"
tools:text="14:02:39" />
tools:text="2026年03月27日 星期五 12:08" />
</androidx.constraintlayout.widget.ConstraintLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

+1 -1
View File
@@ -1,5 +1,5 @@
<resources>
<string name="app_name">餐盘柜</string>
<string name="app_name">发盘机</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
+1 -1
View File
@@ -62,6 +62,6 @@ dependencies {
implementation("com.google.code.gson:gson:2.8.6")
val glideVersion = "4.12.0"
implementation("com.github.bumptech.glide:glide:$glideVersion")
api("com.github.bumptech.glide:glide:$glideVersion")
annotationProcessor("com.github.bumptech.glide:compiler:$glideVersion")
}
@@ -87,10 +87,10 @@ public class ConfigUtil {
/**
* 默认相机分辨率
*/
private static final String DEFAULT_PREVIEW_SIZE = "1280x720";
// private static final String DEFAULT_PREVIEW_SIZE = "1280x720";
private static final String DEFAULT_PREVIEW_SIZE = "1024x768";
// private static final String DEFAULT_PREVIEW_SIZE = "400x640";
/**
* 获取String类型的preference
*
@@ -40,6 +40,12 @@ public class FaceApi {
faceDao.insert(list);
}
public void clearFaceData() {
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
faceDao.deleteAll();
faceDao.resetId();
}
/**
* 激活arcsoft 人脸
*
@@ -46,6 +46,10 @@ public class FaceRectView extends View {
}
}
public int getRectColor() {
return paint.getColor();
}
public void clearFaceInfo() {
drawInfoList.clear();
postInvalidate();
@@ -343,6 +343,14 @@ public class FaceHelper implements FaceListener {
refreshTrackId(faceInfoList);
if (faceInfoList.isEmpty()) {
return facePreviewInfoList;
} else {
FaceInfo currentFaceInfo = faceInfoList.get(0);
// L.e("currentFaceInfo width=" + currentFaceInfo.getRect().width() +
// "---height" + currentFaceInfo.getRect().height());
if (currentFaceInfo.getRect().width() < 200) {//距离远(人脸小),不识别
clearLeftFace(facePreviewInfoList);
return facePreviewInfoList;
}
}
if (!onlyDetectLiveness) {
code = ftEngine.process(rgbNv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList,
@@ -92,9 +92,25 @@ public interface FaceDao {
@Query("SELECT * FROM face WHERE user_name = :userName limit 1")
FaceEntity queryByUserName(String userName);
/**
* 查询指定用户的所有人脸记录(同一用户可能存有多条特征数据)
*
* @param userName 用户ID
* @return 该用户所有人脸记录列表
*/
@Query("SELECT * FROM face WHERE user_name = :userName")
List<FaceEntity> queryAllByUserName(String userName);
/**
* @return 删除临时用户人脸
*/
@Query("DELETE from face WHERE user_type = '2'")
int deleteTempUserFaceData();
/**
* 查询用户人脸数
* @param userType 1-会员,2-临时用户
*/
@Query("SELECT COUNT(1) FROM face WHERE user_type = :userType")
int getFaceCount(String userType);
}
@@ -397,6 +397,20 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
}
}
/**
* 重置人脸识别状态:清空上次识别结果及 FaceHelper 内部的 recognizeInfoMap
* 使下一帧进入时能重新触发识别流程。
* 适用场景:短时间内再次识别、点击重试按钮等需要重新开始识别的时机。
*/
public void resetFaceState() {
// 清空粘性 LiveData,防止旧结果被重新投递给 observer
recognizeUserId.postValue(null);
// 清空 FaceHelper 内部所有人脸状态,让 trackId 对应的状态回到 TO_RETRY
if (faceHelper != null) {
faceHelper.clearFacePreviewInfoList();
}
}
/**
* 释放操作
*/
+1 -1
View File
@@ -25,6 +25,6 @@ dependencyResolutionManagement {
}
}
rootProject.name = "SmartPlateCabinet"
rootProject.name = "SmartTakePlate"
include(":app")
include(":lib_face")