Author SHA1 Message Date
lvmeng c19d84a7a6 refactor(init): 优化结算模式配置逻辑
- 移除未使用的变量声明
- 将结算模式设置改为不可变值
- 简化支付类型判断逻辑
- 保持结算模式存储功能不变
2026-05-26 11:49:23 +08:00
lvmeng 8f1f15dd74 feat(payment): 添加会员折扣功能并重构支付逻辑
- 新增 getMemberDiscount 接口用于查询会员折扣
- 在人脸识别支付中集成会员折扣计算逻辑
- 将支付金额计算从 vipPrice 切换到 specPrice 并应用会员折扣
- 在初始化活动中添加设备配置获取功能
- 使用 ConstraintLayout 重构主餐品列表布局
- 添加 Timber 日志框架替换原有 Log 工具
- 重构 MainScreenPresentation 中的食物价格计算逻辑
- 移除重复的设备配置获取代码,统一在 InitActivity 中处理
2026-04-27 17:52:45 +08:00
lvmeng 416a8366e1 feat(MainScreenPresentation): 添加布局隐藏功能以支持结算模式
- 导入 invisible 扩展函数
- 在特定结算模式下隐藏 eat detail 布局
- 实现相机恢复后的条件性视图控制
2026-04-24 15:41:21 +08:00
lvmeng 03c5aee785 refactor(ui): 重构主界面食物列表布局和支付模式切换功能
- 将原有的RecyclerView替换为FrameLayout容器,实现动态布局加载
- 新增layout_main_food_list.xml和layout_main_food_list2.xml两个布局文件
- 实现联合支付和独立支付两种模式下的不同UI展示
- 集成食物详情显示功能,通过include方式引入layout_eat_detail.xml
- 更新数据绑定逻辑,支持两种支付模式下的食物列表显示
- 迁移SharedPreferences操作至SpTool工具类统一管理
- 优化食物识别结果显示逻辑和搜索按钮状态控制
- 重构RecyclerView初始化和适配器设置代码结构
2026-04-24 15:37:57 +08:00
mazengfei e3b01ff789 修改uat环境域名端口 2026-04-15 16:52:27 +08:00
mazengfeiandClaude Sonnet 4.6 b6c9fe5aed feat(log): 优化日志写入机制,改用内部存储与单线程执行器
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:23:50 +08:00
lvmeng 45562a9fc1 fix(order): 解决订单生成后餐品未拿走的检测逻辑
- 添加abs函数导入用于计算重量绝对值
- 在订单生成成功后增加重量检测逻辑
- 当称重绝对值大于10克时认为餐品未被拿走
- 添加详细的调试日志记录重量信息
- 集成FileUtil工具类用于保存操作日志
- 在人脸数据获取流程中添加日志记录时间戳变化
2026-04-14 15:19:09 +08:00
lvmeng 7f455f2d8f perf(FoodModule): 提高食物查询性能并优化排序逻辑
- 将默认查询数量从15增加到50
- 移除不必要的分数阈值过滤条件
- 移除复杂的按名称分组和计数逻辑
- 简化排序算法,直接按分数排序
- 添加调试日志输出结果数据
2026-04-13 17:20:51 +08:00
mazengfei dde0bbc4d5 优化余量计量模式下拿完所有数据时总重量不对的问题 2026-04-10 17:48:40 +08:00
lvmeng d378cdcf97 fix(main): 修复菜品切换和重量计算相关问题
- 移除未使用的LinearLayoutManager和FoodOrderAdapter导入
- 添加isSwitchFood标志用于菜品切换时重置数据
- 优化resetSelectedFood方法,根据用户状态决定是否重新打开识别功能
- 将currentWeight修改为public访问权限
- 在重量变化时添加判断避免重复更新
- 注释掉不需要的重量更新调用
- 在菜品识别完成时添加重量更新
- 在清空选中项目时重置isSwitchFood标志
- 优化updateWeight方法中的逻辑判断
- 将倒计时重置任务提取为单独方法
- 添加switchFood逻辑控制,避免重复人脸识别
- 修复余量计量模式下的最后餐品处理逻辑
2026-04-10 16:03:27 +08:00
lvmeng e5b682f021 refactor(face): 优化人脸识别状态管理与菜品选择逻辑
- 移除 MainFoodListAdapter 中未使用的适配器和视图参数
- 提取菜品重选逻辑到独立的 resetSelectedFood 方法中
- 删除临时的独立支付模式测试代码
- 在余量计量场景中添加人脸状态检查避免重复数据读取
- 修复倒计时任务状态判断逻辑并清理任务引用
- 添加空值检查防止 CompareResult 为空时的异常
- 实现 resetFaceState 方法清空人脸识别状态
- 在订单提交前重置人脸状态确保下次识别正常进行
2026-04-09 16:02:24 +08:00
lvmeng 0498e124b3 fix(weight): 修复称重逻辑和支付模式相关问题
- 将 WEIGHT_CHANGE_VALUE 从 25 修改为 20
- 添加 currentWeight 变量用于准确记录当前称重值
- 临时修改 settlementMode 为独立支付模式用于测试
- 更新日志输出以更好地跟踪称重变化
- 修复即放即取和余量计量模式下的称重处理逻辑
- 添加食物识别状态控制变量 foodRecognizeState
- 在不同模式下正确设置 eatWeight 和 foodWeight 计算方式
- 优化余量计量模式下的状态重置逻辑
- 添加倒计时功能用于余量计量模式状态重置
- 修复营养计算相关的日志输出格式
2026-04-09 11:59:25 +08:00
lvmeng 30b2b178a9 refactor(SensorScaleUtils): 优化重量读取逻辑
- 移除状态字符串转换的日志代码
- 简化稳定状态判断逻辑,移除重复的lastWeight比较
- 将重量单位转换提取为独立变量,提高代码可读性
- 保持原有的回调机制和重量单位转换功能
2026-04-08 10:59:07 +08:00
lvmeng 2ba55ef027 feat(common): 添加View点击区域扩展功能并优化主界面重量检测逻辑
- 在CommonExt.kt中新增expandClickArea扩展函数,支持扩大View的点击响应区域
- 新增TouchDelegate导入用于实现点击区域扩展功能
- 调整MainActivity.kt中的重量检测逻辑顺序,先处理联合支付模式下的特殊情况
- 添加对当前用户ID的检查,优化即放即取模式下的重量数据处理流程
- 重构重量重置识别条件的位置,确保正确的执行顺序
2026-04-08 10:52:09 +08:00
lvmengandClaude Sonnet 4.6 a45690d3be refactor(utils): 重构 FoodVectorTool 并优化 zero() 提示控制
- 重构 FoodVectorTool:移除单例 pageNum 状态污染问题,分页逻辑下沉为私有 fetchPage,saveFoodVector 改为私有,PAGE_SIZE 移入内部定义,对外统一暴露 loadAndSaveFoodVector 接口
- 更新 InitActivity、MainActivity 调用方,移除重复的 onPageFinish 回调
- SensorScaleUtils.zero() 新增 isShowToastRemind 可选参数,支持按需显示标定成功提示

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 09:25:03 +08:00
lvmeng ab0d9b35f5 feat(data): 更新食物营养数据模型并优化向量数据处理
- 在DataBean中将肉蛋相关字段重命名为肉蛋豆,并添加总重量字段
- 新增FoodVectorTool工具类用于处理食物向量数据的分页获取和存储
- 重构InitActivity中的食物向量数据获取逻辑,使用新的工具类替代原有实现
- 在MainActivity中集成食物向量数据同步功能,在清除本地数据后重新获取
- 更新MainScreenPresentation中营养信息显示逻辑,适配新的数据模型结构
- 修改ApiClient中的日志标记逻辑,为不同接口添加特定的标签
- 调整食物订单列表的数据更新方式,优化适配器通知策略
2026-04-03 18:04:59 +08:00
mazengfeiandClaude Sonnet 4.6 0127697560 fix(main): 优化重量识别逻辑与日志;防抖时间调整为2s;从版本控制中移除.idea和objectbox-models
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:09:14 +08:00
mazengfei dbe3753821 人脸数据重置后加刷新逻辑;人脸识别距离调整; 2026-04-03 10:40:57 +08:00
lvmeng b16147b1b2 feat(face): 添加人脸识别距离过滤功能
- 在FaceHelper中添加人脸宽度小于200像素时的过滤逻辑
- 当检测到人脸过小时自动清除左侧人脸数据
- 防止远距离小人脸影响识别准确性
- 保留原有的活体检测流程不变
2026-04-02 19:13:15 +08:00
lvmeng d6528d33fc refactor(MainActivity): 将人脸数据清除操作移至后台线程执行
- 使用 lifecycleScope.launch 在协程中执行人脸数据清除
- 通过 withContext(Dispatchers.IO) 确保清除操作在 IO 线程运行
- 避免主线程阻塞提升界面响应性能
- 保持原有的用户提示和缓存更新逻辑不变
2026-04-02 17:45:41 +08:00
lvmeng 5479091959 refactor(main): 将Thread替换为lifecycleScope并优化协程处理
- 替换CollectFragment中的Thread为activity.lifecycleScope.launch
- 在MainActivity中添加FaceServer相关依赖和协程调度器
- 修改环境切换对话框以支持人脸数据重置功能
- 将照片拍摄和食物向量保存操作从Thread迁移到lifecycleScope
- 优化人脸识别数据更新逻辑,移除不必要的线程创建
- 修改onTakePhotoSuccess方法为suspend函数并调整UI更新方式
- 更新主线程操作使用withContext(Dispatchers.Main)替代runOnUiThread
- 在MainScreenPresentation中添加相机暂停功能
- 修复SensorScaleUtils中标定调用问题
- 扩展UserViewModel的人脸数据获取接口,支持回调处理
2026-04-02 17:35:37 +08:00
mazengfei 12da153df3 优化 2026-04-01 17:11:00 +08:00
mazengfei 3ef93c5f5e 配置生产域名 2026-03-31 19:08:39 +08:00
lvmeng 91800a6bb1 feat(app): 添加环境切换功能并优化基础URL管理
- 在主界面标题时间区域添加点击事件,支持弹出环境切换对话框
- 新增EnvSwitchDialog类实现TEST/UAT/PROD三套环境切换功能
- 新增dialog_env_switch.xml布局文件定义环境切换弹窗UI
- 重构GlobalData类,统一管理基础URL常量并添加持久化支持
- 修改MyApp初始化逻辑,优先读取用户手动设置的环境配置
- 添加SpTool.baseUrl属性用于环境配置的本地存储
- 修复MainActivity布局文件中的TextView垂直内边距问题
- 调整食物识别防抖时间从500ms到3000ms,优化计算营养逻辑执行时机
2026-03-31 09:48:32 +08:00
mazengfei f40c2046f1 优化 2026-03-30 18:31:24 +08:00
mazengfei ee9f27bb9c 优化 2026-03-30 18:03:01 +08:00
lvmeng 5c230e38c3 refactor(activity): 重构 BaseActivity 权限管理和页面跳转功能
- 在 BaseActivity 中新增权限请求和页面跳转的通用方法
- 移除 InitActivity 中原有的相机权限请求相关代码
- 使用 BaseActivity 的通用权限请求方法替代原有实现
- 添加网络连接检测工具类 NetworkUtils
- 在 WiFi 设置页面返回后重新检查网络连接状态
- 优化按钮可见性控制逻辑
2026-03-30 14:01:13 +08:00
lvmeng 10d1f966da 配置接口新增结算模式字段;优化页面刷新; 2026-03-27 17:21:45 +08:00
lvmeng 57f8920442 优化订单显示档口机标识;优化当前菜品信息的问题; 2026-03-26 18:57:20 +08:00
lvmeng fb4278d2b7 联合支付余量计量模式订单显示当前取餐信息 2026-03-25 15:42:22 +08:00
lvmeng fcea0d785d 联合支付功能测试 2026-03-24 18:34:38 +08:00
lvmeng c4a6101a7b 联合支付功能调试 2026-03-23 18:13:55 +08:00
lvmeng bf8444e5d5 联合支付功能调试 2026-03-20 18:06:22 +08:00
lvmeng 549e8d303f 联合支付功能 2026-03-19 17:44:13 +08:00
lvmeng b51be58c64 联合支付模式使用人脸识别生成就餐记录 2026-03-19 14:19:20 +08:00
lvmeng a6d2441704 优化:修复眼睛图标显示效果 2026-03-18 16:14:47 +08:00
lvmeng e7d11530a6 优化:修改消费码输入框为密码模式,添加显示/隐藏密码切换功能 2026-03-18 15:55:06 +08:00
lvmeng c56769769b 优化:修改消费码输入框为密��模式,添加显示/隐藏密码切换功能 2026-03-18 15:42:50 +08:00
lvmeng 8673cec545 优化:修改人脸识别任务延迟参数和添加相机权限检查 2026-03-18 14:06:42 +08:00
lvmeng 8ec7ea6a79 优化 2026-03-13 15:42:37 +08:00
lvmeng a80e3d1ba6 ObjectBox数据库操作增加线程资源清理 2026-03-12 17:25:36 +08:00
mazengfei 6d825d7235 扫码支付增加会员id字段 2026-03-12 12:39:35 +08:00
lvmeng 4f1860eb37 存在待支付订单时停止识别 2026-03-12 11:43:20 +08:00
mazengfei 45a7bf8369 优化非支付页面扫码点击问题 2026-03-11 18:34:49 +08:00
lvmengandClaude Sonnet 4.6 f0b50ef82f 修复MainActivity中扫码枪按键事件触发页面按钮点击问题
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 14:54:52 +08:00
lvmeng 824dc5f867 优化扫码支付结果会员非会员显示 2026-03-11 13:55:29 +08:00
lvmengandClaude Sonnet 4.6 7232735073 修复MainActivity停止时CameraX NPE崩溃
1. 升级CameraX版本 1.3.0 → 1.4.1,从根本修复RequestWithCallback.abort() NPE
2. 跳转PayActivity前调用shutdownCamera(),防止in-flight拍照请求触发生命周期异常
3. onResume()顶部增加相机恢复逻辑,通过cameraProviderFuture==null判断是否需要重新初始化
4. shutdownCamera()中将cameraProviderFuture置null,作为相机已停止的可靠标志

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 19:05:28 +08:00
mazengfei 8a1630f2a4 优化扫码枪支付 2026-03-10 14:44:34 +08:00
lvmeng 39c0ca7d72 优化人脸识别区域效果 2026-03-10 11:10:23 +08:00
lvmeng dfde8c0fad 人脸全量接口只首次请求 2026-03-10 10:53:18 +08:00
lvmeng aca84f1e94 Rename .java to .kt 2026-03-10 10:53:18 +08:00
lvmeng 7bcb74d9e0 增加支付成功语音提示;增加扫码支付; 2026-03-10 09:41:34 +08:00
mazengfei 6c857dd1f7 优化 2026-03-09 21:59:04 +08:00
mazengfei fd0f9977b1 使用人脸接口中的member字段判断是否会员;逻辑优化; 2026-03-09 21:26:02 +08:00
lvmeng ec4a5801c2 支付页面统一副屏逻辑 2026-03-09 19:56:09 +08:00
92 changed files with 5587 additions and 1976 deletions
+3 -6
View File
@@ -1,15 +1,12 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
app/objectbox-models/*.json
app/objectbox-models/*.json.bak
-3
View File
@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>
-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-02-06T02:13:05.230327500Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.170:5555;connection=1ec19d1e" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>
-20
View File
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="azul-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/lib_face" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>
-9
View File
@@ -1,9 +0,0 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+4
View File
@@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready
+11 -7
View File
@@ -51,13 +51,15 @@ android {
kotlinOptions {
jvmTarget = "11"
}
viewBinding {
enable = true
buildFeatures {
viewBinding = true
buildConfig = true
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
//implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
@@ -108,17 +110,19 @@ dependencies {
implementation(libs.pytorch.android)
implementation(libs.pytorch.android.torchvision)
implementation("io.github.scwang90:refresh-layout-kernel:3.0.0-alpha")
implementation("io.github.scwang90:refresh-header-classics:3.0.0-alpha")
implementation(libs.baseRecyclerViewAdapterHelper4)
implementation(libs.refresh.layout.kernel)
implementation(libs.refresh.header.classics)
val objectboxVersion = "5.0.1"
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
debugImplementation(libs.objectbox.android.objectbrowser)
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
// implementation("io.objectbox:objectbox-fulltext:5.0.1")
implementation(libs.androidx.recyclerview)
implementation("org.greenrobot:eventbus:3.2.0")
implementation(libs.eventbus)
}
apply(plugin = "io.objectbox")
+21
View File
@@ -0,0 +1,21 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.sw.dualscreen",
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "档口双屏_1.0-debug.apk"
}
],
"elementType": "File",
"minSdkVersionForDexing": 29
}
Binary file not shown.
Binary file not shown.
-77
View File
@@ -1,77 +0,0 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "1:594331511073099531",
"lastPropertyId": "11:7949771231314213889",
"name": "Food",
"properties": [
{
"id": "1:2317727855243736226",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "4:1528837175750321569",
"name": "foodVector",
"indexId": "1:6010838397086628487",
"type": 28,
"flags": 8
},
{
"id": "5:5561277398349179032",
"name": "collectId",
"type": 9
},
{
"id": "6:8313847979709019172",
"name": "foodId",
"type": 9
},
{
"id": "7:1375536603452792462",
"name": "foodName",
"type": 9
},
{
"id": "8:7942065542192169896",
"name": "version",
"type": 9
},
{
"id": "9:4394472487596452023",
"name": "otherField",
"type": 9
},
{
"id": "10:5704890122356491756",
"name": "createTime",
"type": 9
},
{
"id": "11:7949771231314213889",
"name": "isDel",
"type": 1
}
],
"relations": []
}
],
"lastEntityId": "1:594331511073099531",
"lastIndexId": "1:6010838397086628487",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [
340951913928211738,
5484846939472684412
],
"retiredRelationUids": [],
"version": 1
}
+37
View File
@@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.sw.dualscreen",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "档口双屏_1.0-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/档口双屏_1.0-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/档口双屏_1.0-release.dm"
]
}
],
"minSdkVersionForDexing": 29
}
+4 -2
View File
@@ -61,11 +61,13 @@
<!-- </activity>-->
<!-- <activity android:name="com.sw.dualscreen.activity.CollectedDataActivity" />-->
<activity android:name="com.sw.dualscreen.activity.SettingActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:launchMode="singleTask" />
<activity android:name="com.sw.dualscreen.activity.CollectedFoodActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity android:name="com.sw.dualscreen.activity.PayActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:launchMode="singleTask"/>
<provider
android:name="androidx.core.content.FileProvider"
@@ -17,11 +17,14 @@ object GlobalData {
*/
var appVersion: String = "1"
var appBaseUrl: String = ""
/**
* 具体业务baseurl
* 具体业务 BaseUrl
*/
var appBaseUrl2: String = "http://192.168.1.201:14801"
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
const val TEST_BASE_URL = "http://192.168.1.201:14801"
const val UAT_BASE_URL = "https://dev.yixiong-tech.com:8083"
const val PROD_BASE_URL = "https://api.dm.yixiong-tech.com:8443"
/**
* 菜品识别模型版本号,后边以接口返回为准
@@ -68,4 +71,6 @@ object GlobalKey {
const val KEY_USER_INFO = "userInfoKey"
const val KEY_PICKUP_MODE = "pickupMode"
const val KEY_CHARGE_MODE = "chargeMode"
const val KEY_SETTLEMENT_MODE = "settlementMode"
const val KEY_BASE_URL = "baseUrlKey"
}
+14 -2
View File
@@ -6,6 +6,8 @@ import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.sdk.SensorScaleUtils
import com.sw.dualscreen.utils.ActivityManager
import com.sw.dualscreen.utils.CrashHandler
import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.utils.SpTool
import com.sw.plate.App
import com.sw.plate.utils.AppUtil
import timber.log.Timber
@@ -13,7 +15,7 @@ import timber.log.Timber
class MyApp : App() {
companion object {
const val DEBUG: Boolean = true
var instance: MyApp?=null
var instance: MyApp? = null
}
override fun onCreate() {
@@ -24,13 +26,23 @@ class MyApp : App() {
Timber.d("deviceId = $deviceId")
// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
GlobalData.deviceId = deviceId
GlobalData.appBaseUrl = if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) GlobalData.appBaseUrl2 else GlobalData.appBaseUrl
ObjectBox.init(this)
// 初始化崩溃处理器
CrashHandler.init(this)
SensorScaleUtils.startScale()
addActivityLifecycleListener()
// 优先读取用户手动切换后持久化的 url
val savedUrl = SpTool.baseUrl
if (!savedUrl.isNullOrEmpty()) {
GlobalData.appBaseUrl = savedUrl
} else {
// 未保存过则使用默认逻辑:特定设备走测试环境,其余走 UAT
GlobalData.appBaseUrl =
if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) GlobalData.TEST_BASE_URL else GlobalData.PROD_BASE_URL
}
}
private fun addActivityLifecycleListener() {
@@ -1,6 +1,7 @@
package com.sw.dualscreen.activity
import android.content.Context
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.hardware.display.DisplayManager
import android.os.Bundle
@@ -59,7 +60,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
override fun onDestroy() {
keyEventHelper?.onDestroy()
keyEventHelper?.release()
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@@ -153,11 +154,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
*/
fun registerKeyEvent() {
keyEventHelper =
ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener {
ScanGunKeyEventHelper( object : ScanGunKeyEventHelper.OnScanSuccessListener {
override fun onScanSuccess(barcode: String?) {
Timber.d("onScanSuccess barcode = $barcode")
if (barcode == null) return
handleScanKeyInfo(barcode)
scanGunKeyEventCallback(barcode)
}
})
}
@@ -166,13 +167,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/**
* 处理扫描枪数据
*/
protected open fun handleScanKeyInfo(scanInfo: String) {
var payCode = scanInfo
//if (scanInfo.length > 6) {
// payCode = scanInfo.take(6)
//}
Log.d("TAG", "handleScanKeyInfo: " + payCode)
fun scanGunKeyEventCallback(scanInfo: String){
if (this is PayActivity) {
qrCodePay(scanInfo)
}
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (keyEventHelper != null) {
@@ -183,4 +183,52 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
return super.dispatchKeyEvent(event)
}
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var activityCallback: ((intent: Intent?) -> Unit)? = null
fun requestMultiplePermissions(
permissions: Array<String>,
callback: (isGranted: Boolean) -> Unit
) {
this.permissionCallback = callback
requestMultiplePermissionsLauncher.launch(permissions)
}
fun requestPermission(permission: String, callback: (isGranted: Boolean) -> Unit) {
this.permissionCallback = callback
requestPermissionLauncher.launch(permission)
}
val requestMultiplePermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
var isGranted = true
permissions.entries.forEach {
if (!it.value) {
isGranted = false
}
}
permissionCallback?.invoke(isGranted)
}
// 权限请求回调
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)
}
}
@@ -48,6 +48,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
override fun initialize() {
super.initialize()
registerKeyEvent()
binding.root.setOnClickListener { it.hideKeyboard() }
binding.rvFoodList.let {
it.layoutManager = LinearLayoutManager(this)
@@ -1,27 +1,32 @@
package com.sw.dualscreen.activity
import android.Manifest
import android.content.Intent
import android.os.CountDownTimer
import android.provider.Settings
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.databinding.ActivityInitBinding
import com.sw.dualscreen.dialog.RemindDialog
import com.sw.dualscreen.ext.clickWithDebounce
import com.sw.dualscreen.ext.invisible
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.utils.FoodVectorTool
import com.sw.dualscreen.utils.L
import com.sw.dualscreen.utils.NetworkUtils
import com.sw.dualscreen.utils.SpTool
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.AppUtil
import com.sw.plate.utils.ToastUtils
import kotlinx.coroutines.launch
class InitActivity : BaseActivity<ActivityInitBinding>() {
companion object {
private const val PAGE_SIZE = 100
const val PAGE_SIZE = 100
}
private val userViewModel by viewModels<UserViewModel>()
@@ -36,55 +41,23 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
}
private fun getCollectedFoodVector(block: () -> Unit) {
userViewModel.getCollectedFoodVector(
param = mutableMapOf(
"pageNum" to "$pageNum",
"pageSize" to "$PAGE_SIZE",
"version" to GlobalData.foodModelVersion
FoodVectorTool.loadAndSaveFoodVector(
userViewModel = userViewModel,
lifecycleScope = lifecycleScope,
successBlock = { block() },
failureBlock = { getVectorErrorDialog() }
)
) { items ->
if (items == null) {
getVectorErrorDialog()
return@getCollectedFoodVector
}
val list = items
if (pageNum == 1 && list.isEmpty()) {
block()
return@getCollectedFoodVector
}
if (list.size >= PAGE_SIZE) {
saveFoodVector(list)
pageNum++
getCollectedFoodVector(block)
return@getCollectedFoodVector
}
if (list.isNotEmpty()) {
saveFoodVector(list)
block()
}
}
}
// private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
private fun saveFoodVector(list: List<FoodVector>) {
lifecycleScope.launch {
val vectorList = list.map { vt ->
val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")
?.map { it.toFloatOrNull() ?: 0.0f }?.toFloatArray()
Food(
collectId = vt.id,
foodId = vt.foodId,
foodName = vt.foodName,
version = vt.version,
foodVector = foodVector
)
}
ObjectBox.putAll(vectorList)
}
}
override fun initialize() {
super.initialize()
//写死,后续改为从接口获取结算类型 ------------------------------------------
//val settlementMode = 1
//SPUtil.getInstance().put(GlobalKey.KEY_SETTLEMENT_MODE, settlementMode)
registerKeyEvent()
initNetworkTimer()
binding.okButton.clickWithDebounce(500) {
checkNetworkTimer?.cancel()
@@ -120,6 +93,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
}
private fun getCollectedFoodVector() {
binding.okButton.invisible()
showWaitingDialog2("加载中……")
lifecycleScope.launch {
FoodModule.init(this@InitActivity) {
@@ -127,7 +101,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
runOnUiThread {
hideWaitingDialog()
binding.root.postDelayed({
goMainActivity()
checkCameraPermissionAndGo()
}, 1000)
}
}
@@ -141,6 +115,57 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
finish()
}
private fun hasCameraPermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == android.content.pm.PackageManager.PERMISSION_GRANTED
}
// 检查相机权限
private fun checkCameraPermissionAndGo() {
val isGrantedCameraPermission = hasCameraPermission()
if (isGrantedCameraPermission) {
// 有权限,直接打开 MainActivity
goMainActivity()
} else {
// 无权限,请求相机权限
requestPermission(Manifest.permission.CAMERA) { isGranted ->
checkCameraPermission(isGranted)
}
}
}
// 从设置页面返回后检查权限
private fun checkCameraPermission(isGranted: Boolean) {
if (isGranted) {
// 权限已授予,打开 MainActivity
goMainActivity()
} else {
// 权限仍未授予,再次显示弹窗
showPermissionDeniedDialog()
}
}
// 显示权限被拒绝的弹窗
private fun showPermissionDeniedDialog() {
RemindDialog(
context = context,
content = "暂无相机权限,请授权后重试",
confirmBlock = {
// 打开 App 设置页面
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = android.net.Uri.fromParts("package", packageName, null)
}
startActivity(intent) {
// 从设置页面返回,重新检查权限
val isGrantedCameraPermission = hasCameraPermission()
checkCameraPermission(isGrantedCameraPermission)
}
}
).show()
}
private var checkNetworkTimer: CountDownTimer? = null
private fun initNetworkTimer() {
@@ -157,17 +182,47 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
loadNetworkErrorDialog()
return
}
getDeviceConfig()
getCollectedFoodVector()
}
}
}
private fun getDeviceConfig() {
userViewModel.getDeviceConfig { deviceConfig ->
runOnUiThread {
if (deviceConfig == null) {
ToastUtils.showToast("获取设备配置数据失败")
return@runOnUiThread
}
//var settlementMode = 1
//结算模式:1-独立支付,2-联合结算
val settlementMode = if (deviceConfig.payType == 2) 0 else 1
// TODO: 2022/10/13 修改为独立支付
SpTool.settlementMode = settlementMode
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
}
}
}
private fun loadNetworkErrorDialog() {
RemindDialog(
context = context,
content = "网络连接异常,请检查WiFi连接状态后重试",
confirmBlock = {
startActivity(Intent("android.settings.WIFI_SETTINGS"))
val intent = Intent("android.settings.WIFI_SETTINGS")
startActivity(intent) {
// 从设置页面返回,重新检查网络连接状态
val isConnected = NetworkUtils.isNetworkConnected(this@InitActivity)
if (isConnected.not()) {
loadNetworkErrorDialog()
return@startActivity
}
getCollectedFoodVector()
}
}).show()
}
@@ -13,6 +13,7 @@ import android.text.TextUtils
import android.view.Display
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.camera.core.CameraSelector
@@ -22,23 +23,30 @@ import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.common.util.concurrent.ListenableFuture
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.adapter.MainFoodListAdapter
import com.sw.dualscreen.databinding.ActivityMainBinding
import com.sw.dualscreen.databinding.LayoutEatDetailBinding
import com.sw.dualscreen.databinding.LayoutMainFoodList2Binding
import com.sw.dualscreen.databinding.LayoutMainFoodListBinding
import com.sw.dualscreen.dialog.EnvSwitchDialog
import com.sw.dualscreen.dialog.RemindDialog
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.invisible
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.ChargeModeEvent
import com.sw.dualscreen.model.response.ClickBackEvent
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.PaySuccessEvent
import com.sw.dualscreen.model.response.ResetRecognizeEvent
import com.sw.dualscreen.model.response.UpdateRefreshEvent
import com.sw.dualscreen.model.response.UserFaceModel
import com.sw.dualscreen.objbox.Food
@@ -51,6 +59,7 @@ import com.sw.dualscreen.socket.TcpClient
import com.sw.dualscreen.utils.ActivityManager
import com.sw.dualscreen.utils.BitmapSaver
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.FoodVectorTool
import com.sw.dualscreen.utils.GsonUtils
import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.utils.IntervalExecutor
@@ -64,22 +73,26 @@ import com.sw.plate.utils.LightManager
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.plate.utils.arcface.faceserver.FaceServer
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.json.JSONObject
import timber.log.Timber
import java.io.File
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.roundToInt
/**
@@ -91,6 +104,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
companion object {
private const val TAG = "MainActivity"
const val TIME_OUT = 5 * 60 * 1000L
const val WEIGHT_CHANGE_VALUE = 20
const val WEIGHT_RESET_RECOGNIZE = 10
}
private val viewModel by viewModels<UserViewModel>()
@@ -101,23 +117,47 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private var presentation: MainScreenPresentation? = null
private val executor = Executors.newSingleThreadExecutor()
private val handler by lazy { Handler(Looper.getMainLooper()) }
private val searchFoodList = mutableListOf<FoodInfo>()
private val adapter by lazy {
MainFoodListAdapter(searchFoodList).apply {
setOnItemClickListener { adapter, view, position ->
if (searchFoodList[position].isChecked) {
MainFoodListAdapter(mutableListOf()).apply {
setOnItemClickListener { _, _, position ->
if (items[position].isChecked) {
return@setOnItemClickListener
}
searchFoodList.forEachIndexed { index, info ->
searchFoodList[index].isChecked = index == position
items.forEachIndexed { index, info ->
items[index].isChecked = index == position
}
val item = searchFoodList[position]
val item = items[position]
log("itemClick ${item.foodName}, position = $position")
item.photoUri = null
checkedItem = item
notifyDataSetChanged()
updateCurrentFood(item)
resetSelectedFood(item)
}
}
}
var isSwitchFood = false
/**
* 用于切换菜品时重置数据
*/
fun resetSelectedFood(foodInfo: FoodInfo) {
isSwitchFood = true
recognizeViewModel.resetFaceState()
updateCurrentFood(foodInfo)
if (settlementMode == 0) {
//联合支付,切换菜品重新请求接口
presentation?.let {
val userId = presentation?.currentUserId
if(userId.isNullOrBlank()) {
//切换菜品时,用户已离开重新打开识别功能
it.pauseCamera()
it.step2FaceRecognizing(foodInfo, true)
} else{
//切换菜品时,用户未离开,重新请求接口
it.step3ShowRecognizeResult(userId)
}
}
} else {
//0-计费,1-不计费
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
//不计费模式切换菜品,重新加载数据
if (mode == 1) {
@@ -126,13 +166,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
}
}
var checkedItem: FoodInfo? = null
private var imageAnalysis: ImageAnalysis? = null
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
private var canIdentify: Boolean = false
private var bottomSheetDialog: CustomBottomSheetDialog? = null
private var lastWeight: Double = 0.0
private var lastWeight: Int = 0
var currentWeight: Int = 0
private var lastPhotoUri: Uri? = null // 最后拍照的图片
private var debouncer = Debouncer(2000)
private var isRecognitionFood = true
@@ -140,6 +181,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private var isStartRecognize = false
//0-联合支付,1-独立支付
var settlementMode: Int = 1
// val settlementMode by lazy {
// SPUtil.getInstance().get(GlobalKey.KEY_SETTLEMENT_MODE, 1)
// }
private lateinit var rvFoodList: RecyclerView
private lateinit var tvToSearch: TextView
lateinit var layoutEatDetailBinding: LayoutEatDetailBinding
override fun getViewModel(): BaseViewModel {
return viewModel
}
@@ -150,13 +201,56 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initialize() {
super.initialize()
settlementMode = SpTool.settlementMode
isPageVisible = true
// 拦截扫码枪按键事件,防止触发页面按钮点击
registerKeyEvent()
addBackEventListener()
initView()
if (SpTool.firstGetFace) {
viewModel.getUserFaceCache(pageNo = 1)
}
setupCamera()
initData()
registerKeyEvent()
}
private fun initLayoutEatDetailBinding() {
Timber.tag(TAG).d("initLayoutEatDetailBinding,settlementMode=$settlementMode")
binding.flFoodList.removeAllViews()
if (settlementMode == 0) {
//联合支付
val b = LayoutMainFoodList2Binding.inflate(layoutInflater, binding.flFoodList, true)
rvFoodList = b.rvFoodList
tvToSearch = b.tvToSearch
layoutEatDetailBinding = b.detailInclude
layoutEatDetailBinding.llFoodTitle.gone()
// layoutEatDetailBinding.root.invisible()
} else {
//独立支付
val b = LayoutMainFoodListBinding.inflate(layoutInflater, binding.flFoodList, true)
rvFoodList = b.rvFoodList
tvToSearch = b.tvToSearch
layoutEatDetailBinding = b.detailInclude
layoutEatDetailBinding.llFoodTitle.gone()
layoutEatDetailBinding.root.gone()
}
tvToSearch.setOnClickListener {
bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
checkedItem = it
it.photoUri = null
adapter.items.forEachIndexed { index, info ->
adapter.items[index].isChecked = false
}
adapter.notifyDataSetChanged()
resetSelectedFood(it)
}
bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
}
rvFoodList.let {
it.layoutManager = GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false)
it.adapter = adapter
}
}
private fun initData() {
@@ -164,19 +258,39 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
private fun initView() {
viewModel.getDeviceConfig { deviceConfig ->
runOnUiThread {
if (deviceConfig == null) {
ToastUtils.showToast("获取设备配置数据失败")
return@runOnUiThread
}
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
viewModel.activeEngine()
}
}
initLayoutEatDetailBinding()
updateDateTime()
binding.tvTitleTime.setOnClickListener {
EnvSwitchDialog(context = this, onEnvChanged = {
showWaitingDialog("重置人脸数据中……")
lifecycleScope.launch {
withContext(Dispatchers.IO) {
FaceServer.getInstance().clearAllFaces()
ObjectBox.removeAll()
}
FoodVectorTool.loadAndSaveFoodVector(
userViewModel = viewModel,
lifecycleScope = lifecycleScope,
successBlock = {
viewModel.getUserFaceCache(pageNo = 1)
},
failureBlock = {
hideWaitingDialog()
ToastUtils.showToast(it)
}
)
}
viewModel.getUserFaceCache(pageNo = 1, onSuccess = {
recognizeViewModel.refreshFaceList()
hideWaitingDialog()
ToastUtils.showToast("人脸数据已更新")
}, onFailure = { errMsg ->
hideWaitingDialog()
ToastUtils.showToast(errMsg)
})
}).show()
}
binding.ivSetting.setOnClickListener {
isRefreshPage = false
startActivity(Intent(this, SettingActivity::class.java))
@@ -187,23 +301,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
binding.previewView.clipToOutline = true
binding.tvToSearch.setOnClickListener {
bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
checkedItem = it
it.photoUri = null
updateCurrentFood(it)
searchFoodList.forEachIndexed { index, info ->
searchFoodList[index].isChecked = false
}
adapter.notifyDataSetChanged()
}
bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
}
binding.recyclerview.let {
it.layoutManager = GridLayoutManager(this, 2)
it.adapter = adapter
}
binding.tvFoodName.setOnClickListener {
SensorScaleUtils.zero()
}
@@ -212,7 +309,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
private var recognizeWeight = 0.0
private var recognizeWeight = 0
fun updateCurrentFood(foodInfo: FoodInfo?) {
if (foodInfo == null) {
binding.tvFoodName.text = "-"
@@ -222,8 +319,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
return
}
pauseAnalysis()
recognizeWeight = lastWeight
log("registerDataChange,副屏updateFood调用前耗时:${System.currentTimeMillis() - startTime}")
recognizeWeight = currentWeight
log("registerDataChange,副屏updateFood调用前耗时:${System.currentTimeMillis() - startTime}, recognizeWeight = ${recognizeWeight}")
startTime = System.currentTimeMillis()
presentation?.updateFood(foodInfo)
binding.tvFoodName.text = foodInfo.foodName
@@ -237,17 +334,115 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.ivImg.load(imgUrl)
}
override fun registerDataChange() {
super.registerDataChange()
SensorScaleUtils.addWeightListener { weight ->
log("registerDataChange weight = $weight")
//runOnUiThread { binding.tvShowWeight.text="${weight*1000}克" }
if (lastWeight - weight > 0.005) {
//及放及取,从秤上拿走超过5克
if (presentation?.mealPickupMode == 0) {
/**
* 联合支付-即放即取模式
*/
private fun loadJFJQBySettlement(weight: Int) {
log("registerDataChange weight 联合支付-即放即取")
//即放即取+联合支付人脸查询订单完成
if (presentation!!.jointPaymentQueryFinish) {
log("registerDataChange weight 联合支付-即放即取,人脸查询订单完成")
if (weight > WEIGHT_RESET_RECOGNIZE) {
//已识别完成人脸并查询到订单数据,增加重量不再重新识别
log("registerDataChange weight 联合支付-即放即取,不再重新识别")
return
}
log("registerDataChange weight 联合支付-即放即取,step1FoodRecognizing")
//重量小于等于10克时改为识别中页面
isAnalyzing = false
presentation?.step1FoodRecognizing()
return
}
log("registerDataChange weight 联合支付-即放即取,人脸查询订单没有完成")
if (weight <= WEIGHT_RESET_RECOGNIZE) {
//秤上重量过小,显示识别中页面
log("registerDataChange weight 联合支付-即放即取,重量小于${WEIGHT_RESET_RECOGNIZE}g,step1FoodRecognizing")
presentation?.step1FoodRecognizing()
return
}
log("registerDataChange weight 联合支付-即放即取,recognizeByWeight")
// val isWeightChange = abs(weight - lastWeight) > 0.02
val isWeightChange = weight - lastWeight > WEIGHT_CHANGE_VALUE
if (weight != lastWeight) {
log("registerDataChange weight 联合支付-即放即取,recognizeByWeight---00001,weight=$weight,lastWeight=$lastWeight,isWeightChange=$isWeightChange")
}
recognizeByWeight(weight, isWeightChange)
}
/**
* 联合支付-余量计量模式
*/
private fun loadYLJLBySettlement(weight: Int) {
log("registerDataChange weight 联合支付-余量计量")
if (weight <= WEIGHT_RESET_RECOGNIZE) {
log("registerDataChange weight 联合支付-余量计量,重量小于${WEIGHT_RESET_RECOGNIZE}g,step1FoodRecognizing")
if (presentation?.currentUserId.isNullOrBlank().not() && presentation!!.jointPaymentQueryFinish) {
//联合支付+余量计量,已识别人脸的状态下,从秤上拿餐品后,不再读取数据,等待人脸离开提交订单
if (lastWeight != weight) {
presentation?.updateWeight(weight)
lastWeight = weight
return@addWeightListener
}
return
}
//秤上重量过小,显示识别中页面
presentation?.step1FoodRecognizing()
return
}
log("registerDataChange weight 联合支付-余量计量,订单完成状态:${presentation!!.jointPaymentQueryFinish}")
//余量计量+联合支付人脸查询订单完成
if (presentation!!.jointPaymentQueryFinish) {
log("registerDataChange weight 联合支付-余量计量,updateWeight")
if (lastWeight != weight) {
presentation?.updateWeight(weight)
log("registerDataChange weight 重置lastWeight与weigh${weight}一致----------------1000006")
lastWeight = weight
}
return
}
val isWeightChange = weight - lastWeight > WEIGHT_CHANGE_VALUE
recognizeByWeight(weight, isWeightChange)
}
private fun readWeight(weight: Int) {
currentWeight = weight
log("registerDataChange weight = $weight lastWeight = $lastWeight")
binding.tvShowWeight.let {
if (it.text.toString() != weight.toString()) {
it.text = "$weight"
}
}
if (settlementMode == 0) {
log("registerDataChange weight 联合支付模式")
//联合支付-------------------------------------------
//0-即放即取,1-余量计量
val mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
if (mealPickupMode == 1) {
loadYLJLBySettlement(weight)
return
}
if (presentation?.currentUserId.isNullOrBlank().not()) {
//联合支付+即放即取,已识别人脸的状态下,从秤上拿餐品后,不再读取数据,等待人脸离开提交订单
return
}
loadJFJQBySettlement(weight)
return
}
//独立支付-------------------------------------------
//0-即放即取,1-余量计量
val mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
if(mealPickupMode == 0 && chargeMode == 1) {
//即放即取+不计费模式,
if (presentation?.currentUserId.isNullOrBlank().not()) {
//已识别人脸的状态下,从秤上拿餐品后,不再读取数据,等待人脸离开提交订单
return
}
if (weight <= WEIGHT_RESET_RECOGNIZE) {
log("registerDataChange weight 秤重量小于${WEIGHT_RESET_RECOGNIZE}g")
//检测到秤上没有东西
presentation?.step1FoodRecognizing()
return
}
}
if (isPageVisible.not() || isStartRecognize.not()) {
@@ -255,46 +450,69 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
isPageVisible = true
isStartRecognize = true
}
return@addWeightListener
return
}
//0.05
// val isWeightChange = abs(weight - lastWeight) > 0.02
val isWeightChange = weight - lastWeight > 0.02
log("recognizeByWeight---00001,weight=$weight,lastWeight=$lastWeight,isWeightChange=$isWeightChange")
val isWeightChange = weight - lastWeight > WEIGHT_CHANGE_VALUE
recognizeByWeight(weight, isWeightChange)
}
override fun registerDataChange() {
super.registerDataChange()
SensorScaleUtils.addWeightListener { value ->
runOnUiThread {
if (foodRecognizeState.not()) {
if (value <= WEIGHT_RESET_RECOGNIZE) {
presentation?.step1FoodRecognizing()
}
return@runOnUiThread
}
readWeight(value)
}
}
}
private fun recognizeByWeight(weight: Double, isWeightChange: Boolean, block: () -> Unit = {}) {
presentation?.updateWeight(weight)
private fun recognizeByWeight(weight: Int, isWeightChange: Boolean, block: () -> Unit = {}) {
log("registerDataChange weight 开始识别")
if (weight != lastWeight) {
val payTypeDesc = if (settlementMode == 0) "联合支付" else "独立支付"
log("registerDataChange weight readWeight-${payTypeDesc},weight=$weight,lastWeight=$lastWeight,isWeightChange=$isWeightChange")
}
// presentation?.updateWeight(weight)
if (weight <= 0.005) {
log("recognizeByWeight---00002,秤重未超过5")
//检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
if (weight <= WEIGHT_RESET_RECOGNIZE) {
log("registerDataChange weight recognizeByWeight---00002,秤重未超过${WEIGHT_RESET_RECOGNIZE}")
//检测到秤上没有东西,重新启动识别菜品
isRecognitionFood = true
return
}
if (isWeightChange && isRecognitionFood) { // 大于50g
log("recognizeByWeight---00003,mealPickupMode=${presentation?.mealPickupMode}")
if (presentation?.mealPickupMode == 1) {
if (isWeightChange && isRecognitionFood) {
log("registerDataChange weight recognizeByWeight---00003,mealPickupMode=${presentation?.mealPickupMode}")
if (presentation?.mealPickupMode == 1 && checkedItem != null) {
isRecognitionFood = false
return
}
debouncer.debounce {
log("recognizeByWeight---00004")
log("registerDataChange weight recognizeByWeight---00004--recognizeFood")
//保存当前重量
currentWeight = weight
recognizeFood()
if (presentation?.mealPickupMode == 0) {
lastWeight = weight
}
}
} else {
log("registerDataChange weight recognizeByWeight---block")
block()
}
if (presentation?.mealPickupMode == 1 || settlementMode == 0) {
log("registerDataChange weight recognizeByWeight---lastWeight=${lastWeight}, weight=${weight}, checkedItem=${checkedItem}")
if (weight - lastWeight > WEIGHT_CHANGE_VALUE && checkedItem == null) {
debouncer.debounce {
log("registerDataChange weight重新进行菜品识别 ")
recognizeFood()
}
}
}
}
if (presentation?.mealPickupMode == 1) {
lastWeight = weight
}
}
private var startTime = 0L
@@ -304,12 +522,19 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
LightManager.closeRedLight()
LightManager.openGreenLight()
showWaitingDialog("识别中,请稍后……")
lifecycleScope.launch {
delay(5000)
withContext(Dispatchers.Main) {
hideWaitingDialog()
}
}
takePhoto(successCallback = { photoUri ->
Thread {
lifecycleScope.launch {
onTakePhotoSuccess(photoUri)
}.start()
}
failCount = 0
}, failureCallback = {
log("registerDataChange main,拍照异常,请重试")
//ToastUtils.showToast("拍照异常,请重试")
failCount++
hideWaitingDialog()
@@ -328,18 +553,29 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
*/
private var failCount = 0
/**
* 是否进行识别:true-识别,false-不识别,默认开启识别
*/
private var foodRecognizeState = true
@SuppressLint("NotifyDataSetChanged")
private fun updateFoodInfo(list: MutableList<FoodInfo>, scoreList: List<IdNameScore>) {
val queryData = GsonUtils.toJson(list)
val recData = GsonUtils.toJson(scoreList)
log("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData")
if (list.isEmpty()) {
binding.tvToSearch.let {
foodRecognizeState = false
tvToSearch.let {
it.text = "未查询到,手动搜索"
it.visible()
}
return
}
foodRecognizeState = true
//查询到菜品信息,同步设置lastWeight=currentWeight
lastWeight=currentWeight
presentation?.updateWeight(currentWeight)
list.forEach { foodInfo ->
val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName }
val score = scoreItem?.score ?: 0.0
@@ -353,9 +589,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// e.printStackTrace()
// list
// }
searchFoodList.clear()
searchFoodList.addAll(list)
adapter.notifyDataSetChanged()
adapter.submitList(list)
rvFoodList.visible()
checkedItem = list[0].also {
it.photoUri = lastPhotoUri
@@ -366,10 +602,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// lastPhotoUri = null
LightManager.closeRedLight()
binding.tvToSearch.let {
tvToSearch.let {
it.text = "以上都不是,手动搜索"
it.visible()
}
log("registerDataChange识别完成:visible=${rvFoodList.isVisible},count=${rvFoodList.childCount}")
}
private fun setupCamera() {
@@ -456,6 +693,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// 3. 关闭相关资源
imageCapture = null
imageAnalysis = null
cameraProviderFuture = null
// 4. 停止副屏更新
isAnalyzing = false
@@ -537,10 +775,19 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
@SuppressLint("NotifyDataSetChanged")
fun clearFoodList() {
//adapter.updateData(listOf())
searchFoodList.clear()
adapter.notifyDataSetChanged()
fun clearRecognizeFood() {
log("clearRecognizeFood")
rvFoodList.invisible()
layoutEatDetailBinding.root.invisible()
adapter.submitList(null)
tvToSearch.gone()
binding.previewView.visible()
binding.ivImg.gone()
checkedItem = null
lastWeight = 0
foodRecognizeState = true
isSwitchFood = false
}
private fun updateDateTime() {
@@ -554,6 +801,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onResume() {
super.onResume()
isPageVisible = true
// shutdownCamera 后 cameraProviderFuture 置为 null,需重新初始化相机
if (cameraProviderFuture == null) {
setupCamera()
}
// 启动定时器
handler.postDelayed(timeoutRunnable, TIME_OUT)
// ToastUtils.showToast("isPageVisible=$isPageVisible")
@@ -568,7 +819,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
log("onResume-00001isRefreshPage=$isRefreshPage")
if (presentation == null || !presentation!!.isShowing) {
log("onResume-00002,副屏未显示,加载副屏")
lastWeight = 0.0
lastWeight = 0
setupSecondaryDisplay()
presentation?.step1FoodRecognizing()
} else {
@@ -577,21 +828,23 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
isRefreshPage = false
//已显示副屏,则进行更新
val weight = lastWeight
lastWeight = 0.0
lastWeight = 0
presentation?.pauseCamera()
presentation?.hideRecImage()
presentation?.step1FoodRecognizing()
log("onResume-recognizeByWeight")
binding.root.postDelayed({
recognizeByWeight(weight, true)
}, 500)
}
}
}
//根据付费模式确认是否显示按钮
binding.btnPay.run {
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
if (mode == 0) visible() else gone()
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
if (settlementMode == 1 && chargeMode == 0) visible() else gone()
}
}
@@ -632,10 +885,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
eatWeight: Int,
eatNum: Int,
userId: String?,
isMember: Boolean,
notEnoughOneBlock: () -> Unit = {},
block: () -> Unit
successBlock: () -> Unit
) {
log("foodWeight=$foodWeight,eatWeight=$eatWeight")
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
//0-即放即取,1-余量计量
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
// if (pickupMode == 0) {
// if (foodWeight <= 5) {
@@ -653,7 +910,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
return
}
showWaitingDialog("加载中,请稍候……")
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
val order = FoodOrder(
deviceId = GlobalData.deviceId,
foodId = foodInfo.foodId,
@@ -667,12 +923,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
userId = userId,
notPay = chargeMode != 0,
//即放即取-1,称重-2
mode = if (pickupMode == 1) 1 else 2
mode = if (pickupMode == 1) 1 else 2,
paymentFrom = if (settlementMode == 0) 1 else 2,
member = isMember
)
submitOrder(order, block)
submitOrder(order, successBlock)
}
private fun submitOrder(order: FoodOrder, block: () -> Unit) {
private fun submitOrder(order: FoodOrder, successBlock: () -> Unit) {
viewModel.createOrder(order) { orderId ->
runOnUiThread {
binding.root.postDelayed({
@@ -682,13 +940,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
return@postDelayed
}
foodOrderId = orderId
block()
successBlock()
if (checkedItem!!.isFromSearch == true) {
if (checkedItem?.isFromSearch == true) {
//当前菜名为手动搜索选择,非识别结果,保存向量数据
Thread {
lifecycleScope.launch {
saveFoodVector(checkedItem!!)
}.start()
}
}
}, 300)
}
@@ -696,8 +954,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
fun getEatNum(realWeight: Double?, specWeight: Double?): Int {
if (realWeight == null || realWeight <= 0.0 || specWeight == null || specWeight <= 0.0) return 0
return (realWeight / specWeight).roundToInt()
if (realWeight == null || realWeight <= 0.0 || specWeight == null || specWeight <= 0.0) {
log("getEatNum:realWeight=$realWeight,specWeight=$specWeight")
return 0
}
val eatNum = (realWeight / specWeight).roundToInt()
log("getEatNum:eatNum=$eatNum,realWeight=$realWeight,specWeight=$specWeight,realWeight/$specWeight=${realWeight / specWeight}}")
return eatNum;
}
private var foodOrderId: String = ""
@@ -721,16 +984,27 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUpdateRefreshEvent(event: UpdateRefreshEvent) {
isRefreshPage = true
checkedItem = null
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPaySuccessEvent(event: PaySuccessEvent) {
isRefreshPage = true
checkedItem = null
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) ?: 0
presentation?.hideNutritionView(chargeMode)
presentation?.cancel()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onResetRecognizeState(event: ResetRecognizeEvent) {
// presentation?.step1FoodRecognizing()
isRefreshPage = true
checkedItem = null
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onChargeModeEvent(event: ChargeModeEvent) {
isRefreshPage = true
@@ -754,49 +1028,53 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// }
private fun clickPayButton() {
log("eatWeight:recognizeWeight=$recognizeWeight, currentWeight=$currentWeight, lastWeight=$lastWeight")
if (checkedItem == null) {
ToastUtils.showToast("暂无识别数据,请搜索选择")
return
}
//val realWeight = (lastWeight * 1000).roundToInt()
//0-即放即取,1-余量计量
val mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) ?: 0
var foodWeight = 0.0
var eatWeight = 0.0
var foodWeight = 0
var eatWeight = 0
if (chargeMode == 0) {
//计费
if (mealPickupMode == 0) {
//即放即取
eatWeight = lastWeight * 1000
foodWeight = lastWeight * 1000
eatWeight = currentWeight
foodWeight = currentWeight
} else {
//余量计量
eatWeight = (recognizeWeight - lastWeight) * 1000
foodWeight = recognizeWeight * 1000
log("eatWeight=$eatWeight, recognizeWeight=$recognizeWeight, lastWeight=$lastWeight")
eatWeight = recognizeWeight - currentWeight
foodWeight = recognizeWeight
log("eatWeight=$eatWeight, recognizeWeight=$recognizeWeight, currentWeight=$currentWeight, lastWeight=$lastWeight")
}
} else {
//不计费
foodWeight = recognizeWeight * 1000
foodWeight = recognizeWeight
eatWeight = if (mealPickupMode == 0) {
//即放即取
recognizeWeight * 1000
recognizeWeight
} else {
//余量计量
(recognizeWeight - lastWeight) * 1000
recognizeWeight - currentWeight
}
}
val eatNum = getEatNum(eatWeight, checkedItem!!.specWeight)
val eatNum = getEatNum(eatWeight.toDouble(), checkedItem!!.specWeight)
createOrder(
foodInfo = checkedItem!!,
foodWeight = foodWeight.roundToInt(),
eatWeight = eatWeight.roundToInt(),
foodWeight = foodWeight,
eatWeight = eatWeight,
eatNum = eatNum,
userId = null,
isMember = false
) {
isRefreshPage = false
//presentation?.dismiss()
shutdownCamera()
startActivity(Intent(this, PayActivity::class.java).apply {
putExtra(PayActivity.FOOD_INFO, checkedItem!!)
putExtra(PayActivity.FOOD_EAT_NUM, eatNum)
@@ -915,16 +1193,17 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// private val initialDelay = 5 * 60 * 1000L
// private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 30 * 1000L
private val dealyMillis = 3 * 60 * 1000L
// private var taskPageNo = 1
private val initialDelay = 10 * 1000L
private val dealyMillis = 5 * 1000L
// private var taskPageNo = 1
fun startFaceTask() {
faceTaskJob =
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
val timestamp = SpTool.getLastFaceTimestamp()
if (timestamp == 0L) {
return@startIntervalTaskWithInitialDelay
}
val timestamp = SpTool.lastFaceTimestamp
//if (timestamp == 0L) {
// return@startIntervalTaskWithInitialDelay
//}
viewModel.getFaceIncrementList(
// pageNo = taskPageNo,
timestamp = timestamp,
@@ -936,14 +1215,15 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
if (list.isEmpty()) {
return@runOnUiThread
}
lifecycleScope.launch {
updateFaceData(list)
}
}
})
}
}
private fun updateFaceData(list: List<UserFaceModel>) {
Thread {
val faceList = mutableListOf<FaceEntity>()
try {
list.forEach { model ->
@@ -956,7 +1236,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
model.userId, null, Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.isMember) "1" else "2"
it.userType = if (model.member) "1" else "2"
it.cardNo = model.cardNo
}
faceList.add(faceEntity)
@@ -973,7 +1253,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
private fun sendFaceData(faceData: String) {
@@ -987,18 +1266,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private var tcpClient: TcpClient? = null
private fun onTakePhotoSuccess(photoUri: Uri) {
private suspend fun onTakePhotoSuccess(photoUri: Uri) {
lastPhotoUri = photoUri
log("registerDataChange main,takePhoto耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
log("registerDataChange photoUri = ${photoUri.path}")
// viewModel.getIdentifiedFoodList()
lifecycleScope.launch {
ImageUtil.uriToBitmap(this@MainActivity, photoUri)?.let { bitmap ->
queryFoodData(bitmap)
}
}
}
private suspend fun queryFoodData(bitmap: Bitmap) {
log("registerDataChange main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}")
@@ -1025,11 +1302,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
hideWaitingDialog()
log("registerDataChange photoUri 识别数据名称:$foodName")
runOnUiThread {
withContext(Dispatchers.Main) {
binding.flPay.run {
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
//0-计费,1-不计费
if (chargeMode == 0) visible() else gone()
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
//独立支付+计费模式才显示“去结算”按钮,否则使用人脸识别
if (settlementMode == 1 && chargeMode == 0) visible() else gone()
}
}
if (TextUtils.isEmpty(foodName)) {
@@ -1037,8 +1315,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
LightManager.closeRedLight()
//binding.layoutRescan.visibility = View.VISIBLE
runOnUiThread {
binding.tvToSearch.let {
withContext(Dispatchers.Main) {
tvToSearch.let {
it.text = "未识别到,手动搜索"
it.visible()
}
@@ -1063,4 +1341,5 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// ObjectBox.init(this)
// }
}
@@ -1,5 +1,6 @@
package com.sw.dualscreen.activity
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import com.sw.dualscreen.GlobalKey
@@ -14,13 +15,15 @@ import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.PaySuccessEvent
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.utils.SoundPoolUtil
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
class PayActivity : BaseActivity<ActivityPayBinding>() {
@@ -37,6 +40,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
const val TAG_PAY_NUMBER = "tagPayNumber"
const val TAG_PAY_FACE = "tagPayFace"
const val TAG_PAY_RESULT = "tagPayResult"
const val PAY_SUCCESS = "pay_success"
}
val userViewModel by viewModels<UserViewModel>()
@@ -61,10 +65,31 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
var foodOrderId: String = ""
var eatNum: Int = 1
var isMember: Boolean = false
val presentation by lazy {
PayPresentation(
activity = this@PayActivity,
userViewModel = userViewModel,
recognizeViewModel = recognizeViewModel,
display = displays[1]
)
}
fun playSoundOnPaySuccess() {
SoundPoolUtil.getInstance().play(PAY_SUCCESS, 0)
}
@Suppress("DEPRECATION")
override fun initialize() {
super.initialize()
presentation.show()
registerKeyEvent()
addBackEventListener()
val soundMap = hashMapOf<String, Int>()
soundMap[PAY_SUCCESS] = R.raw.pay_success
SoundPoolUtil.getInstance().loadR(this, soundMap)
foodInfo = intent.getParcelableExtra(FOOD_INFO)
foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: ""
eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1)
@@ -74,12 +99,14 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
binding.include.ivPageBack.setOnClickListener { finish() }
binding.include.tvPageTitle.text = "下单结算"
binding.btnPayQrCode.setOnClickListener {
isMember = false
if (binding.btnPayQrCode.isChecked) {
return@setOnClickListener
}
showScanQrCodePay()
}
binding.btnPayCash.setOnClickListener {
isMember = false
if (binding.btnPayCash.isChecked) {
return@setOnClickListener
}
@@ -101,7 +128,6 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
}
showFacePay()
}
showScanQrCodePay()
}
@@ -136,46 +162,68 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
}
override fun onDestroy() {
qrCodePayFragment?.presentation?.dismiss()
cashPayFragment?.presentation?.dismiss()
numberPayFragment?.presentation?.dismiss()
facePayFragment?.presentation?.dismiss()
scanQrCodePayPresentation?.dismiss()
presentation.dismiss()
super.onDestroy()
}
private var scanQrCodePayPresentation: ScanQrCodePayPresentation? = null
var memberDiscount = 1.0
var memberInfo: MemberInfo? = null
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
this.memberInfo = memberInfo
payResultFragment = PayResultFragment.instance(type, memberInfo)
showFragment(payResultFragment!!, TAG_PAY_RESULT)
if (displays.size > 1) {
scanQrCodePayPresentation = ScanQrCodePayPresentation(
activity = this,
display = displays[1],
type = type
) {
scanQrCodePayPresentation?.dismiss()
}.also {
// if (displays.size > 1) {
// scanQrCodePayPresentation = ScanQrCodePayPresentation(
// activity = this,
// display = displays[1],
// type = type
// ) {
// scanQrCodePaypresentation.dismiss()
// }.also {
// it.foodName = foodInfo?.foodName
// //确认使用vipPrice 还是 specPrice
// val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
// it.totalPrice = (onePrice ?: 0.0) * eatNum
// }
// scanQrCodePaypresentation.show()
// }
presentation.let {
it.pageType = PayPresentation.QR_CODE_PAY
it.type = type
it.foodName = foodInfo?.foodName
//确认使用vipPrice 还是 specPrice
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
it.totalPrice = (onePrice ?: 0.0) * eatNum
//val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
if (isVip) {
userViewModel.getMemberDiscount(memberInfo?.faceUserId?:"") { discount ->
memberDiscount = if (discount == null) 1.0 else discount / 10
var onePrice = foodInfo?.specPrice ?: 0.0
onePrice = if (isVip) onePrice * memberDiscount else onePrice
it.totalPrice = onePrice * eatNum
presentation.initView()
}
} else {
it.totalPrice = (foodInfo?.specPrice ?: 0.0) * eatNum
presentation.initView()
}
scanQrCodePayPresentation?.show()
}
}
fun updateQrCodeImage(qrCodeUrl: String?) {
scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl)
// scanQrCodePaypresentation.loadQrCodeImage(qrCodeUrl)
presentation.loadQrCodeImage(qrCodeUrl)
}
private var debouncer = Debouncer(5000)
fun showPaySuccess(isVip: Boolean) {
debouncer.debounce {
playSoundOnPaySuccess()
EventBus.getDefault().post(PaySuccessEvent())
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
hidePayTab()
}
}
fun getQrCodeImg(
orderId: String,
@@ -206,7 +254,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
}
}
fun memberPay(param: HashMap<String, String>, block: () -> Unit) {
fun memberPay(param: HashMap<String, String?>, block: () -> Unit) {
userViewModel.memberPay(param) {
runOnUiThread {
if (it.not()) {
@@ -251,6 +299,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
}
fun bindOrder(userId: String, block: (Boolean) -> Unit) {
//0-即放即取,1-余量计量
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
val mode = if (pickupMode == 1) 1 else 2
userViewModel.bindOrder(userId, foodOrderId, mode = mode) { bindResult ->
@@ -260,6 +309,18 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
}
}
fun qrCodePay(scanInfo: String) {
showWaitingDialog("支付中……")
userViewModel.qrCodePay(scanInfo, foodOrderId, memberInfo?.id) { payState, backData ->
Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData")
hideWaitingDialog()
if (payState) {
showPaySuccess(isMember)
}
}
}
// fun bindOrder(userId: String, block: () -> Unit) {
// val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
// val mode = if (pickupMode == 1) 1 else 2
@@ -303,4 +364,12 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
// }
// }
// }
fun addBackEventListener() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
})
}
}
@@ -32,6 +32,7 @@ class SettingActivity : BaseActivity<ActivitySettingBinding>() {
override fun initialize() {
super.initialize()
registerKeyEvent()
initBackDispatcher()
binding.include.let {
it.ivPageBack.setOnClickListener {
@@ -6,8 +6,10 @@ import com.sw.dualscreen.R
import com.sw.dualscreen.activity.SettingActivity
import com.sw.dualscreen.databinding.FragmentBusinessBinding
import com.sw.dualscreen.model.response.ChargeModeEvent
import com.sw.dualscreen.model.response.ResetRecognizeEvent
import com.sw.dualscreen.sdk.SensorScaleUtils
import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.utils.SpTool
import com.sw.plate.utils.ToastUtils
import org.greenrobot.eventbus.EventBus
import kotlin.math.roundToInt
@@ -19,19 +21,21 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
}
override fun initialize() {
binding.rgSettlement.let {
//val settlementMode = if (binding.rgSettlement.checkedRadioButtonId == R.id.rbJointPayment) 0 else 1
//val mode = SPUtil.getInstance().get(GlobalKey.KEY_SETTLEMENT_MODE, 1)
it.check(if (SpTool.settlementMode == 0) R.id.rbJointPayment else R.id.rbIndependentPayment)
binding.rbJointPayment.isEnabled = false
binding.rbIndependentPayment.isEnabled = false
}
binding.rgCharge.let {
// it.setOnCheckedChangeListener { group, checkedId ->
// val mode = if (checkedId == R.id.rbChargeYes) 0 else 1
// SPUtil.getInstance().put(GlobalKey.KEY_CHARGE_MODE, mode)
// }
//0-计费,1-不计费
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
it.check(if (mode == 0) R.id.rbChargeYes else R.id.rbChargeNot)
}
binding.rgTakeFood.let {
// it.setOnCheckedChangeListener { group, checkedId ->
// val mode = if (checkedId == R.id.rbPickAndPlace) 0 else 1
// SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode)
// }
//0-即放即取,1-余量计量
val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
it.check(if (mode == 0) R.id.rbPickAndPlace else R.id.rbSurplusCalculate)
}
@@ -39,6 +43,10 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
SensorScaleUtils.tare()
}
binding.btnConfirm.setOnClickListener { v ->
// if (binding.rgSettlement.checkedRadioButtonId == -1) {
// ToastUtils.showToast("请设置结算模式")
// return@setOnClickListener
// }
if (binding.rgCharge.checkedRadioButtonId == -1) {
ToastUtils.showToast("请设置计费模式")
return@setOnClickListener
@@ -50,6 +58,7 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
val chargeMode = if (binding.rgCharge.checkedRadioButtonId == R.id.rbChargeYes) 0 else 1
//原来的支付模式
//0-计费,1-不计费
val oldChargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
if (chargeMode != oldChargeMode) {
//支付模式有变化通知首页页面刷新
@@ -59,7 +68,7 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
val pickupMode = if (binding.rgTakeFood.checkedRadioButtonId == R.id.rbPickAndPlace) 0 else 1
SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, pickupMode)
EventBus.getDefault().post(ResetRecognizeEvent())
//ToastUtils.showToast("设置已保存")
(activity as SettingActivity).let {
it.showWaitingDialog("设置保存中")
@@ -70,8 +79,8 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
}
}
SensorScaleUtils.addWeightListener { value ->
val realWeight = (value * 1000).roundToInt()
binding.tvFoodWeight.text = "$realWeight"
//val realWeight = (value * 1000).roundToInt()
binding.tvFoodWeight.text = "$value"
}
}
}
@@ -118,11 +118,11 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
collectionAdapter.notifyItemChanged(index)
}
// settingActivity?.hideWaitingDialog()
Thread {
activity?.lifecycleScope?.launch {
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
getImageVector(index, bitmap)
}
}.start()
}
} catch (e: Exception) {
e.printStackTrace()
settingActivity?.hideWaitingDialog()
@@ -8,7 +8,7 @@ import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentCashPayBinding
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.CashPayPresentation
import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.plate.utils.ToastUtils
class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
@@ -17,8 +17,6 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
private var foodName: String? = null
private var payAmount: Double? = null
var presentation: CashPayPresentation? = null
override fun inflateViewBinding(): FragmentCashPayBinding {
return FragmentCashPayBinding.inflate(layoutInflater)
}
@@ -27,12 +25,13 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
payActivity = requireActivity() as PayActivity
payActivity.foodInfo?.let {
foodName = it.foodName
payAmount = (it.specPrice?:0.0) * payActivity.eatNum
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
}
binding.tvRealAmount.text = getAmountText(payAmount.format2String(2))
binding.btnConfirmPayFinish.setOnClickListener {
payActivity.cashPay(hashMapOf(
payActivity.cashPay(
hashMapOf(
//标价金额(单位为元)
"totalFee" to payAmount.format2String(2),
//订单类型(0付款1会员充值)
@@ -41,49 +40,58 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
"orderNo" to payActivity.foodOrderId,
//支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动
"paySource" to "0"
)) { paySuccess ->
)
) { paySuccess ->
if (paySuccess.not()) {
ToastUtils.showToast("接口调用失败")
return@cashPay
}
payActivity.showPaySuccess( false)
payActivity.showPaySuccess(false)
}
}
showSubScreen()
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (hidden) {
dismissSubScreen()
return
}
//showSubScreen()
}
override fun onDestroy() {
dismissSubScreen()
super.onDestroy()
}
private fun dismissSubScreen() {
presentation?.let {
if (it.isShowing) {
it.dismiss()
}
}
}
// override fun onHiddenChanged(hidden: Boolean) {
// super.onHiddenChanged(hidden)
// if (hidden) {
// dismissSubScreen()
// return
// }
// //showSubScreen()
// }
//
// override fun onDestroy() {
// dismissSubScreen()
// super.onDestroy()
// }
//
// private fun dismissSubScreen() {
// }
private fun showSubScreen() {
// 查找副屏(通常索引为1
if (displays.size > 1) {
dismissSubScreen()
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
presentation?.dismiss()
}.also {
// // 查找副屏(通常索引为1)
// if (displays.size > 1) {
// dismissSubScreen()
// presentation = PayPresentation(
// activity = payActivity,
// pageType = PayPresentation.CASH_PAY,
// userViewModel = payActivity.userViewModel,
// recognizeViewModel = payActivity.recognizeViewModel,
// display = displays[1]
// ) {
// presentation?.dismiss()
// }.also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.CASH_PAY
it.foodName = foodName
it.payAmount = payAmount.format2String(2)
}
presentation?.show()
it.initView()
}
}
@@ -5,14 +5,13 @@ import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentFacePayBinding
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.FacePayPresentation
import com.sw.dualscreen.presentation.pay.PayPresentation
class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
private lateinit var payActivity: PayActivity
private var foodName: String? = null
private var payAmount: Double? = null
var presentation: FacePayPresentation? = null
override fun inflateViewBinding(): FragmentFacePayBinding {
return FragmentFacePayBinding.inflate(layoutInflater)
}
@@ -24,32 +23,36 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
// }
payActivity.foodInfo?.let {
foodName = it.foodName
payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
// payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
val price = it.specPrice ?: 0.0
payAmount = price * payActivity.memberDiscount * payActivity.eatNum
}
showSubScreen()
}
//
// override fun onHiddenChanged(hidden: Boolean) {
// super.onHiddenChanged(hidden)
// if (hidden) {
//// dismissSubScreen()
// payActivity.presentation.pauseCamera()
// return
// }
// payActivity.presentation.resumeCamera()
// //showSubScreen()
// }
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (hidden) {
dismissSubScreen()
return
}
//showSubScreen()
}
// override fun onDestroy() {
// dismissSubScreen()
// super.onDestroy()
// }
override fun onDestroy() {
dismissSubScreen()
super.onDestroy()
}
private fun dismissSubScreen() {
presentation?.let {
if (it.isShowing) {
it.dismiss()
}
}
}
// private fun dismissSubScreen() {
// presentation?.let {
// if (it.isShowing) {
// it.dismiss()
// }
// }
// }
fun loadBitmap(frame: Bitmap) {
binding.ivFaceImage.setImageBitmap(frame)
@@ -57,21 +60,44 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
private fun showSubScreen() {
// 查找副屏(通常索引为1
if (displays.size > 1) {
dismissSubScreen()
presentation = FacePayPresentation(
activity = payActivity,
display = displays[1],
userViewModel = payActivity.userViewModel,
recognizeViewModel = payActivity.recognizeViewModel,
// parentTextureView = binding.parentTextureView,
) {
presentation?.dismiss()
}.also {
// if (displays.size > 1) {
// dismissSubScreen()
// presentation = PayPresentation(
// activity = payActivity,
// pageType = PayPresentation.FACE_PAY,
// userViewModel = payActivity.userViewModel,
// recognizeViewModel = payActivity.recognizeViewModel,
// display = displays[1]
// ) {
// presentation?.dismiss()
// }.also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.FACE_PAY
it.foodName = foodName
it.payAmount = payAmount.format2String(2)
isResumeCamera = true
it.initView()
}
presentation?.show()
}
override fun onPause() {
super.onPause()
payActivity.presentation.pauseCamera()
}
private var isResumeCamera = false
override fun onResume() {
super.onResume()
if (isResumeCamera) {
payActivity.presentation.resumeCamera()
}
}
}
@@ -1,11 +1,13 @@
package com.sw.dualscreen.activity.fragment.pay
import android.text.InputType
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.presentation.pay.CashPayPresentation
import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.plate.utils.ToastUtils
class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
@@ -13,7 +15,8 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
private lateinit var payActivity: PayActivity
private var foodName: String? = null
private var payAmount: Double? = null
var presentation: CashPayPresentation? = null
// 密码是否显示的标志
private var isPasswordVisible = false
override fun inflateViewBinding(): FragmentNumberPayBinding {
return FragmentNumberPayBinding.inflate(layoutInflater)
}
@@ -28,6 +31,12 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
foodName = it.foodName
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
}
// 密码显示/隐藏切换
binding.ivTogglePassword.setOnClickListener {
togglePasswordVisibility()
}
binding.btnConfirm.setOnClickListener { v ->
val phone = binding.etInputPhone.text.toString().trim()
val key = binding.etInputNumber.text.toString().trim()
@@ -42,7 +51,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
// ToastUtils.showToast("会员验证成功")
// }
// }
payActivity.isMember = false
payActivity.getMemberInfoByPhone(phone, key) { memberInfo ->
if (memberInfo == null) {
binding.root.postDelayed({
@@ -51,6 +60,9 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
}, 1000)
return@getMemberInfoByPhone
}
//肯定是会员
memberInfo.member = true
payActivity.isMember = true
//绑定订单使用faceUserId
payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
if (bindResult.not()) {
@@ -60,7 +72,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
}
binding.root.postDelayed({
payActivity.hideWaitingDialog()
payActivity.showPayInfo(type = 1, isVip = true, memberInfo = memberInfo)
payActivity.showPayInfo(type = 1, isVip = memberInfo.member, memberInfo = memberInfo)
payActivity.hidePayTab()
}, 1000)
}
@@ -79,40 +91,74 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
showSubScreen()
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (hidden) {
dismissSubScreen()
return
}
//showSubScreen()
}
// override fun onHiddenChanged(hidden: Boolean) {
// super.onHiddenChanged(hidden)
// if (hidden) {
// dismissSubScreen()
// return
// }
// //showSubScreen()
// }
//
// override fun onDestroy() {
// dismissSubScreen()
// super.onDestroy()
// }
override fun onDestroy() {
dismissSubScreen()
super.onDestroy()
}
private fun dismissSubScreen() {
presentation?.let {
if (it.isShowing) {
it.dismiss()
}
}
}
// private fun dismissSubScreen() {
// presentation?.let {
// if (it.isShowing) {
// it.dismiss()
// }
// }
// }
private fun showSubScreen() {
// 查找副屏(通常索引为1
if (displays.size > 1) {
dismissSubScreen()
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
presentation?.dismiss()
}.also {
// if (displays.size > 1) {
// dismissSubScreen()
// presentation = PayPresentation(
// activity = payActivity,
// pageType = PayPresentation.CASH_PAY,
// userViewModel = payActivity.userViewModel,
// recognizeViewModel = payActivity.recognizeViewModel,
// display = displays[1]
// ).also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.CASH_PAY
it.foodName = foodName
it.payAmount = payAmount.format2String(2)
it.initView()
}
presentation?.show()
}
// 切换密码显示/隐藏
private fun togglePasswordVisibility() {
val editText = binding.etInputNumber
val imageView = binding.ivTogglePassword
// 保存当前光标位置
val cursorPosition = editText.selectionStart
if (isPasswordVisible) {
// 当前显示,改为隐藏
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
imageView.setImageResource(R.drawable.ic_eye_close)
isPasswordVisible = false
} else {
// 当前隐藏,改为显示
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
imageView.setImageResource(R.drawable.ic_eye_open)
isPasswordVisible = true
}
// 恢复光标位置
editText.setSelection(cursorPosition)
}
// private fun bindOrder(userId: String, block: () -> Unit) {
@@ -74,8 +74,10 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
memberInfo = it.getParcelable(MEMBER_INFO)
}
if (memberInfo != null) {
totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
if (memberInfo != null && memberInfo!!.member) {
// totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
val price = payActivity.foodInfo?.specPrice ?: 0.0
totalPrice = price * payActivity.memberDiscount * payActivity.eatNum
binding.layoutVip.visible()
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
@@ -112,9 +114,15 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
)
)
if (memberInfo != null && memberInfo!!.member) {
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
getAmountList()
)
} else {
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
getAmountListNotMember()
)
}
if (balance >= totalPrice) {
binding.ivPayQrCode.invisible()
binding.btnConfirmPay.run {
@@ -201,6 +209,21 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
//扣除余额
private var expensesBalance = 0.0
private fun getAmountListNotMember(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf()
list.add(TextBean(text = "应付金额 ", textSize = 30, textColor = "#FF889AC2"))
list.add(
TextBean(
text = "-${totalPrice.format2String(2)}",
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
list.add(TextBean(text = "", textSize = 30, textColor = "#FF889AC2"))
return list
}
private fun getAmountList(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf()
@@ -268,7 +291,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
//订单号
"orderNo" to payActivity.foodOrderId,
//用户id
"memberId" to memberInfo!!.id!!
"memberId" to memberInfo?.id
),
block = callback
)
@@ -8,11 +8,11 @@ import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
import com.sw.dualscreen.utils.countDownByFlow
import kotlinx.coroutines.Job
import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.dualscreen.utils.IntervalExecutor
class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
@@ -25,7 +25,6 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
private var foodName: String? = null
private var payAmount: Double? = null
private var payQrCodePic: String? = null
var presentation: ScanQrCodePayPresentation? = null
private var countDownJob: Job? = null
override fun inflateViewBinding(): FragmentScanQrcodePayBinding {
@@ -37,7 +36,7 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
payActivity.foodInfo?.let {
foodName = it.foodName
payAmount = (it.specPrice?:0.0)*payActivity.eatNum
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
}
binding.tvRealAmount.text = getAmountText(payAmount.format2String(2))
//不需要绑定会员id
@@ -60,41 +59,48 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (hidden) {
dismissSubScreen()
return
}
// if (hidden) {
//// dismissSubScreen()
// return
// }
//showSubScreen()
}
override fun onDestroy() {
dismissSubScreen()
// dismissSubScreen()
countDownJob?.cancel() // 自动取消订阅,防止内存泄漏
payTaskJob?.cancel()
super.onDestroy()
}
private fun dismissSubScreen() {
presentation?.let {
if (it.isShowing) {
it.dismiss()
}
}
// presentation?.let {
// if (it.isShowing) {
// it.dismiss()
// }
// }
}
private fun showSubScreen() {
// 查找副屏(通常索引为1
if (displays.size > 1) {
dismissSubScreen()
presentation =
ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
presentation?.dismiss()
}.also {
// // 查找副屏(通常索引为1)
// if (displays.size > 1) {
// dismissSubScreen()
// presentation =
// ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
// presentation?.dismiss()
// }.also {
// it.foodName = foodName
// it.totalPrice = payAmount ?: 0.0
// it.payQrCodePic = payQrCodePic
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.QR_CODE_PAY
it.foodName = foodName
it.totalPrice = payAmount ?: 0.0
it.payQrCodePic = payQrCodePic
}
presentation?.show()
it.initView()
}
}
@@ -7,17 +7,17 @@ import androidx.core.graphics.toColorInt
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.sw.dualscreen.R
import com.sw.dualscreen.databinding.ListItemSearchFoodBinding
import com.sw.dualscreen.databinding.ListItemSearchFood3Binding
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.model.response.FoodInfo
class CollectFoodListAdapter(var list: MutableList<FoodInfo>) :
BaseQuickAdapter<FoodInfo, CollectFoodListAdapter.VH>(list) {
inner class VH(var binding: ListItemSearchFoodBinding) : QuickViewHolder(binding.root)
inner class VH(var binding: ListItemSearchFood3Binding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemSearchFoodBinding.inflate(inflater, parent, false)
val binding = ListItemSearchFood3Binding.inflate(inflater, parent, false)
return VH(binding)
}
@@ -0,0 +1,66 @@
package com.sw.dualscreen.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.graphics.toColorInt
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.sw.dualscreen.databinding.ListItemFoodOrderBinding
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.FoodItem
class FoodOrderAdapter(list: MutableList<FoodItem>) :
BaseQuickAdapter<FoodItem, FoodOrderAdapter.VH>(list) {
inner class VH(var binding: ListItemFoodOrderBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemFoodOrderBinding.inflate(inflater, parent, false)
return VH(binding)
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: VH, position: Int, item: FoodItem?) {
val binding = holder.binding
setTextColor(binding.tvFoodName, binding.tvPriceNorm, binding.tvWeight, binding.tvAmount)
item?.let {
binding.tvFoodName.text = it.foodName
binding.tvFoodName.setTextColor(if (it.isLocalData) Color.GREEN else "#FF33446A".toColorInt())
if (it.orderFrom == 2) {
binding.tvPriceNorm.text = if (it.specName.isNullOrBlank()) "${it.specWeight}" else "${it.specName}/${it.specWeight}"
} else {
val foodPrice = it.foodPrice.format2String(2)
binding.tvPriceNorm.text = "${foodPrice}/100克"
}
//if (it.specName.isNullOrBlank().not()) {
// binding.tvPriceFlag.text = "[${it.specName}]"
//}
binding.tvWeight.text = "${it.num}"
binding.tvAmount.text = it.price.format2String(2)
binding.tvLabel.run {
if (it.orderFrom == 2) visible() else gone()
}
}
binding.root.updateLayoutParams<RecyclerView.LayoutParams> {
topMargin = 2.dp
bottomMargin = 2.dp
}
}
fun setTextColor(vararg tvList: TextView) {
tvList.forEach {
it.setTextColor("#FF33446A".toColorInt())
}
}
}
@@ -0,0 +1,69 @@
package com.sw.dualscreen.dialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.databinding.DialogEnvSwitchBinding
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.utils.SpTool
import com.sw.plate.utils.ToastUtils
/**
* 环境切换弹窗
*
* 提供 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()
}
}
}
@@ -7,8 +7,11 @@ import android.graphics.BitmapFactory
import android.graphics.ImageFormat
import android.graphics.Rect
import android.graphics.YuvImage
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.TouchDelegate
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
@@ -159,3 +162,35 @@ fun View.invisible() {
fun View.gone() {
visibility = View.GONE
}
val Context.screenSize: IntArray
get() {
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
return intArrayOf(
displayMetrics.widthPixels,
displayMetrics.heightPixels
)
}
/**
* 扩大 View 的点击区域
* @param horizontal 水平方向扩大距离(dp)→ 左右各扩大一半
* @param vertical 垂直方向扩大距离(dp)→ 上下各扩大一半
*/
fun View.expandClickArea(horizontal: Int, vertical: Int) {
val density = resources.displayMetrics.density
val hPx = (horizontal * density).toInt()
val vPx = (vertical * density).toInt()
post {
val rect = android.graphics.Rect()
getHitRect(rect)
// 扩大区域:负值 = 向外扩大
rect.inset(-hPx, -vPx)
//rect.bottom += downPx // 只往下扩大
(parent as View).touchDelegate = TouchDelegate(rect, this)
}
}
@@ -34,7 +34,11 @@ data class FoodOrder(
var remark: String? = "",
//即放即取-1,称重-2
var mode: Int
var mode: Int,
//设备模式,1 结算终端,2档口机
var paymentFrom: Int,
//是否会员
var member: Boolean
)
@Parcelize
@@ -54,7 +58,8 @@ data class MemberInfo(
//赠送余额
val rewardBalance: Double?,
//积分余额
val integralBalance: Int?
val integralBalance: Int?,
var member: Boolean = false
) : Parcelable
data class TextBean(
@@ -95,12 +100,14 @@ data class UserNutrition(
val fruitsVegetablesNearExcess: String? = null,
//果蔬超量
val fruitsVegetablesExcess: String? = null,
//肉蛋推荐值
val meatEggsRecommend: String? = null,
//肉蛋即将超量
val meatEggsNearExcess: String? = null,
//肉蛋超量
val meatEggsExcess: String? = null,
//肉蛋推荐值
val meatEggsBeansRecommend: String? = null,
//肉蛋即将超量
val meatEggsBeansNearExcess: String? = null,
//肉蛋超量
val meatEggsBeansExcess: String? = null,
//总重量
val eatWeightSum:Double? = null,
)
//{"code":"00000","data":"{\"orderType\":0,\"orderPayFrom\":2,\"payType\":\"1\",\"paySuc\":0,\"totalFee\":0.01,\"orderCode\":\"1998193614899372032\",\"message\":\"支付成功\",\"payCode\":\"1998193614899372032508227\"}","msg":"成功","total":0}
@@ -122,7 +129,7 @@ data class RespCodeMsg(
data class PostEvent(var name: String = "")
data class UpdateRefreshEvent(var name:String = "")
data class UpdateRefreshEvent(var name: String = "")
data class PaySuccessEvent(
var name: String = ""
)
@@ -130,6 +137,9 @@ data class PaySuccessEvent(
data class ChargeModeEvent(
var chargeMode: Int = 0
)
data class ResetRecognizeEvent(
var state: Int = 0
)
data class ClickBackEvent(
var name: String = ""
@@ -138,7 +148,9 @@ data class ClickBackEvent(
data class DeviceConfig(
var arcsoftAppId: String? = null,
var arcsoftSdkKey: String? = null,
var arcsoftActiveKey: String? = null
var arcsoftActiveKey: String? = null,
//结算模式:1-独立支付模式,2-联合结算模式
var payType: Int = 1
)
data class FoodVector(
@@ -158,3 +170,32 @@ data class WeightRecord(
var lastWeight: Int = 0,
var currentWeight: Int = 0
)
data class FoodItem(
var id: String? = null,
var foodId: String? = null,
var foodName: String? = null,
var foodMaterialId: String? = null,
var specId: String? = null,
var num: Int = 0,
var eatNum: Int = 0,
var price: Double = 0.0,
var foodPrice: Double = 0.0,
var discount: Double = 0.0,
var income: Double = 0.0,
var foodDifferenceId: Long = 0,
var createTime: String? = null,
var specName: String? = null,
var specWeight: Int = 0,
var orderFrom: Int = 0,
var isLocalData: Boolean = false
)
data class FoodOrderModel(
val calorie: Double = 0.0,
val incomeSum: Double = 0.0,
val discountSum: Double = 0.0,
val eatWeightSum: Int = 0,
val orderNo: String? = null,
val list: List<FoodItem>? = null
)
@@ -32,7 +32,7 @@ data class UserFaceModel(
/**
* 是否会员
*/
val isMember: Boolean
val member: Boolean
) : Parcelable
@Parcelize
@@ -1,8 +1,11 @@
package com.sw.dualscreen.network
import com.sw.dualscreen.BuildConfig
import com.sw.dualscreen.MyApp
import com.sw.dualscreen.network.api.ApiService
import com.sw.dualscreen.network.interceptor.RequestInterceptor
import com.sw.dualscreen.utils.FileUtil
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
@@ -19,14 +22,23 @@ object ApiClient {
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Timber.d("okhttp logger ==>${it}")
.addNetworkInterceptor(Interceptor {chain ->
val request = chain.request()
val url = request.url.toString()
val tag = if (url.contains("terminal/neglect/common/app/faceFeature/increment/list")) "faceIncrement"
else if (url.contains("terminal/neglect/pay/app/turnOrderInfo")) "turnOrderInfo"
else "ApiClient"
val loggingInterceptor = HttpLoggingInterceptor(logger = {
Timber.tag(tag).d("okhttp logger ==>${it}")
FileUtil.saveLog("okhttp logger ==>${it}")
}).apply {
level = if (MyApp.DEBUG) {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}
loggingInterceptor.intercept(chain)
})
.addInterceptor(RequestInterceptor())
.build()
@@ -8,6 +8,7 @@ import com.sw.dualscreen.model.response.DinnerType
import com.sw.dualscreen.model.response.FaceData
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.FoodOrderModel
import com.sw.dualscreen.model.response.FoodSearchReq
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.model.response.MemberInfo
@@ -202,7 +203,7 @@ interface ApiService {
@POST
suspend fun memberPay(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/memberPay",
@Body param: HashMap<String, String>
@Body param: HashMap<String, String?>
): ApiResponse<Any?>
/**
@@ -216,6 +217,16 @@ interface ApiService {
@Query("totalFee") totalFee: String? = null
): ApiResponse<String?>
/**
* 二维码支付
* @param orderType 0付款 1会员充值
*/
@POST
suspend fun qrCodePay(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay",
@Body param: HashMap<String, String?>
): ApiResponse<String?>
/**
* 开餐-生成订单
*/
@@ -301,4 +312,22 @@ interface ApiService {
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/deleteBoothMachineCollection",
@QueryMap param: MutableMap<String, String?>
): ApiResponse<Any?>
/**
* 查询就餐数据
*/
@GET
suspend fun getFoodOrderList(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/settlement/terminal/app/getFoodInfoBySettlement",
@Query("userId") userId: String
): ApiResponse<FoodOrderModel?>
/**
* 查询会员折扣
*/
@GET
suspend fun getMemberDiscount(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getMemberDiscount",
@Query("userId") userId: String
): ApiResponse<Double?>
}
@@ -124,7 +124,7 @@ object FoodModule {
suspend fun queryFoodNameScore(
floatArray: FloatArray?,
queryCount: Int = 15
queryCount: Int = 50
): List<IdNameScore> {
if (floatArray == null) return emptyList()
val startTime = System.currentTimeMillis()
@@ -151,7 +151,7 @@ object FoodModule {
return nameScoreList
}
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 50): List<IdNameScore> {
val startTime = System.currentTimeMillis()
val floatArray = bitmap2FloatArray(bitmap, false)
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
@@ -161,28 +161,26 @@ object FoodModule {
return emptyList()
}
val maxScoreList = nameScoreList
.filter { it.score < 1 - THRESHOLD }
// .filter { it.score < 1 - THRESHOLD }
.groupBy { it.name }
.map { (_, value) -> value.minByOrNull { it.score }!! }
.toMutableList()
val map = mutableMapOf<String, Int>()
nameScoreList.forEach {
val key = it.name
val count = map[key] ?: 0
map[key] = count + 1
}
val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
val firstFood = nameScoreList[0].name
orderList.remove(firstFood)
orderList.add(0, firstFood)
val sortedScoreList = maxScoreList.sortedWith(compareBy {
orderList.indexOf(it.name)
})
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
//Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
// val map = mutableMapOf<String, Int>()
// nameScoreList.forEach {
// val key = it.name
// val count = map[key] ?: 0
// map[key] = count + 1
// }
// val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
// val firstFood = nameScoreList[0].name
// orderList.remove(firstFood)
// orderList.add(0, firstFood)
//
// val sortedScoreList = maxScoreList.sortedWith(compareBy {
// orderList.indexOf(it.name)
// })
val sortedScoreList = maxScoreList.sortedBy { it.score }
Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toString()}")
return sortedScoreList
}
@@ -19,6 +19,7 @@ package com.sw.dualscreen.objbox
import android.content.Context
import android.os.Environment
import android.util.Log
import com.sw.dualscreen.BuildConfig
import com.sw.plate.App
import io.objectbox.Box
import io.objectbox.BoxStore
@@ -91,14 +92,14 @@ object ObjectBox {
}
}
// if (BuildConfig.DEBUG) {
if (BuildConfig.DEBUG) {
val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
Timber.tag(TAG)
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
// Enable ObjectBox Admin on debug builds.
// https://docs.objectbox.io/data-browser
Admin(boxStore).start(context.applicationContext)
// }
}
}
@@ -143,6 +144,9 @@ object ObjectBox {
} catch (e: Exception) {
e.printStackTrace()
null
} finally {
// 线程资源清理
boxStore?.closeThreadResources()
}
}
}
@@ -177,16 +181,13 @@ object ObjectBox {
getBox<Food>()?.all
} ?: emptyList()
suspend fun filter(name: String?, isDistinct: Boolean = false) = safeDbOp {
val list = if (isDistinct) {
getBox<Food>()?.all?.distinctBy { it.foodName }
suspend fun filter(name: String?) = safeDbOp {
if (!name.isNullOrBlank()) {
getBox<Food>()?.query(Food_.foodName.contains(name))?.build()?.find()
?.distinctBy { it.foodName }
} else {
getBox<Food>()?.all
}
if (name.isNullOrBlank().not()) {
list?.filter { it.foodName?.contains(name) == true }
}
list
getBox<Food>()?.all?.distinctBy { it.foodName }
} ?: emptyList()
} ?: emptyList()
suspend fun removeAll() = safeDbOp {
@@ -194,10 +195,7 @@ object ObjectBox {
}
suspend fun remove(name: String) = safeDbOp {
getBox<Food>()?.run {
val filterIdList = all.filter { it.foodName == name }.map { it.id }
removeByIds(filterIdList)
}
getBox<Food>()?.query(Food_.foodName.equal(name))?.build()?.remove()
}
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
@@ -207,9 +205,7 @@ object ObjectBox {
// 检查存储是否可读写(操作数据库前调用)
fun isStorageAvailable(context: Context): Boolean {
return try {
val state = Environment.getExternalStorageState()
val innerDir = context.filesDir
Environment.MEDIA_MOUNTED == state && innerDir.canRead() && innerDir.canWrite()
context.filesDir.canRead() && context.filesDir.canWrite()
} catch (e: Exception) {
false
}
@@ -1,48 +1,50 @@
package com.sw.dualscreen.presentation
import android.Manifest
import android.annotation.SuppressLint
import android.app.Presentation
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Outline
import android.graphics.Point
import android.hardware.Camera
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.DisplayMetrics
import android.util.Log
import android.view.Display
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.arcsoft.face.ErrorInfo
import com.sw.dualscreen.BuildConfig
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.MyApp
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.MainActivity
import com.sw.dualscreen.adapter.FoodOrderAdapter
import com.sw.dualscreen.databinding.PresentationMainScreenBinding
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.invisible
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.ext.maskName
import com.sw.dualscreen.ext.roundedDecimalPlace
import com.sw.dualscreen.ext.screenSize
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.DinnerType
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodItem
import com.sw.dualscreen.model.response.UserEnergy
import com.sw.dualscreen.model.response.UserNutrition
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.utils.countDownByFlow
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.LightManager
import com.sw.plate.utils.ToastUtils
@@ -59,14 +61,15 @@ import com.sw.plate.utils.arcface.face.model.CompareResult
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
import timber.log.Timber
import java.text.DecimalFormat
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
class MainScreenPresentation(
val activity: MainActivity,
display: Display,
@@ -76,7 +79,7 @@ class MainScreenPresentation(
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
companion object {
const val TAG = "SubScreenPresentation"
const val TAG = "MainScreenPresentation"
}
private lateinit var binding: PresentationMainScreenBinding
@@ -98,14 +101,14 @@ class MainScreenPresentation(
private var irFaceRectTransformer: FaceRectTransformer? = null
private var detectWeight = 0.0 //识别菜品时的重量
var lastWeight = 0.0 // 上一次的计算热量结果
var lastWeight = 0 // 上一次的计算热量结果
// private var userNutritionData: UserNutritionData? = null
private var userNutrition: UserNutrition? = null
private var dinnerTypeInfo: DinnerType? = null
private val debouncer = Debouncer(500)
private val debouncer = Debouncer(1500)
private var recognitionTime: Long = 0L // 人脸识别时的时间
var recognitionWeight: Double = 0.0 // 人脸识别时的重量
var recognitionWeight: Int = 0 // 人脸识别时的重量
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
var mealPickupMode: Int = 0 // 取餐模式 0 即放即取 1 余量取餐
@@ -114,6 +117,7 @@ class MainScreenPresentation(
var isKeepFaceState = false
var currentUserId: String? = null
var isMember: Boolean = false
var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
@@ -121,11 +125,13 @@ class MainScreenPresentation(
super.onCreate(savedInstanceState)
binding = PresentationMainScreenBinding.inflate(layoutInflater)
setContentView(binding.root)
//0-即放即取,1-余量计量
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
window?.setBackgroundDrawableResource(android.R.color.white)
initView()
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
if (chargeMode == 1) {
if (activity.settlementMode == 0 || chargeMode == 1) {
setupArcCamera()
}
registerDataChange()
@@ -134,71 +140,83 @@ class MainScreenPresentation(
private var isFirstGetWeight = true
fun updateWeight(weight: Double) {
lastWeight = weight * 1000 // 将千克转成克
fun updateWeight(weight: Int) {
Timber.tag(TAG)
.d("updateWeight lastWeight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1")
.d("updateWeight weight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1")
//0-即放即取,1-余量计量
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
// if (mealPickupMode == 1 && chargeMode == 0) {
// //余量计量+计费模式
// if (isFirstGetWeight && lastWeight > 5) {
// recognitionWeight = lastWeight
// isFirstGetWeight = false
// }
// if (lastWeight > 5) {
// //余量计量,有重量的情况下不回到重新识别状态
// return
// }
// isFirstGetWeight = true
// }
if (currentStep == 2 && lastWeight <= 5 && isGoStep1) {
if (currentStep == 2 && weight <= MainActivity.WEIGHT_RESET_RECOGNIZE && isGoStep1) {
//人脸识别过程中,秤上物品拿走
isGoStep1 = false
activity.runOnUiThread {
Timber.tag(TAG).e("切回step1")
Timber.tag(TAG).e("切回step1,------2000000000000")
step1FoodRecognizing()
}
} else if (lastWeight <= 5) {
} else if (weight <= MainActivity.WEIGHT_RESET_RECOGNIZE) {
isGoStep1 = false
activity.runOnUiThread {
Timber.tag(TAG).e("切回step1")
Timber.tag(TAG).e("切回step1,------2000000000001")
if (mealPickupMode == 0) {
//即放即取
step1FoodRecognizing()
} else {
//秤上菜品不足,请补充
if (currentStep == 1 && currentFood == null) {
return@runOnUiThread
}
//余量计量
initCountDownResetJob(weight)
}
}
}
// if (currentStep != 3) {
// return
// }
if (currentFood == null) {
Timber.tag(TAG).e("updateWeight currentFood is null")
return
}
//if (userNutritionData == null) {
if (userNutrition == null) {
Timber.tag(TAG).e("updateWeight userNutritionData is null")
return
}
// if (dinnerTypeInfo == null) {
// Timber.tag(TAG).e("updateWeight dinnerType is null")
// return
// }
if (chargeMode == 1) {
//不计费
if (chargeMode == 1 || activity.settlementMode == 0) {
//不计费或者联合支付模式更新营养热量数据
if (mealPickupMode == 0) {
//即放即取
calculateNutrition(lastWeight)
calculateNutrition(weight)
} else {
//余量计量
calculateNutrition(recognitionWeight - lastWeight)
val value = recognitionWeight - weight
Timber.tag(TAG).e("updateWeight 余量计量:${recognitionWeight} - ${weight} = ${value}")
calculateNutrition(value)
}
}
lastWeight = weight
}
private var countDownResetJob: Job? = null
private fun initCountDownResetJob(weight: Int) {
if (countDownResetJob == null || countDownResetJob?.isActive == false) {
countDownResetJob = countDownByFlow(
total = 30,
scope = activity.lifecycleScope,
onStart = {},
onTick = { seconds ->
Timber.tag(TAG).e("余量计量模型秤重量已清空,等待${seconds}秒后继续重置识别状态")
if (weight > MainActivity.WEIGHT_RESET_RECOGNIZE) {
countDownResetJob?.cancel()
countDownResetJob = null
}
},
onFinish = {
step1FoodRecognizing()
countDownResetJob?.cancel()
countDownResetJob = null
}
)
}
}
fun setStepChangeCallback(callback: (Int) -> Unit) {
@@ -275,16 +293,58 @@ class MainScreenPresentation(
}
}
private fun setTopInfoVisible() {
binding.llTopInfo.visible()
binding.flFoodDetail.gone()
if (activity.settlementMode == 0) {
try {
foodOrderList.clear()
foodOrderAdapter.submitList(foodOrderList)
} catch (e: Exception) {
e.printStackTrace()
}
}
binding.llTopInfo.updateLayoutParams {
height = activity.screenSize[1] / 2 + 120.dp
}
}
private var isGoRecognize = true
/**
* 显示识别出的菜品信息
*/
fun step3ShowRecognizeResult(userId: String) {
step3Debouncer.debounce {
Timber.tag(TAG).d("main,step3,耗时:${System.currentTimeMillis() - startTime}")
LightManager.closeRedLight()
LightManager.openGreenLight()
currentStep = 3
stepChangeCallback(currentStep)
//根据接口数据更新热量数据--------------------------
setTopInfoVisible()
if (activity.settlementMode == 0) {
//判断是联合支付调用接口查询就餐信息后,再查询热量营养数据
loadOrderList(userId) {
loadUserNutritionData(userId)
}
activity.layoutEatDetailBinding.root.visible()
} else {
//独立支付
loadUserNutritionData(userId)
}
step3Debouncer.reset()
activity.isSwitchFood = false
}
}
private val step3Debouncer = Debouncer(10*1000)
private var memberDiscount:Double = 1.0
/**
* 根据接口数据更新热量、营养数据
*/
private fun loadUserNutritionData(userId: String) {
userViewModel.getUserNutritionData(userId = userId) { nutrition ->
activity.runOnUiThread {
if (nutrition == null) {
@@ -401,7 +461,7 @@ class MainScreenPresentation(
}
private var isLoadUnbilledMode = false
private val df by lazy { DecimalFormat("#.##") }
// private val df by lazy { DecimalFormat("#.##") }
/**
* 餐品识别成功,不计费模式
@@ -410,19 +470,6 @@ class MainScreenPresentation(
if (currentUserId.isNullOrBlank()) {
return
}
//todo 清除人脸数据
// binding.dualCameraFaceRectView.clearFaceInfo()
// clearLeftFaceData()
// facePreviewInfoList?.clear()
// recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
// pauseCamera()
// resumeCamera()
// TODO: 如何清除人脸数据重新识别
// stopCamera()
// setupArcCamera()
isLoadUnbilledMode = true
Timber.tag(TAG).d("loadUnbilledMode")
@@ -440,15 +487,16 @@ class MainScreenPresentation(
//updateFoodInfo(foodInfo)
binding.nutritionInclude.tvUserName.text = userNutrition?.name.maskName()
val recommendCalorie = userNutrition?.recommendCalorie ?: 0.0
val recommendCalorie = (userNutrition?.recommendCalorie ?: 0.0).roundedDecimalPlace(1)
binding.nutritionInclude.tvRecommendHeat.text =
"推荐热量:${df.format(recommendCalorie)}kcal"
// binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${recommendCalorie.removeTrailingZeros()}kcal"
//updateWeight(lastWeight / 1000)
// -------------------------------------------------
// 删除userNutritionData,改为userNutrition
calculateNutrition(recognitionWeight - lastWeight)
binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${recommendCalorie}kcal"
//0-即放即取,1-余量计量
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
calculateNutrition(
if (mealPickupMode == 1) recognitionWeight - lastWeight
else recognitionWeight
)
binding.root.postDelayed({
isLoadUnbilledMode = false
}, 10000)
@@ -465,24 +513,24 @@ class MainScreenPresentation(
private var isFirstOpen = true
var startTime = 0L
var jointPaymentQueryFinish = false
/**
* 餐品识别中
*/
fun step1FoodRecognizing() {
if (currentStep == 1 && currentFood == null) {
activity.clearRecognizeFood()
return
}
Timber.tag(TAG).d("main,step1,耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
jointPaymentQueryFinish = false
hideStandbyScreen()
activity.resetTouchTime()
if (isFirstOpen.not()) {
activity.resumeAnalysis()
}
//if (abs(lastWeight.toInt()) <= 5) {
// activity.runOnUiThread {
// ToastUtils.showToast("秤上重量几乎为0,请确认")
// }
// return
//}
currentStep = 1
LightManager.closeGreenLight()
LightManager.closeRedLight()
@@ -490,9 +538,9 @@ class MainScreenPresentation(
currentFood = null
// userNutritionData = null
userNutrition = null
recognitionWeight = 0.0
recognitionWeight = 0
stepChangeCallback(currentStep)
setTopInfoVisible()
binding.calorieInclude.root.gone()
binding.nutritionInclude.root.gone()
@@ -511,31 +559,23 @@ class MainScreenPresentation(
binding.flRecognizeIr.gone()
pauseCamera()
activity.clearFoodList()
}
activity.clearRecognizeFood()
// fun foodRecSuccess(foodInfo: FoodInfo) {
// //识别逻辑完成根据是否计费显示不同页面
// val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
// if (mode == 0) {
// //计费
// loadBilledMode()
// } else {
// //不计费
// //userViewModel.getUserNutritionData(
// // userId = userId,
// // foodId = currentFood!!.foodId!!
// //)
// loadUnbilledMode()
// }
// }
currentStep = 1
stepChangeCallback(currentStep)
}
/**
* 用户人脸识别中
*/
fun step2FaceRecognizing(foodInfo: FoodInfo) {
fun step2FaceRecognizing(foodInfo: FoodInfo, hideFoodDetail: Boolean = false) {
isGoStep1 = true
Timber.tag(TAG).d("main,step2,耗时:${System.currentTimeMillis() - startTime}")
if (hideFoodDetail) {
setTopInfoVisible()
}
startTime = System.currentTimeMillis()
currentStep = 2
LightManager.closeRedLight()
@@ -559,7 +599,7 @@ class MainScreenPresentation(
dinnerTypeInfo = null
// userNutritionData = null
userNutrition = null
recognitionWeight = 0.0
recognitionWeight = 0
stepChangeCallback(currentStep)
currentFood = foodInfo
@@ -567,6 +607,10 @@ class MainScreenPresentation(
// userViewModel.getDinnerType()
resumeCamera()
if (activity.settlementMode == 0) {
activity.layoutEatDetailBinding.root.invisible()
}
}
fun hideRecImage() {
@@ -574,7 +618,7 @@ class MainScreenPresentation(
}
// 当前食物信息
private var currentFood: FoodInfo? = null
var currentFood: FoodInfo? = null
fun updateFood(foodInfo: FoodInfo?) {
//if (isLoadUnbilledMode) return
Timber.tag(TAG).d("main,updateFood,耗时:${System.currentTimeMillis() - startTime}")
@@ -586,6 +630,7 @@ class MainScreenPresentation(
binding.tvFoodName.text = foodInfo.foodName
binding.ivPreviewImage.gone()
binding.ivFrame.gone()
//0-计费,1-不计费
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
if (chargeMode == 0) {
binding.ivRecImage.let {
@@ -597,22 +642,25 @@ class MainScreenPresentation(
} else {
binding.ivRecImage.gone()
}
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
if (mode == 0) {
//独立支付+计费模式,使用人脸识别
if (activity.settlementMode == 1 && chargeMode == 0) {
//计费,查询营养信息、价格信息------------------------
loadBilledMode()
} else {
//不计费
//开启人脸识别
//联合支付,进行人脸识别
if (activity.settlementMode == 0) {
if (activity.isSwitchFood.not()) {
step2FaceRecognizing(foodInfo)
}
} else {
//不计费,开启人脸识别
if (currentStep == 1 || binding.tvFaceTip.isVisible.not()) {
if (activity.isSwitchFood.not()) {
step2FaceRecognizing(foodInfo)
}
}
//if (currentStep == 1) {
// step2FaceRecognizing(foodInfo)
//} else {
// updateFoodInfo(foodInfo)
//}
}
}
}
private fun updateFoodInfo(foodInfo: FoodInfo) {
@@ -636,12 +684,80 @@ class MainScreenPresentation(
// }
}
private fun calculateNutrition(weight1: Double) {
private var lastAddWeight = 0
private fun addFoodToOrder(weight: Int, foodInfo: FoodInfo) {
Timber.tag(TAG) .d("addFoodToOrder: ${weight == lastAddWeight}")
// if (weight == lastAddWeight) return
lastAddWeight = weight
if (weight <= 10) {
//0-即放即取,1-余量计量
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
if (foodOrderList.isNotEmpty() && mealPickupMode == 1) {
val last = foodOrderList.last()
if (last.isLocalData) {
foodOrderList.remove(last)
// foodOrderAdapter.submitList(foodOrderList)
foodOrderAdapter.notifyDataSetChanged()
}
}
return
}
//val foodPrice = if (isMember) foodInfo.vipPrice else foodInfo.specPrice
val foodPrice = (foodInfo.specPrice ?: 0.0) * memberDiscount
val eatNum = activity.getEatNum(weight.toDouble(), foodInfo.specWeight)
val price = eatNum * foodPrice
if (foodOrderList.isNotEmpty() && mealPickupMode == 1) {
val last = foodOrderList.last()
if (last.isLocalData) {
last.eatNum = eatNum
last.num = weight
last.price = price
// foodOrderAdapter.submitList(foodOrderList)
foodOrderAdapter.notifyItemChanged(foodOrderList.lastIndex)
return
}
}
foodOrderList.add(
FoodItem(
id = System.currentTimeMillis().toString(),
foodId = foodInfo.foodId,
foodName = foodInfo.foodName,
price = price,
specId = foodInfo.specId,
foodMaterialId = foodInfo.foodMaterialId,
specWeight = foodInfo.specWeight?.toInt() ?: 0,
orderFrom = 2,
num = weight,
eatNum = eatNum,
isLocalData = true
)
)
// foodOrderAdapter.submitList(foodOrderList)
foodOrderAdapter.notifyDataSetChanged()
Timber.tag(TAG) .d(
"addFoodToOrder: size=${foodOrderList.size},新增数据:${foodOrderList.last()}"
)
//foodOrderAdapter.notifyDataSetChanged()
var eatWeightSum = 0
foodOrderList.forEach {
eatWeightSum += it.num
}
binding.detailInclude.tvTotalWeight.text = "总重量:${eatWeightSum}"
Timber.tag(TAG) .d("addFoodToOrder: 更新总重量,新增数据------------------------------")
}
private fun calculateNutrition(value: Int) {
Timber.tag(TAG) .d("calculateNutrition:weight=$value,recognitionWeight=$recognitionWeight,foodInfo=$currentFood")
if (currentFood == null || userNutrition == null) {
return
}
var weight = weight1
if (weight < 0) weight = 0.0
var weight = value
if (weight < 0) weight = 0
if (activity.settlementMode == 0) {
activity.runOnUiThread {
addFoodToOrder(weight, currentFood!!)
}
}
debouncer.debounce {
Timber.tag(TAG)
.d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight")
@@ -652,7 +768,7 @@ class MainScreenPresentation(
currentFood!!,
// userNutritionData!!,
userNutrition!!,
weight,
weight.toDouble(),
dinnerType = dinnerType ?: ""
)
// Timber.tag(TAG).d("calculateNutrition calcResultInfo = $calcResultInfo")
@@ -666,6 +782,15 @@ class MainScreenPresentation(
val fruitsVegetables = energy.fruitsVegetables
val meatEggs = energy.meatEggs
val showTotalCalorie = "总热量:${totalKcal.roundedDecimalPlace(1)}千卡"
binding.detailInclude.tvTotalCalorie.text = showTotalCalorie
activity.layoutEatDetailBinding.tvTotalCalorie.text = showTotalCalorie
val totalWeight = (userNutrition?.eatWeightSum ?: 0.0) + weight
val showTotalWeight = "总重量:${totalWeight.roundedDecimalPlace(1)}"
binding.detailInclude.tvTotalWeight.text = showTotalWeight
activity.layoutEatDetailBinding.tvTotalWeight.text = showTotalWeight
val include = binding.nutritionInclude
// 设置热量---------------------------------------------------------------------
@@ -684,7 +809,7 @@ class MainScreenPresentation(
// 避免0作为被除数
maxKcal = if (maxKcal <= 0.0) 1.0 else maxKcal
Timber.tag(TAG).d("maxKcal 1 = $maxKcal")
include.totalKcalTv.text = totalKcal.format2String(2)
include.totalKcalTv.text = totalKcal.roundedDecimalPlace(1).toString()
var totalKcalHeight = (totalKcal / maxKcal).toFloat()
if (totalKcalHeight == 0F) {
include.divKcalView.visible()
@@ -702,7 +827,7 @@ class MainScreenPresentation(
)
val grainIndex = UserNutritionUtils.findCalorieIndex(energy.grain, grainArray)
include.customFoodColumn.setImageDrawable(false, grainIndex)
include.totalFoodTv.text = grain.format2String(2)
include.totalFoodTv.text = grain.roundedDecimalPlace(1).toString()
columnMax = if (columnMax <= 0.0) 1.0 else columnMax
var grainHeight = (grain / columnMax).toFloat()
if (grainHeight == 0F) {
@@ -722,7 +847,7 @@ class MainScreenPresentation(
val fruitsIndex =
UserNutritionUtils.findCalorieIndex(energy.fruitsVegetables, fruitsArray)
include.customVegetableColumn.setImageDrawable(false, fruitsIndex)
include.totalVegetableTv.text = fruitsVegetables.format2String(2)
include.totalVegetableTv.text = fruitsVegetables.roundedDecimalPlace(1).toString()
var fruitsHeight = (fruitsVegetables / columnMax).toFloat()
if (fruitsHeight == 0F) {
include.divVegetableView.visible()
@@ -734,14 +859,14 @@ class MainScreenPresentation(
// 设置肉蛋-----------------------------------------------------------
val meatEggsArray = UserNutritionUtils.getCalorieArray(
left = userNutrition?.meatEggsRecommend,
mid = userNutrition?.meatEggsNearExcess,
right = userNutrition?.meatEggsExcess
left = userNutrition?.meatEggsBeansRecommend,
mid = userNutrition?.meatEggsBeansNearExcess,
right = userNutrition?.meatEggsBeansExcess
)
val meatEggsIndex =
UserNutritionUtils.findCalorieIndex(energy.meatEggs, meatEggsArray)
include.customMeatColumn.setImageDrawable(false, meatEggsIndex)
include.totalMeatTv.text = meatEggs.format2String(2)
include.totalMeatTv.text = meatEggs.roundedDecimalPlace(1).toString()
var meatHeight = (meatEggs / columnMax).toFloat()
if (meatHeight == 0F) {
include.divMeatView.visible()
@@ -765,6 +890,7 @@ class MainScreenPresentation(
*/
fun updateMealPickupMode(mode: Int) {
Timber.tag(TAG).d("updateMealPickupMode mode = $mode")
Timber.tag(TAG).e("切回step1,------2000000000003")
mealPickupMode = mode
step1FoodRecognizing()
}
@@ -785,7 +911,16 @@ class MainScreenPresentation(
fun resumeCamera() {
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
isRecognition = true
if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) {
if (rgbCameraHelper == null) {
Timber.tag(TAG).d("resumeCamera isRecognition ---1")
setupArcCamera()
binding.root.postDelayed({
if (rgbCameraHelper != null) {
rgbCameraHelper!!.start()
}
}, 2000)
} else if (rgbCameraHelper!!.isStopped) {
Timber.tag(TAG).d("resumeCamera isRecognition ---2")
rgbCameraHelper!!.start()
}
}
@@ -795,6 +930,23 @@ class MainScreenPresentation(
isRecognition = false
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
//triggerReidentify()
}
/**
* 重置人脸跟踪状态,让已在画面中的人脸重新触发识别
* 适用场景:切换按钮后需要重新识别当前画面中的人脸
*/
fun triggerReidentify() {
// 清除UI上的人脸框
binding.dualCameraFaceRectView.clearFaceInfo()
// 清除本地跟踪数据
clearLeftFaceData()
// 重置上一次的 trackId,避免 drawPreviewInfo 中的去重逻辑拦截
lastFaceTrackId = -1
// 插入空帧,让引擎重置内部跟踪状态
// isRecognition 保持 true,相机继续送帧,下一帧真实人脸会产生新 trackId
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
}
private var recognizeTime = 0L
@@ -842,23 +994,30 @@ class MainScreenPresentation(
recognizeViewModel.recognizeUserId.observe(
activity,
Observer { compareResult: CompareResult ->
Observer { result: CompareResult? ->
if (result == null) return@Observer
if (currentStep != 2 || System.currentTimeMillis() - recognizeTime <= 2000) {
//忽略非识别中及秒内的再次识别
//忽略非识别中及5秒内的再次识别
return@Observer
}
// if (isGoRecognize.not()) {
// return@Observer
// }
recognizeTime = System.currentTimeMillis()
activity.runOnUiThread {
activity.showWaitingDialog("加载中,请稍后……")
}
Timber.tag(TAG)
.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}, step = $currentStep")
.i("recognizeUserId observe compareResult = ${result.trackId}, userId = ${result.faceEntity.userName}, step = $currentStep")
recognitionTime = System.currentTimeMillis()
//识别成功时更新lastWeight,解决使用过程中给秤上放东西,导致重量错误问题
lastWeight = activity.currentWeight
recognitionWeight = lastWeight
lastFaceTrackId = compareResult.trackId
val faceEntity = compareResult.faceEntity
lastFaceTrackId = result.trackId
val faceEntity = result.faceEntity
val userId = faceEntity.userName
currentUserId = userId
isMember = faceEntity.userType == "1"
isAgainLoadData = false
if (userId == null) return@Observer
//ToastUtils.showToast("人脸识别成功,userId = $userId")
@@ -872,7 +1031,12 @@ class MainScreenPresentation(
return@runOnUiThread
}
}
// pauseCamera()
//先查询会员折扣
userViewModel.getMemberDiscount(userId) { discount ->
memberDiscount = if (discount == null) 1.0 else discount / 10
step3ShowRecognizeResult(userId)
}
})
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
@@ -978,6 +1142,7 @@ class MainScreenPresentation(
}
if (facePreviewInfoList.isNullOrEmpty()) {
currentUserId = null
isMember = false
}
//recognizeViewModel.clearLeftFace(facePreviewInfoList)
clearLeftFaceData()
@@ -1135,46 +1300,90 @@ class MainScreenPresentation(
val listIsEmpty = facePreviewInfoList.isEmpty()
val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0].trackId else null
Timber.tag(TAG)
.d("listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId")
// Timber.tag(TAG)
// .d("drawPreviewInfo,listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId")
if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) {
if (lastFaceTrackId != -1 && currentUserId != null) {
//0-即放即取,1-余量计量
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
Timber.tag(TAG).i("$lastFaceTrackId 用户离开")
Timber.tag(TAG).i("drawPreviewInfo,$lastFaceTrackId 用户离开")
lastFaceTrackId = -1
Timber.tag(TAG)
.d("postUserData userNutritionData是否null ${userNutrition == null}, currentFood是否null: ${currentFood == null}currentUserId是否null${currentUserId == null}")
.d("drawPreviewInfo,postUserData userNutritionData是否null ${userNutrition == null}, currentFood是否null: ${currentFood == null}currentUserId是否null${currentUserId == null}")
if (userNutrition == null || currentFood == null || currentUserId == null) {
return
}
//重置人脸
recognizeViewModel.resetFaceState()
Timber.tag(TAG).d("drawPreviewInfo,开始提交订单")
createOrder(notEnoughOneBlock = {
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock")
if (activity.settlementMode == 0) {
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock,联合模式")
setTopInfoVisible()
}
// if (mealPickupMode == 0) {
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock,step2FaceRecognizing")
currentStep = 2
step2FaceRecognizing(currentFood!!)
// }
}) {
}, successBlock = {
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成")
// TODO: 测试人脸识别问题--------------------
//recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
if (MyApp.DEBUG) {
if (BuildConfig.DEBUG) {
ToastUtils.showToast("订单已生成")
}
currentUserId = null
isMember = false
//ToastUtils.showToast("isKeepFaceState=$isKeepFaceState,mealPickupMode=$mealPickupMode")
//|| isKeepFaceState
// if (activity.settlementMode == 0) {
// activity.isAnalyzing = false
// step1FoodRecognizing()
// } else {
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,mealPickupMode=$mealPickupMode")
if (mealPickupMode == 1) {
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step2FaceRecognizing")
if (activity.currentWeight > MainActivity.WEIGHT_RESET_RECOGNIZE) {
//余量计量拿走秤上最后一点餐品时,不用再进行人脸识别了
step2FaceRecognizing(currentFood!!)
}
isKeepFaceState = false
if (activity.settlementMode == 0) {
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step2FaceRecognizing,联合支付")
setTopInfoVisible()
}
} else {
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,称重为:${activity.currentWeight}")
//即放即取未拿走餐品,不用重置为默认识别中状态
if (abs(activity.currentWeight) > 10) {
//提交订单后秤重量绝对值大于10克,都认为餐品还没拿走
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,未拿走餐品")
return@createOrder
}
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step1FoodRecognizing")
Timber.tag(TAG).e("切回step1,------2000000000002")
activity.isAnalyzing = false
step1FoodRecognizing()
}
}
// }
})
}
}
}
fun createOrder(notEnoughOneBlock: () -> Unit = {}, block: () -> Unit) {
/**
* 人脸离开后调用创建订单接口
*/
fun createOrder(notEnoughOneBlock: () -> Unit = {}, successBlock: () -> Unit) {
//0-即放即取,1-余量计量
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
//if (settlementMode == 0) {
// //使用联合支付方式
// ToastUtils.showToast("使用联合支付方式")
// return
//}
val eatWeight = if (mealPickupMode == 0) {
//即放即取
recognitionWeight
@@ -1190,16 +1399,21 @@ class MainScreenPresentation(
// foodWeight = lastWeight
//)
//userViewModel.postUserNutritionData(listOf(userNutritionParam))
val realWeight = eatWeight.toDouble()
val specWeight = currentFood!!.specWeight
val eatNum = activity.getEatNum(realWeight, specWeight)
activity.log("createOrder,realWeight=$realWeight,specWeight=$specWeight")
activity.createOrder(
foodInfo = currentFood!!,
foodWeight = lastWeight.roundToInt(),
eatWeight = eatWeight.roundToInt(),
eatNum = activity.getEatNum(eatWeight, currentFood!!.specWeight),
foodWeight = lastWeight,
eatWeight = eatWeight,
eatNum = eatNum,
userId = currentUserId,
isMember = isMember,
notEnoughOneBlock = notEnoughOneBlock,
block = {
successBlock = {
activity.runOnUiThread {
block()
successBlock()
}
}
)
@@ -1244,4 +1458,75 @@ class MainScreenPresentation(
// }
}
private var foodOrderList = mutableListOf<FoodItem>()
private val foodOrderAdapter by lazy {
FoodOrderAdapter(foodOrderList)
}
private fun initRv(rv: RecyclerView) {
rv.run {
if (adapter == null) {
layoutManager =
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
adapter = foodOrderAdapter
}
}
}
/**
* 加载订单列表
*/
private fun loadOrderList(userId: String, callback: () -> Unit) {
binding.llTopInfo.gone()
binding.flFoodDetail.visible()
binding.detailInclude.let {
it.root.updateLayoutParams {
height = activity.screenSize[1] / 2 + 120.dp
}
initRv(it.rvFoodList)
initRv(activity.layoutEatDetailBinding.rvFoodList)
userViewModel.getFoodOrderList(userId) { model ->
activity.runOnUiThread {
if (model == null) return@runOnUiThread
foodOrderList.clear()
foodOrderList.addAll(model.list ?: emptyList())
//foodOrderAdapter.submitList(foodOrderList)
foodOrderAdapter.notifyDataSetChanged()
//binding.detailInclude.tvTotalWeight.text = "总重量:${model.eatWeightSum}克"
Timber.tag(TAG).d("addFoodToOrder: 更新总重量,订单列表------------------------------")
// binding.detailInclude.tvTotalCalorie.text = "总热量:${model.calorie.roundedDecimalPlace(1)}千卡"
jointPaymentQueryFinish = true
//重新加载营养信息
callback()
}
}
}
}
/**
* 构造符合分辨率的 NV21 格式全黑帧
* @param width 图像宽度(如 1920
* @param height 图像高度(如 1080
* @return 合法的全黑帧字节数组
*/
private fun createBlackNV21Frame(width: Int, height: Int): ByteArray {
val frameSize = width * height
val nv21Data = ByteArray(frameSize * 3 / 2) // NV21 格式长度 = 宽×高×1.5
// Y 分量(亮度)设为 0(全黑),UV 分量设为 128(默认值)
// 1. 填充 Y 分量(前 frameSize 个字节)
// Arrays.fill(nv21Data, 0, frameSize, 0.toByte())
nv21Data.fill(0, 0, frameSize)
// 2. 填充 UV 分量(后 frameSize/2 个字节)
// Arrays.fill(nv21Data, frameSize, nv21Data.size, 128.toByte())
nv21Data.fill(128.toByte(), frameSize, nv21Data.size)
return nv21Data
}
// 调用示例:构造 1920×1080 的全黑帧(长度 1382400
// private val emptyFrame = createBlackNV21Frame(1920, 1080)
private val emptyFrame = createBlackNV21Frame(1280, 720)
}
@@ -1,49 +1,49 @@
package com.sw.dualscreen.presentation.pay
import android.R
import android.app.Presentation
import android.os.Bundle
import android.text.Spanned
import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan
import android.view.Display
import androidx.core.text.buildSpannedString
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationCashPayBinding
import com.sw.dualscreen.ext.load
class CashPayPresentation(
val activity: PayActivity,
display: Display,
private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) {
private lateinit var binding: PresentationCashPayBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PresentationCashPayBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawableResource(R.color.transparent)
initView()
}
var foodName: String? = null
var payAmount: String? = null
private fun initView() {
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = getAmountText(payAmount ?: "")
}
private fun getAmountText(amount: String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
}
//package com.sw.dualscreen.presentation.pay
//
//import android.R
//import android.app.Presentation
//import android.os.Bundle
//import android.text.Spanned
//import android.text.SpannedString
//import android.text.style.AbsoluteSizeSpan
//import android.view.Display
//import androidx.core.text.buildSpannedString
//import com.sw.dualscreen.activity.PayActivity
//import com.sw.dualscreen.databinding.PresentationCashPayBinding
//import com.sw.dualscreen.ext.load
//
//class CashPayPresentation(
// val activity: PayActivity,
// display: Display,
// private val onDismissListener: () -> Unit = {}
//) : Presentation(activity, display) {
//
// private lateinit var binding: PresentationCashPayBinding
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// binding = PresentationCashPayBinding.inflate(layoutInflater)
// setContentView(binding.root)
// window?.setBackgroundDrawableResource(R.color.transparent)
// initView()
// }
//
// var foodName: String? = null
// var payAmount: String? = null
// private fun initView() {
// binding.tvFoodName.text = foodName
// binding.tvRealAmount.text = getAmountText(payAmount ?: "")
// }
//
// private fun getAmountText(amount: String): SpannedString {
// return buildSpannedString {
// append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
// append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
// }
// }
//
// override fun onDisplayRemoved() {
// super.onDisplayRemoved()
// onDismissListener()
// }
//}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,826 @@
package com.sw.dualscreen.presentation.pay
import android.Manifest
import android.annotation.SuppressLint
import android.app.Presentation
import android.content.pm.PackageManager
import android.graphics.Point
import android.hardware.Camera
import android.os.Build
import android.os.Bundle
import android.text.Spanned
import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan
import android.view.Display
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.text.buildSpannedString
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import com.arcsoft.face.ErrorInfo
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationPayBinding
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.invisible
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.TextBean
import com.sw.dualscreen.utils.SpannedUtils
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.ConfigUtil
import com.sw.plate.utils.arcface.ErrorCodeUtil
import com.sw.plate.utils.arcface.FaceRectTransformer
import com.sw.plate.utils.arcface.FaceRectView
import com.sw.plate.utils.arcface.FaceRectView.DrawInfo
import com.sw.plate.utils.arcface.PreviewConfig
import com.sw.plate.utils.arcface.camera.CameraListener
import com.sw.plate.utils.arcface.camera.DualCameraHelper
import com.sw.plate.utils.arcface.face.constants.LivenessType
import com.sw.plate.utils.arcface.face.model.CompareResult
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
class PayPresentation(
val activity: PayActivity,
display: Display,
val userViewModel: UserViewModel,
val recognizeViewModel: RecognizeViewModel,
private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
companion object {
private const val TAG = "PayPresentation"
const val CASH_PAY = 100
const val FACE_PAY = 200
const val QR_CODE_PAY = 300
}
private lateinit var binding: PresentationPayBinding
// 虹软人脸配置 ⬇
private var isRecognition = false
private var rgbCameraHelper: DualCameraHelper? = null
private var rgbFaceRectTransformer: FaceRectTransformer? = null
private val livenessType = LivenessType.IR
private var openRectInfoDraw = false
private var irCameraHelper: DualCameraHelper? = null
private var irFaceRectTransformer: FaceRectTransformer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PresentationPayBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.white)
setupArcCamera()
registerDataChange()
initView()
}
override fun show() {
super.show()
pauseCamera()
}
var pageType: Int = 0
var type: Int = 0
// fun updatePage(pageType:Int, type:Int = -1) {
// this.pageType == pageType
// this.type = type
// if (pageType == PayPresentation.CASH_PAY) {
//
// return
// }
// if (pageType == PayPresentation.FACE_PAY) {
//
// return
// }
// if (pageType == PayPresentation.QR_CODE_PAY) {
//
// return
// }
// }
var foodName: String? = null
var payAmount: String? = null
fun initView() {
when (pageType) {
FACE_PAY -> {
//人脸识别
binding.flCashPay.gone()
binding.flQrCodePay.gone()
binding.flFacePay.visible()
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 32),
TextBean(text = payAmount ?: "", textSize = 48),
)
)
//pauseCamera()
resumeCamera()
// binding.root.postDelayed({
// resumeCamera()
// }, 1000)
}
QR_CODE_PAY -> {
//二维码
binding.flCashPay.gone()
binding.flQrCodePay.visible()
binding.flFacePay.gone()
initQrCodeView()
}
CASH_PAY -> {
binding.flCashPay.visible()
binding.flQrCodePay.gone()
binding.flFacePay.gone()
binding.tvFoodName2.text = foodName
binding.tvRealAmount2.text = getAmountText(payAmount ?: "")
}
}
}
private fun getAmountText(amount: String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
override fun onGlobalLayout() {
Timber.tag(TAG).d("onGlobalLayout")
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this)
//parentTextureView.getViewTreeObserver().removeOnGlobalLayoutListener(this)
openCamera()
}
private fun openCamera() {
Timber.tag(TAG).d("openCamera")
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED
) {
ToastUtils.showToast("无摄像头权限")
return
}
try {
val cameraCount = Camera.getNumberOfCameras()
if (cameraCount < 3) {
ToastUtils.showToast("摄像头数量异常")
return
}
recognizeViewModel.init(
PreviewConfig(
2, 1, 90, 90
)
)
initRgbCamera()
if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) {
initIrCamera()
}
} catch (e: Exception) {
Timber.tag(TAG).e(e, "打开摄像头失败")
}
}
/**
* 启用虹软人脸失败
*/
private fun setupArcCamera() {
initArcViewModel()
initArcView()
openRectInfoDraw = true
}
private fun registerDataChange() {
activity.lifecycleScope.launch {
userViewModel.loadFaceResult.collect { needUpdate ->
if (needUpdate) {
recognizeViewModel.refreshFaceList()
}
}
}
}
override fun onStop() {
rgbCameraHelper?.release()
rgbCameraHelper = null
irCameraHelper?.release()
irCameraHelper = null
recognizeViewModel.destroy()
super.onStop()
}
fun resumeCamera() {
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
isRecognition = true
if (rgbCameraHelper?.isStopped == true) {
rgbCameraHelper?.start()
}
}
fun pauseCamera() {
Timber.tag(TAG).d("pauseCamera isRecognition = $isRecognition")
isRecognition = false
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
}
private fun initArcViewModel() {
recognizeViewModel.setLiveType(livenessType)
recognizeViewModel.ftInitCode.observe(activity, Observer { ftInitCode: Int? ->
if (ftInitCode != ErrorInfo.MOK) {
val error: String? = context.getString(
R.string.specific_engine_init_failed, "ftEngine",
ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!)
)
Timber.tag(TAG).e("ftInitCode observe = $error")
ToastUtils.showToast(error)
}
})
recognizeViewModel.frInitCode.observe(activity, Observer { frInitCode: Int? ->
if (frInitCode != ErrorInfo.MOK) {
val error: String? = context.getString(
R.string.specific_engine_init_failed, "frEngine",
frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!)
)
Timber.tag(TAG).e("frInitCode observe = $error")
ToastUtils.showToast(error)
}
})
recognizeViewModel.flInitCode.observe(activity, Observer { flInitCode: Int? ->
if (flInitCode != ErrorInfo.MOK) {
val error: String? = context.getString(
R.string.specific_engine_init_failed, "flEngine",
flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!)
)
Timber.tag(TAG).e("flInitCode observe = $error")
ToastUtils.showToast(error)
}
})
recognizeViewModel.recognizeConfiguration
.observe(activity, Observer { recognizeConfiguration: RecognizeConfiguration? ->
Timber.tag(TAG)
.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}")
})
recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? ->
Timber.tag(TAG).i("recognizeNotice observe notice = $notice")
})
recognizeViewModel.recognizeUserId.observe(
activity,
Observer { compareResult: CompareResult ->
Timber.tag(TAG)
.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}")
// recognitionTime = System.currentTimeMillis()
// recognitionWeight = lastWeight
if (isExistNotPayOrder.get()) return@Observer
lastFaceTrackId = compareResult.trackId
val faceEntity = compareResult.faceEntity
val userId = faceEntity.userName
val isMember = faceEntity.userType == "1"
activity.isMember = isMember
if (userId.isNullOrBlank()) return@Observer
// TODO: 测试支付用户id
// userId = "1987710988425662466"
isExistNotPayOrder.set(true)
faceRecSuccess(userId, isMember)
})
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
Timber.tag(TAG).i("drawRectInfoText observe info = $info")
})
}
private var isExistNotPayOrder = AtomicBoolean(false)
private fun initArcView() {
//在布局结束后才做初始化操作
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
//parentTextureView.getViewTreeObserver().addOnGlobalLayoutListener(this)
recognizeViewModel.getCompareResultList().getValue()
}
/**
* 调整View的宽高,使预览显示正常且采集框固定为
*
* @param rgbPreview RGB预览View
* @param previewView 显示预览数据的view
* @param faceRectView 画框的view
* @param previewSize 预览大小
* @param displayOrientation 相机旋转角度
* @param scale 缩放比例
* @return 调整后的LayoutParams
*/
private fun adjustPreviewViewSize(
rgbPreview: View,
previewView: View,
faceRectView: FaceRectView,
previewSize: Camera.Size,
displayOrientation: Int,
scale: Float
): ViewGroup.LayoutParams {
val w = (600.dp * 1.5).toInt()
val h = 1068.dp
val layoutParams = FrameLayout.LayoutParams(w, h)
previewView.setLayoutParams(layoutParams)
faceRectView.setLayoutParams(layoutParams)
return layoutParams
}
private fun initRgbCamera() {
val cameraListener: CameraListener = object : CameraListener {
override fun onCameraOpened(
camera: Camera,
cameraId: Int,
displayOrientation: Int,
isMirror: Boolean
) {
Timber.tag(TAG)
.d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
activity.runOnUiThread({
val previewSizeRgb = camera.getParameters().getPreviewSize()
val layoutParams = adjustPreviewViewSize(
binding.dualCameraTexturePreviewRgb,
binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView,
previewSizeRgb, displayOrientation, 0.6F
)
Timber.tag(TAG)
.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
Timber.tag(TAG)
.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
Timber.tag(TAG).d(
"initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${
ConfigUtil.isDrawRgbRectHorizontalMirror(
context
)
}, isDrawRgbRectVerticalMirror = ${
ConfigUtil.isDrawRgbRectVerticalMirror(
context
)
}"
)
// 调整识别窗口位置
rgbFaceRectTransformer = FaceRectTransformer(
previewSizeRgb.width,
previewSizeRgb.height,
// layoutParams.width,
// layoutParams.height,
layoutParams.width,
layoutParams.height,
90,
cameraId,
isMirror,
true,
true
)
recognizeViewModel.onRgbCameraOpened(camera)
recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer)
})
}
@RequiresApi(api = Build.VERSION_CODES.Q)
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
if (!isRecognition) {
return
}
binding.dualCameraFaceRectView.clearFaceInfo()
val facePreviewInfoList: MutableList<FacePreviewInfo?>? =
recognizeViewModel.onPreviewFrame(nv21, true)
if (facePreviewInfoList != null && rgbFaceRectTransformer != null) {
drawPreviewInfo(facePreviewInfoList)
}
recognizeViewModel.clearLeftFace(facePreviewInfoList)
}
override fun onCameraClosed() {
Timber.tag(TAG).i("initRgbCamera onCameraClosed: ")
}
override fun onCameraError(e: java.lang.Exception) {
Timber.tag(TAG).i("initRgbCamera onCameraError: %s", e.message)
e.printStackTrace()
}
override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
Timber.tag(TAG)
.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}")
if (rgbFaceRectTransformer != null) {
rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
}
Timber.tag(TAG)
.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation")
}
}
val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth
val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight
Timber.tag(TAG)
.i("initRgbCamera measuredWidth=$measuredWidthmeasuredHeight=$measuredHeight")
val previewConfig: PreviewConfig = recognizeViewModel.previewConfig
rgbCameraHelper = DualCameraHelper.Builder()
.previewViewSize(Point(measuredWidth, measuredHeight))
.rotation(activity.windowManager.defaultDisplay.rotation)
.additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度
.previewSize(recognizeViewModel.loadPreviewSize())
.specificCameraId(previewConfig.rgbCameraId)
.isMirror(true)
.previewOn(binding.dualCameraTexturePreviewRgb)
.cameraListener(cameraListener)
.build()
rgbCameraHelper!!.setSurfaceFrameCallback { frame ->
activity.facePayFragment?.loadBitmap(frame)
}
rgbCameraHelper!!.init()
// rgbCameraHelper!!.start()
}
/**
* 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用
*/
private fun initIrCamera() {
Timber.tag(TAG).d("initIrCamera: livenessType = $livenessType")
if (livenessType === LivenessType.RGB) {
return
}
val irCameraListener: CameraListener = object : CameraListener {
override fun onCameraOpened(
camera: Camera,
cameraId: Int,
displayOrientation: Int,
isMirror: Boolean
) {
Timber.tag(TAG)
.d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
val previewSizeIr = camera.getParameters().getPreviewSize()
val layoutParams = adjustPreviewViewSize(
binding.dualCameraTexturePreviewRgb,
binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr,
previewSizeIr, displayOrientation, 0.25f
)
irFaceRectTransformer = FaceRectTransformer(
previewSizeIr.width, previewSizeIr.height,
// layoutParams.width, layoutParams.height,
layoutParams.width, layoutParams.height,
displayOrientation, cameraId, isMirror,
ConfigUtil.isDrawIrRectHorizontalMirror(context),
ConfigUtil.isDrawIrRectVerticalMirror(context)
)
recognizeViewModel.onIrCameraOpened(camera)
recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer)
}
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
recognizeViewModel.refreshIrPreviewData(nv21)
}
override fun onCameraClosed() {
Timber.tag(TAG).i("initIrCamera onCameraClosed: ")
}
override fun onCameraError(e: java.lang.Exception) {
Timber.tag(TAG).i("initIrCamera onCameraError: ${e.message}")
e.printStackTrace()
}
override fun onCameraConfigurationChanged(
cameraID: Int,
displayOrientation: Int
) {
if (irFaceRectTransformer != null) {
irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
}
Timber.tag(TAG)
.i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation")
}
}
val previewConfig = recognizeViewModel.previewConfig
irCameraHelper = DualCameraHelper.Builder()
.previewViewSize(
Point(
binding.dualCameraTexturePreviewIr.measuredWidth,
binding.dualCameraTexturePreviewIr.measuredHeight
)
)
.rotation(activity.windowManager.defaultDisplay.rotation)
.specificCameraId(previewConfig.irCameraId)
.previewOn(binding.dualCameraTexturePreviewIr)
.cameraListener(irCameraListener)
.isMirror(true)
.previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小
.additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度
.build()
irCameraHelper!!.init()
try {
irCameraHelper!!.start()
} catch (e: RuntimeException) {
ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice))
}
}
/**
* 绘制RGB、IR画面的实时人脸信息
*
* @param facePreviewInfoList RGB画面的实时人脸信息
*/
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) {
// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
if (rgbFaceRectTransformer != null) {
val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo(
facePreviewInfoList,
LivenessType.RGB,
openRectInfoDraw
)
// 识别成功
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList)
}
if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) {
if (lastFaceTrackId != -1) {
// mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
// Timber.tag(TAG).i("$lastFaceTrackId 用户离开")
// lastFaceTrackId = -1
// postUserData()
// if (mealPickupMode == 0) {
// step1FoodRecognizing()
// } else {
// step2FaceRecognizing(currentFood!!)
// }
resumeCamera()
}
}
}
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
private fun faceRecSuccess(userId: String, isMember: Boolean) {
activity.runOnUiThread {
activity.showWaitingDialog("加载中,请稍后……")
}
activity.getMemberInfoById(userId) { memberInfo ->
if (memberInfo == null) {
activity.hideWaitingDialog()
//ToastUtils.showToast("查询会员信息失败,请稍后重试")
return@getMemberInfoById
}
memberInfo.member = isMember
activity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
if (bindResult.not()) {
activity.hideWaitingDialog()
//ToastUtils.showToast("订单绑定失败")
return@bindOrder
}
binding.root.postDelayed({
activity.hideWaitingDialog()
// TODO: 测试 memberInfo.member是否返回正常数据---------------
activity.showPayInfo(type = 1, isVip = memberInfo.member, memberInfo = memberInfo)
activity.hidePayTab()
// binding.root.postDelayed({
// dismiss()
// }, 500)
}, 1000)
}
}
}
// private fun bindOrder(userId: String, block: () -> Unit) {
// val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
// val mode = if (pickupMode == 1) 1 else 2
// userViewModel.bindOrder(userId, activity.foodOrderId, mode = mode) { bindResult ->
// activity.runOnUiThread {
// if (bindResult.not()) {
// activity.hideWaitingDialog()
// //ToastUtils.showToast("订单绑定失败")
// return@runOnUiThread
// }
// block()
// }
// }
// }
// private fun getMemberInfo(userId: String, block:(MemberInfo)-> Unit) {
// userViewModel.getMemberInfoById(memberId = userId) { memberInfo ->
// activity.runOnUiThread {
// if (memberInfo == null) {
// activity.hideWaitingDialog()
// //ToastUtils.showToast("查询会员信息失败,请稍后重试")
// return@runOnUiThread
// }
// block(memberInfo)
// }
// }
// }
var payQrCodePic: Any? = null
//总价格
var totalPrice = 0.0
//余额
private var balance = 0.0
//实际支付金额
private var realPayPrice = 0.0
//扣除余额
private var expensesBalance = 0.0
@SuppressLint("SetTextI18n")
private fun initQrCodeView() {
when (type) {
0 -> {
binding.layoutVip.gone()
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
binding.tvPayTip.text = "请扫码支付或出示付款码"
binding.tvFoodName3.text = foodName
binding.tvRealAmount3.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48),
)
)
binding.ivPayQrCode.load(payQrCodePic)
}
1 -> {
val memberInfo = activity.memberInfo
binding.layoutVip.run {
if (memberInfo != null && memberInfo.member) visible() else gone()
}
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance
memberInfo?.let { loadUserInfo(it) }
binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 20),
TextBean(text = balance.format2String(2), textSize = 32),
)
)
binding.tvFoodName3.text = foodName
binding.tvRealAmount3.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48),
)
)
binding.ivPayQrCode.run {
// load(payQrCodePic)
if (balance >= totalPrice) invisible() else visible()
}
if (memberInfo !=null && memberInfo!!.member) {
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
} else {
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountListNotMember())
}
}
2 -> {
val memberInfo = activity.memberInfo
binding.layoutVip.run {
if (memberInfo != null && memberInfo.member) visible() else gone()
}
binding.layoutPaySuccess.visible()
binding.layoutScanQrCode.gone()
balance = if (memberInfo != null && memberInfo.member) (memberInfo.topUpBalance
?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance
memberInfo?.let { loadUserInfo(it) }
val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 24),
TextBean(text = remainBalance.format2String(2), textSize = 36),
)
)
val showBalance = expensesBalance.format2String(2)
val showTotal = totalPrice.format2String(2)
binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal"
}
}
}
fun loadQrCodeImage(qrCodeUrl: String?) {
binding.ivPayQrCode.load(qrCodeUrl)
}
private fun loadUserInfo(item: MemberInfo) {
binding.ivHeadPic.load(
if (item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
else item.faceUrl
)
binding.tvUserName.text = item.name
val phone = item.phone ?: ""
binding.tvUserPhone.text =
if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
else phone
}
private fun getAmountListNotMember(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf()
list.add(TextBean(text = "应付金额 ", textSize = 30, textColor = "#FF889AC2"))
list.add(
TextBean(
text = "-${totalPrice.format2String(2)}",
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
list.add(TextBean(text = "", textSize = 30, textColor = "#FF889AC2"))
return list
}
private fun getAmountList(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf()
list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
list.add(
TextBean(
text = "-${expensesBalance.format2String(2)}",
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
if (balance >= totalPrice) {
//余额大于等于总价格,使用余额支付
list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
val remainingBalance = balance - totalPrice
list.add(
TextBean(
text = remainingBalance.format2String(2),
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
} else {
//余额小于总价格,使用余额+扫码支付
//实际支付金额
list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
list.add(
TextBean(
text = realPayPrice.format2String(2),
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
}
list.add(TextBean(text = "", textSize = 30, textColor = "#FF5E7585"))
return list
}
}
@@ -1,202 +1,202 @@
package com.sw.dualscreen.presentation.pay
import android.annotation.SuppressLint
import android.app.Presentation
import android.os.Bundle
import android.view.Display
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.invisible
import com.sw.dualscreen.ext.load
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.TextBean
import com.sw.dualscreen.utils.SpannedUtils
class ScanQrCodePayPresentation(
val activity: PayActivity,
display: Display,
val type: Int,
private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) {
//type = 0,扫码支付默认显示二维码
//type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
//type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
private lateinit var binding: PresentationScanQrcodePayBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.transparent)
initView()
}
var foodName: String? = null
var payQrCodePic: Any? = null
//总价格
var totalPrice = 0.0
//余额
private var balance = 0.0
//实际支付金额
private var realPayPrice = 0.0
//扣除余额
private var expensesBalance = 0.0
@SuppressLint("SetTextI18n")
private fun initView() {
when (type) {
0 -> {
binding.layoutVip.gone()
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
binding.tvPayTip.text = "请扫码支付或出示付款码"
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48),
)
)
binding.ivPayQrCode.load(payQrCodePic)
}
1 -> {
val memberInfo = activity.memberInfo
binding.layoutVip.run {
if (memberInfo != null) visible() else gone()
}
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance
memberInfo?.let { loadUserInfo(it) }
binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 20),
TextBean(text = balance.format2String(2), textSize = 32),
)
)
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48),
)
)
binding.ivPayQrCode.run {
// load(payQrCodePic)
if (balance >= totalPrice) invisible() else visible()
}
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
}
2 -> {
val memberInfo = activity.memberInfo
binding.layoutVip.run {
if (memberInfo != null) visible() else gone()
}
binding.layoutPaySuccess.visible()
binding.layoutScanQrCode.gone()
balance = if (memberInfo != null) (memberInfo.topUpBalance
?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance
memberInfo?.let { loadUserInfo(it) }
val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 24),
TextBean(text = remainBalance.format2String(2), textSize = 36),
)
)
val showBalance = expensesBalance.format2String(2)
val showTotal = totalPrice.format2String(2)
binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal"
}
}
}
fun loadQrCodeImage(qrCodeUrl: String?) {
binding.ivPayQrCode.load(qrCodeUrl)
}
private fun loadUserInfo(item: MemberInfo) {
binding.ivHeadPic.load(
if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
else item.faceUrl
)
binding.tvUserName.text = item.name
val phone = item.phone ?: ""
binding.tvUserPhone.text =
if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
else phone
}
private fun getAmountList(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf()
list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
list.add(
TextBean(
text = "-${expensesBalance.format2String(2)}",
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
if (balance >= totalPrice) {
//余额大于等于总价格,使用余额支付
list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
val remainingBalance = balance - totalPrice
list.add(
TextBean(
text = remainingBalance.format2String(2),
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
} else {
//余额小于总价格,使用余额+扫码支付
//实际支付金额
list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
list.add(
TextBean(
text = realPayPrice.format2String(2),
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
}
list.add(TextBean(text = "", textSize = 30, textColor = "#FF5E7585"))
return list
}
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
}
//package com.sw.dualscreen.presentation.pay
//
//import android.annotation.SuppressLint
//import android.app.Presentation
//import android.os.Bundle
//import android.view.Display
//import com.sw.dualscreen.R
//import com.sw.dualscreen.activity.PayActivity
//import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding
//import com.sw.dualscreen.ext.format2String
//import com.sw.dualscreen.ext.gone
//import com.sw.dualscreen.ext.invisible
//import com.sw.dualscreen.ext.load
//import com.sw.dualscreen.ext.visible
//import com.sw.dualscreen.model.response.MemberInfo
//import com.sw.dualscreen.model.response.TextBean
//import com.sw.dualscreen.utils.SpannedUtils
//
//class ScanQrCodePayPresentation(
// val activity: PayActivity,
// display: Display,
// val type: Int,
// private val onDismissListener: () -> Unit = {}
//) : Presentation(activity, display) {
//
// //type = 0,扫码支付默认显示二维码
// //type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
// //type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
//
// private lateinit var binding: PresentationScanQrcodePayBinding
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
// setContentView(binding.root)
// window?.setBackgroundDrawableResource(android.R.color.transparent)
// initView()
// }
//
// var foodName: String? = null
// var payQrCodePic: Any? = null
//
// //总价格
// var totalPrice = 0.0
//
// //余额
// private var balance = 0.0
//
// //实际支付金额
// private var realPayPrice = 0.0
//
// //扣除余额
// private var expensesBalance = 0.0
//
// @SuppressLint("SetTextI18n")
// private fun initView() {
// when (type) {
// 0 -> {
// binding.layoutVip.gone()
// binding.layoutPaySuccess.gone()
// binding.layoutScanQrCode.visible()
//
// binding.tvPayTip.text = "请扫码支付或出示付款码"
// binding.tvFoodName.text = foodName
// binding.tvRealAmount.text = SpannedUtils.getAmountText(
// listOf(
// TextBean(text = "¥", textSize = 32),
// TextBean(text = totalPrice.format2String(2), textSize = 48),
// )
// )
// binding.ivPayQrCode.load(payQrCodePic)
// }
//
// 1 -> {
// val memberInfo = activity.memberInfo
// binding.layoutVip.run {
// if (memberInfo != null) visible() else gone()
// }
// binding.layoutPaySuccess.gone()
// binding.layoutScanQrCode.visible()
//
// balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
// realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
// expensesBalance = if (balance >= totalPrice) totalPrice else balance
//
// memberInfo?.let { loadUserInfo(it) }
// binding.tvUserBalance.text = SpannedUtils.getAmountText(
// listOf(
// TextBean(text = "¥", textSize = 20),
// TextBean(text = balance.format2String(2), textSize = 32),
// )
// )
//
// binding.tvFoodName.text = foodName
// binding.tvRealAmount.text = SpannedUtils.getAmountText(
// listOf(
// TextBean(text = "¥", textSize = 32),
// TextBean(text = totalPrice.format2String(2), textSize = 48),
// )
// )
// binding.ivPayQrCode.run {
//// load(payQrCodePic)
// if (balance >= totalPrice) invisible() else visible()
// }
//
// binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
// }
//
// 2 -> {
// val memberInfo = activity.memberInfo
// binding.layoutVip.run {
// if (memberInfo != null) visible() else gone()
// }
// binding.layoutPaySuccess.visible()
// binding.layoutScanQrCode.gone()
//
// balance = if (memberInfo != null) (memberInfo.topUpBalance
// ?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
// realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
// expensesBalance = if (balance >= totalPrice) totalPrice else balance
//
// memberInfo?.let { loadUserInfo(it) }
// val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
// binding.tvUserBalance.text = SpannedUtils.getAmountText(
// listOf(
// TextBean(text = "¥", textSize = 24),
// TextBean(text = remainBalance.format2String(2), textSize = 36),
// )
// )
//
// val showBalance = expensesBalance.format2String(2)
// val showTotal = totalPrice.format2String(2)
// binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal 元"
// }
// }
// }
//
// fun loadQrCodeImage(qrCodeUrl: String?) {
// binding.ivPayQrCode.load(qrCodeUrl)
// }
//
// private fun loadUserInfo(item: MemberInfo) {
// binding.ivHeadPic.load(
// if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
// else item.faceUrl
// )
// binding.tvUserName.text = item.name
//
// val phone = item.phone ?: ""
// binding.tvUserPhone.text =
// if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
// else phone
// }
//
// private fun getAmountList(): List<TextBean> {
// val list: MutableList<TextBean> = mutableListOf()
//
// list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
// list.add(
// TextBean(
// text = "-${expensesBalance.format2String(2)}",
// textSize = 30,
// textColor = "#FF0A1428",
// isBold = true
// )
// )
//
// if (balance >= totalPrice) {
// //余额大于等于总价格,使用余额支付
// list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
// val remainingBalance = balance - totalPrice
// list.add(
// TextBean(
// text = remainingBalance.format2String(2),
// textSize = 30,
// textColor = "#FF0A1428",
// isBold = true
// )
// )
// } else {
// //余额小于总价格,使用余额+扫码支付
// //实际支付金额
// list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
// list.add(
// TextBean(
// text = realPayPrice.format2String(2),
// textSize = 30,
// textColor = "#FF0A1428",
// isBold = true
// )
// )
// }
// list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF5E7585"))
//
// return list
// }
//
// override fun onDisplayRemoved() {
// super.onDisplayRemoved()
// onDismissListener()
// }
//}
@@ -9,6 +9,7 @@ import com.sw.dualscreen.model.response.FaceData
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodSearchReq
import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.FoodOrderModel
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.UserFaceModel
@@ -76,6 +77,7 @@ class RemoteRepository constructor(
)
}
}
/**
* 获取人脸数据
*/
@@ -134,16 +136,18 @@ class RemoteRepository constructor(
suspend fun getRestInfoFoodsByType(
foodName: String,
pageNum: Int = 1,
pageSize:Int = 50
pageSize: Int = 50
): ApiResponse<List<FoodInfo>> {
return safeApiCall {
apiService.getRestInfoFoodsByType(param = hashMapOf(
apiService.getRestInfoFoodsByType(
param = hashMapOf(
"name" to foodName,
"pageNum" to "$pageNum",
"pageSize" to "$pageSize",
//"订单来源权举:1-营养秤,2-档口
"deviceType" to "2"
))
)
)
}
}
@@ -186,6 +190,26 @@ class RemoteRepository constructor(
}
}
/**
* 二维码支付
*/
suspend fun qrCodePay(
authCode: String,
orderNo: String,
memberId: String?
): ApiResponse<String?> {
return safeApiCall {
apiService.qrCodePay(
param = hashMapOf(
"authCode" to authCode,
"outTradeNo" to orderNo,
"memberId" to memberId,
"orderType" to "0"
)
)
}
}
suspend fun getDinnerType(
// restId: String = GlobalData.restId,
): ApiResponse<DinnerType> {
@@ -247,7 +271,7 @@ class RemoteRepository constructor(
/**
* 会员支付
*/
suspend fun memberPay(param: HashMap<String, String>): ApiResponse<Any?> {
suspend fun memberPay(param: HashMap<String, String?>): ApiResponse<Any?> {
return safeApiCall {
apiService.memberPay(param = param)
}
@@ -276,10 +300,12 @@ class RemoteRepository constructor(
*/
suspend fun getMemberInfoByPhone(phone: String, key: String): ApiResponse<MemberInfo?> {
return safeApiCall {
apiService.getMemberInfoByPhone(param = hashMapOf(
apiService.getMemberInfoByPhone(
param = hashMapOf(
"phone" to phone,
"password" to key
))
)
)
}
}
@@ -317,12 +343,26 @@ class RemoteRepository constructor(
}
}
suspend fun deleteCollectFood(foodId:String?, version: String?): ApiResponse<Any?> {
suspend fun deleteCollectFood(foodId: String?, version: String?): ApiResponse<Any?> {
return safeApiCall {
apiService.deleteCollectFood(param = mutableMapOf(
apiService.deleteCollectFood(
param = mutableMapOf(
"foodId" to foodId,
"version" to version
))
)
)
}
}
suspend fun getFoodOrderList(userId: String): ApiResponse<FoodOrderModel?> {
return safeApiCall {
apiService.getFoodOrderList(userId = userId)
}
}
suspend fun getMemberDiscount(userId: String): ApiResponse<Double?> {
return safeApiCall {
apiService.getMemberDiscount(userId = userId)
}
}
}
@@ -7,7 +7,7 @@ import timber.log.Timber
import kotlin.math.roundToInt
typealias Callback = (Double) -> Unit
typealias WeightCallback = (Double) -> Unit
typealias WeightCallback = (Int) -> Unit
private const val TAG = "SensorScaleUtils"
@@ -31,21 +31,24 @@ object SensorScaleUtils {
* 读取重量
*/
override fun readWeight(state: Int, value: Double) {
val stateStr = when (state) {
SensorScale.STATE_STABLE -> "稳定"
SensorScale.STATE_UNSTABLE -> "不稳定"
SensorScale.STATE_OVER_WEIGHT -> "量程溢出"
else -> "未知"
}
// val stateStr = when (state) {
// SensorScale.STATE_STABLE -> "稳定"
// SensorScale.STATE_UNSTABLE -> "不稳定"
// SensorScale.STATE_OVER_WEIGHT -> "量程溢出"
// else -> "未知"
// }
// Log.d(TAG, "readWeight state = ${stateStr}, weight = $value")
// 只使用稳定值
if (state == SensorScale.STATE_STABLE && lastWeight != value) {
if (state == SensorScale.STATE_STABLE) {
if (lastWeight != value) {
lastWeight = value
callback?.invoke(value)
}
weightCallbackList.forEach {
it.invoke(value)
val currentWeight = value * 1000
it.invoke(currentWeight.roundToInt())
}
}
}
@@ -87,8 +90,7 @@ object SensorScaleUtils {
// Thread.sleep(1000)
// TODO: 暂时不标定---------
//zero()
zero()
}.start()
}
}
@@ -130,15 +132,17 @@ object SensorScaleUtils {
/**
* 零位标定
*/
fun zero() {
fun zero(isShowToastRemind: Boolean = false) {
Timber.d("zero isOpened = $isOpened, mSensorScale = $mSensorScale")
if (!isOpened) return
mSensorScale?.zero {
isZero = true
Timber.d("zero 零位标定操作成功")
if (isShowToastRemind) {
ToastUtils.showToast("零位标定操作成功")
}
}
}
/**
* 去皮置零
@@ -19,11 +19,18 @@ 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 FileUtil {
private static boolean isSaveLog = true;
public static final String FILE_STR_PATH = App.getContext().getExternalCacheDir().getAbsolutePath();
// 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值
public static final int SIZETYPE_MB = 3;//获取文件大小单位为MB的double值
@@ -610,24 +617,19 @@ public class FileUtil {
if (!isSaveLog) {
return;
}
new Thread() {
@Override
public void run() {
super.run();
// 提交到单线程队列,串行执行,避免并发写入竞争
LOG_EXECUTOR.execute(() -> {
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";
FileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
}
}.start();
});
}
private static final String[][] MIME_MapTable = {
@@ -0,0 +1,108 @@
package com.sw.dualscreen.utils
import androidx.lifecycle.LifecycleCoroutineScope
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.viewmodel.UserViewModel
import kotlinx.coroutines.launch
object FoodVectorTool {
/** 每页拉取条数 */
private const val PAGE_SIZE = 100
/**
* 统一入口:分页拉取所有食物向量数据并保存到本地 ObjectBox
*
* @param userViewModel 请求 ViewModel
* @param lifecycleScope 协程作用域(用于保存操作)
* @param successBlock 全部数据拉取并保存成功后回调
* @param failureBlock 任意一次请求失败时回调,参数为错误信息
*/
fun loadAndSaveFoodVector(
userViewModel: UserViewModel,
lifecycleScope: LifecycleCoroutineScope,
successBlock: () -> Unit,
failureBlock: (String) -> Unit
) {
// 每次调用从第 1 页开始,避免单例状态污染
fetchPage(
pageNum = 1,
userViewModel = userViewModel,
lifecycleScope = lifecycleScope,
successBlock = successBlock,
failureBlock = failureBlock
)
}
/**
* 递归分页请求(内部方法,外部不感知分页细节)
*
* @param pageNum 当前页码
* @param userViewModel 请求 ViewModel
* @param lifecycleScope 协程作用域
* @param successBlock 成功回调
* @param failureBlock 失败回调
*/
private fun fetchPage(
pageNum: Int,
userViewModel: UserViewModel,
lifecycleScope: LifecycleCoroutineScope,
successBlock: () -> Unit,
failureBlock: (String) -> Unit
) {
userViewModel.getCollectedFoodVector(
param = mutableMapOf(
"pageNum" to "$pageNum",
"pageSize" to "$PAGE_SIZE",
"version" to GlobalData.foodModelVersion
)
) { items ->
if (items == null) {
failureBlock("未查询到向量数据")
return@getCollectedFoodVector
}
// 第一页为空,视为成功(数据库无数据)
if (pageNum == 1 && items.isEmpty()) {
successBlock()
return@getCollectedFoodVector
}
// 有数据则保存
if (items.isNotEmpty()) {
saveFoodVector(lifecycleScope, items)
}
// 数据量达到一页上限,继续拉取下一页
if (items.size >= PAGE_SIZE) {
fetchPage(pageNum + 1, userViewModel, lifecycleScope, successBlock, failureBlock)
} else {
successBlock()
}
}
}
/**
* 将向量数据列表保存到 ObjectBox 本地数据库
*
* @param lifecycleScope 协程作用域
* @param list 待保存的食物向量数据列表
*/
private fun saveFoodVector(lifecycleScope: LifecycleCoroutineScope, list: List<FoodVector>) {
lifecycleScope.launch {
val vectorList = list.map { vt ->
// 解析向量字符串 "[0.1,0.2,...]" 为 FloatArray
val foodVector = vt.foodVector?.removeSurrounding("[", "]")
?.split(",")?.map { it.toFloatOrNull() ?: 0.0f }?.toFloatArray()
Food(
collectId = vt.id,
foodId = vt.foodId,
foodName = vt.foodName,
version = vt.version,
foodVector = foodVector
)
}
ObjectBox.putAll(vectorList)
}
}
}
@@ -0,0 +1,24 @@
package com.sw.dualscreen.utils
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
/**
* 网络连接检测工具类
* 提供检测设备网络连接状态的功能
*/
object NetworkUtils {
/**
* 检测设备是否连接到网络
* @param context 应用上下文
* @return true 表示已连接网络,false 表示未连接
*/
fun isNetworkConnected(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}
@@ -0,0 +1,427 @@
package com.sw.dualscreen.utils;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SoundPoolUtil {
private static final String TAG = SoundPoolUtil.class.getSimpleName();
private static SoundPoolUtil mSound;
private SoundPool mSoundPool;
private boolean isLoadC = false;
private Map<String, Integer> idCache;
private List<Integer> sidCache;
public SoundPoolUtil() {
idCache = new HashMap<>();
sidCache = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes aab = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA)
.build();
mSoundPool = new SoundPool.Builder()
.setMaxStreams(10)
.setAudioAttributes(aab)
.build();
} else {
mSoundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 8);
}
// mSoundPool = new SoundPool(60, AudioManager.USE_DEFAULT_STREAM_TYPE, 7);
mSoundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener());
}
public static SoundPoolUtil getInstance() {
synchronized (SoundPoolUtil.class) {
if (mSound == null) {
mSound = new SoundPoolUtil();
}
}
return mSound;
}
private int loadCompleteSize = 0;
private LoadCompletion completionListener;
public void setCompletionListener(LoadCompletion listener) {
this.completionListener = listener;
}
private class MyOnLoadCompleteListener implements SoundPool.OnLoadCompleteListener {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
loadCompleteSize++;
L.e("loadSize" + loadSize + "===" + loadCompleteSize);
if (loadSize == loadCompleteSize) {
isLoadC = true;
if (completionListener != null) {
completionListener.onCompletion();
}
}
}
}
/**
* 加载指定资源
*
* @param name
* @param path
*/
public void loadR(String name, String path) {
if (checkSoundPool()) {
if (!idCache.containsKey(name)) {
idCache.put(name, mSoundPool.load(path, 1));
}
}
}
private int loadSize = 0;
/**
* 加载指定路径列表的资源
*
* @param map
*/
public void loadR(Map<String, String> map) {
loadSize = map.size();
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
if (checkSoundPool()) {
if (!idCache.containsKey(key)) {
idCache.put(key, mSoundPool.load(entry.getValue(), 1));
}
}
}
}
/**
* 加载指定AssetFileDescriptor的资源
*
* @param name
* @param afd
*/
public void loadRF(String name, AssetFileDescriptor afd) {
if (checkSoundPool()) {
if (!idCache.containsKey(name)) {
idCache.put(name, mSoundPool.load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), 1));
}
}
}
/**
* 加载指定AssetFileDescriptor列表的资源
*
* @param map
*/
public void loadRF(Map<String, AssetFileDescriptor> map) {
Set<Map.Entry<String, AssetFileDescriptor>> entries = map.entrySet();
for (Map.Entry<String, AssetFileDescriptor> entry : entries) {
String key = entry.getKey();
if (checkSoundPool()) {
if (!idCache.containsKey(key)) {
idCache.put(key, mSoundPool.load(entry.getValue().getFileDescriptor(), entry.getValue().getStartOffset(), entry.getValue().getLength(), 1));
}
}
}
}
/**
* 加载指定列表资源
*
* @param context
* @param map
*/
public void loadR(Context context, Map<String, Integer> map) {
loadSize = map.size();
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
if (checkSoundPool()) {
if (!idCache.containsKey(key)) {
idCache.put(key, mSoundPool.load(context, entry.getValue(), 1));
}
}
}
}
/**
* 加载单个音频
*
* @param context
* @param name
* @param res
*/
public void loadR(Context context, String name, int res) {
if (checkSoundPool()) {
if (!idCache.containsKey(name)) {
idCache.put(name, mSoundPool.load(context, res, 1));
}
}
}
/**
* 播放指定音频,并返用于停止、暂停、恢复的StreamId
*
* @param name
* @param times
* @return
*/
public int play(String name, int times) {
L.e(String.format("play %s times=%s", name, times));
return this.play(name, 1, 1, 1, times, 1);
}
/**
* 播放指定音频,并指定播放次数和频率
*
* @param name
* @param times
* @param rate
* @return
*/
public int play(String name, int times, int rate) {
return this.play(name, 1, 1, 1, times, rate);
}
/**
* 播放指定音频,并指定优先级和播放频率
*
* @param name
* @param property
* @param times
* @param rate
* @return
*/
public int play(String name, int property, int times, int rate) {
return this.play(name, 1, 1, property, times, rate);
}
/**
* 播放指定音频,并指定左右声道、优先级、播放次数、播放频率
*
* @param name
* @param leftVolume
* @param rightVolume
* @param property
* @param times
* @param rate
* @return
*/
public int play(String name, float leftVolume, float rightVolume, int property, int times, int rate) {
int streamId = -1;
if (checkSoundPool()) {
L.d(TAG, "play: " + name);
if (idCache.containsKey(name) && isLoadC) {
L.d(TAG, "name:" + idCache.get(name));
streamId = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate);
L.d(TAG, "streadmId:" + streamId);
sidCache.add(streamId);
}
}
return streamId;
}
/**
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表
*
* @param names
* @param times
* @return
*/
public List<Integer> play(List<String> names, int times) {
return this.play(names, 1, 1, 1, times, 1);
}
/**
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定次数和频率
*
* @param names
* @param times
* @param rate
* @return
*/
public List<Integer> play(List<String> names, int times, int rate) {
return this.play(names, 1, 1, 1, times, rate);
}
/**
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定所有参数
*
* @param names
* @param leftVolume
* @param rightVolume
* @param property
* @param times
* @param rate
* @return
*/
public List<Integer> play(List<String> names, int leftVolume, int rightVolume, int property, int times, int rate) {
List<Integer> streamIds = new ArrayList<>();
if (checkSoundPool()) {
for (String name : names) {
if (idCache.containsKey(name) && isLoadC) {
int a = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate);
streamIds.add(a);
sidCache.add(a);
}
}
}
return streamIds;
}
/**
* 停止指定id音频
*/
public void stop(int r) {
if (checkSoundPool()) {
mSoundPool.stop(r);
}
}
/**
* 停止指定列表音频
*/
public void stopAll() {
if (checkSoundPool()) {
for (int r : sidCache) {
mSoundPool.stop(r);
}
}
}
/**
* 暂停指定音效
*
* @param r
*/
public void pause(int r) {
if (checkSoundPool()) {
mSoundPool.pause(r);
}
}
/**
* 暂停指定列表音频
*
* @param list
*/
public void pause(List<Integer> list) {
if (checkSoundPool()) {
for (int r : list) {
mSoundPool.pause(r);
}
}
}
/**
* 暂停所有音效
*/
public void pauseAll() {
mSoundPool.autoPause();
}
/**
* 恢复指定音频播放
*
* @param r
*/
public void resume(int r) {
if (checkSoundPool()) {
mSoundPool.resume(r);
}
}
/**
* 恢复指定列表的音频
*
* @param list
*/
public void resume(List<Integer> list) {
if (checkSoundPool()) {
for (int r : list) {
mSoundPool.resume(r);
}
}
}
/**
* 恢复所有暂停的音频
*/
public void resumeAll() {
if (checkSoundPool()) {
mSoundPool.autoResume();
}
}
/**
* 卸载指定音频
*
* @param name
*/
public void unLoad(String name) {
if (checkSoundPool()) {
if (idCache.containsKey(name)) {
mSoundPool.unload(idCache.get(name));
idCache.remove(name);
}
}
}
/**
* 卸载指定列表的音频
*
* @param names
*/
public void unLoad(List<String> names) {
if (checkSoundPool()) {
for (String name : names) {
if (idCache.containsKey(name)) {
mSoundPool.unload(idCache.get(name));
idCache.remove(name);
}
}
}
}
/**
* 释放所有资源,如果想继续播放,需要重新加载资源
*/
public void release() {
if (checkSoundPool()) {
mSound = null;
mSoundPool.release();
idCache.clear();
}
}
private boolean checkSoundPool() {
if (mSoundPool != null) {
return true;
}
return false;
}
public interface LoadCompletion {
void onCompletion();
}
}
@@ -1,19 +0,0 @@
package com.sw.dualscreen.utils;
import com.sw.dualscreen.MyApp;
public class SpTool {
public static final String LAST_FACE_TIMESTAMP = "faceTimestamp";
public static long getLastFaceTimestamp() {
return SPUtil.Companion.getInstance(MyApp.getContext(), "default_sp").get(LAST_FACE_TIMESTAMP, 0L);
}
public static void setLastFaceTimestamp(long timestamp) {
SPUtil.Companion.getInstance(MyApp.getContext(), "default_sp").put(LAST_FACE_TIMESTAMP, timestamp);
}
}
@@ -0,0 +1,42 @@
package com.sw.dualscreen.utils
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.MyApp
import com.sw.dualscreen.utils.SPUtil.Companion.getInstance
object SpTool {
const val LAST_FACE_TIMESTAMP: String = "faceTimestamp"
const val IS_FIRST_GET_FACE = "isFirstGetFace"
private var spUtil: SPUtil? = null
init {
spUtil = getInstance(MyApp.instance!!, "default_sp")
}
var lastFaceTimestamp: Long
get() = spUtil?.get(LAST_FACE_TIMESTAMP, 0L) ?: 0L
set(timestamp) {
spUtil?.put(LAST_FACE_TIMESTAMP, timestamp)
}
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)
}
var settlementMode: Int
get() = spUtil?.get(GlobalKey.KEY_SETTLEMENT_MODE, 0) ?: 0
set(value) {
spUtil?.put(GlobalKey.KEY_SETTLEMENT_MODE, value)
}
}
@@ -21,7 +21,7 @@ class HollowOvalInRectView @JvmOverloads constructor(
private val rectWidthDp = 400f // 矩形宽度
private val rectHeightDp = 510f // 矩形高度
private val ovalWidthDp = 400f // 椭圆宽度
private val ovalHeightDp = 436f // 椭圆高度
private val ovalHeightDp = 416f // 椭圆高度
// 转换后的像素尺寸
private val rectWidth: Float
@@ -65,7 +65,9 @@ abstract class BaseViewModel() : ViewModel() {
}
val message = response.msg
Timber.d("msg = ${message}, code = $code")
if (message!=null && !message.startsWith("未知错误")) {
ToastUtils.showToast("${message}(${code})")
}
return false
}
@@ -5,10 +5,12 @@ import com.arcsoft.face.ErrorInfo
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.model.request.UserNutritionParam
import com.sw.dualscreen.model.response.ApiResponse
import com.sw.dualscreen.model.response.DeviceConfig
import com.sw.dualscreen.model.response.DinnerType
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.FoodOrderModel
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.UserFaceModel
@@ -16,6 +18,7 @@ import com.sw.dualscreen.model.response.UserFaceModel2
import com.sw.dualscreen.model.response.UserNutrition
import com.sw.dualscreen.objbox.CollectedFoodInfo
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.FileUtil
import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.utils.SpTool
import com.sw.plate.App
@@ -89,27 +92,46 @@ class UserViewModel : BaseViewModel() {
/**
* 获取人脸数据
*/
fun getUserFaceCache(pageNo: Int = 1, pageSize: Int = PAGE_SIZE) {
fun getUserFaceCache(
pageNo: Int = 1,
pageSize: Int = PAGE_SIZE,
onSuccess: () -> Unit = {},
onFailure: (String) -> Unit = {}
) {
// Initialize current page number
var currentPageNo = pageNo
// Debug log to track current page
Timber.tag(TAG).d("getUserFaceCache index = $currentPageNo")
// Launch a coroutine to perform the face data retrieval
launch {
// Set loading state to false
_loadFaceResult.value = false
//获取全量数据时,时间戳改为0
SpTool.setLastFaceTimestamp(0)
SpTool.lastFaceTimestamp = 0
FileUtil.saveLog("获取人脸全量数据开始,重置时间戳为0")
val response = repository.getUserFaceCache(currentPageNo)
if (parseResponse(response)) {
val check = parseResponse(response)
if (!check) {
// 获取失败
onFailure("获取人脸数据失败,${response.msg}(${response.code})")
return@launch
}
// 获取成功一次后缓存状态
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
withContext(Dispatchers.Default) {
val list: List<UserFaceModel> = response.data ?: emptyList()
if (pageNo == 1 && list.isEmpty()) {
ToastUtils.showToast("查询人脸数据为空")
//ToastUtils.showToast("查询人脸数据为空")
onFailure("查询人脸数据为空")
return@withContext
}
// val item = list.firstOrNull { it.userId == "1951105919342936066" }
// Timber.tag(TAG).d("getUserFaceCache userId = ${item?.userId}")
//val item = list.map { it.member }
//Timber.tag(TAG).d("getUserFaceCache item = $item")
val faceEntity = list.map {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr)).also { item ->
item.userType = if (it.member) "1" else "2"
item.cardNo = it.cardNo
}
}
faceApi.updateFaceData(currentPageNo, faceEntity)
if (list.size >= pageSize) {
@@ -123,10 +145,11 @@ class UserViewModel : BaseViewModel() {
//非空最后一条数据的是时间戳,空使用上次list最后一条时间戳
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
}
SpTool.setLastFaceTimestamp(lastFaceTimestamp)
SpTool.lastFaceTimestamp = lastFaceTimestamp
FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp")
_loadFaceResult.value = true
onSuccess()
}
// val nextPageIndex = response.result?.nextPageIndex ?: -1
// if (nextPageIndex > 0) {
// getUserFaceCache(nextPageIndex)
@@ -135,7 +158,6 @@ class UserViewModel : BaseViewModel() {
// }
}
}
}
// fun getUserFaceCache2(index: Int = 0) {
// Timber.d("getUserFaceCache index = $index")
@@ -201,7 +223,8 @@ class UserViewModel : BaseViewModel() {
//非空实现最后一条数据的是时间戳,空使用上次list最后一条时间戳
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
}
SpTool.setLastFaceTimestamp(lastFaceTimestamp)
SpTool.lastFaceTimestamp = lastFaceTimestamp
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
onAllQueryFinished()
}
}
@@ -356,6 +379,25 @@ class UserViewModel : BaseViewModel() {
}
}
/**
* 二维码支付
*/
fun qrCodePay(
authCode: String,
orderNo: String,
memberId: String?,
block: (Boolean, String?) -> Unit
) {
launch {
val response = repository.qrCodePay(authCode, orderNo, memberId)
if (parseResponse(response)) {
block(true, response.data)
} else {
block(false, null)
}
}
}
fun getDinnerType() {
Timber.tag(TAG).d("getDinnerType")
_dinnerTypeInfo.value = null
@@ -474,7 +516,7 @@ class UserViewModel : BaseViewModel() {
/**
* 会员支付
*/
fun memberPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
Timber.tag(TAG).d("memberPay")
launchWithLoading {
val response = repository.memberPay(param)
@@ -571,19 +613,22 @@ class UserViewModel : BaseViewModel() {
}
}
fun getCollectedFoodVector(param: MutableMap<String, String>, block: (List<FoodVector>?) -> Unit) {
fun getCollectedFoodVector(
param: MutableMap<String, String>,
block: (List<FoodVector>?) -> Unit
) {
Timber.tag(TAG).d("getCollectedFoodVector")
launchWithLoading {
val response = repository.getCollectedFoodVector(param)
if (parseResponse(response)) {
block(response.data?:emptyList())
block(response.data ?: emptyList())
} else {
block(null)
}
}
}
fun deleteCollectFood(foodId:String?, version: String?, block: (Boolean) -> Unit) {
fun deleteCollectFood(foodId: String?, version: String?, block: (Boolean) -> Unit) {
Timber.tag(TAG).d("deleteCollectFood")
launchWithLoading {
val response = repository.deleteCollectFood(foodId, version)
@@ -594,4 +639,28 @@ class UserViewModel : BaseViewModel() {
}
}
}
fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) {
Timber.tag(TAG).d("getFoodOrderList")
launchWithLoading {
val response = repository.getFoodOrderList(userId)
if (parseResponse(response)) {
block(response.data)
} else {
block(null)
}
}
}
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
Timber.tag(TAG).d("getMemberDiscount")
launchWithLoading {
val response = repository.getMemberDiscount(userId)
if (parseResponse(response)) {
block(response.data)
} else {
block(null)
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#F2F2F2"/>
<!-- <stroke android:color="#cdcdcd" android:width="1dp"/>-->
<corners android:radius="5dp"/>
</shape>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- 眼睛轮廓 -->
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/black"
android:strokeWidth="1.5"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
<!-- 瞳孔(path绘制圆形) -->
<path
android:fillColor="@android:color/black"
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
<!-- 斜线表示隐藏 -->
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/black"
android:strokeWidth="1.5"
android:strokeLineCap="round"
android:pathData="M 4 4 L 20 20" />
</vector>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- 眼睛轮廓 -->
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/black"
android:strokeWidth="1.5"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
<!-- 瞳孔(path绘制圆形) -->
<path
android:fillColor="@android:color/black"
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
</vector>
+18 -45
View File
@@ -22,6 +22,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="28dp"
android:paddingVertical="10dp"
android:textColor="#ff0a1428"
android:textSize="26sp"
android:textStyle="bold"
@@ -48,19 +49,23 @@
android:textColor="#FF0A1428"
android:textSize="52sp"
android:textStyle="bold" />
<!-- <TextView-->
<!-- android:id="@+id/tvShowWeight"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="80dp"-->
<!-- android:textSize="30sp"-->
<!-- android:gravity="center"-->
<!-- tools:text="100克"/>-->
<TextView
android:id="@+id/tvShowWeight"
android:layout_width="match_parent"
android:layout_height="80dp"
android:textSize="20sp"
android:gravity="center"
android:maxLines="1"
android:textColor="@color/black"
tools:text="100克"/>
<FrameLayout
android:id="@+id/foodDisplayLayout"
android:layout_width="match_parent"
android:layout_height="560dp"
android:layout_marginHorizontal="28dp"
android:layout_marginTop="80dp"
android:layout_marginTop="0dp"
android:visibility="visible">
<androidx.camera.view.PreviewView
@@ -84,53 +89,21 @@
android:layout_margin="27dp"
android:background="@drawable/ic_camera_rect" />
<!-- preview_border-->
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
<FrameLayout
android:id="@+id/flFoodList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="14dp"
android:layout_marginTop="42dp"
android:layout_weight="1"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="8"
tools:listitem="@layout/list_item_search_food" />
<TextView
android:id="@+id/tvToSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="38dp"
android:layout_marginBottom="0dp"
android:padding="10dp"
android:text="以上都不是,手动搜索"
android:textColor="#FFFF3232"
android:textSize="31sp"
android:textStyle="bold"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="54dp"
android:gravity="center">
<TextView
android:id="@+id/tvWeightRealInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="12sp"
tools:text="以上都不是,手动搜索" />
</LinearLayout>
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/flPay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:layout_marginTop="45dp"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatButton
+3 -3
View File
@@ -13,7 +13,7 @@
<RadioGroup
android:id="@+id/rgSetting"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_height="75dp"
android:orientation="horizontal">
<Space
@@ -31,7 +31,7 @@
android:maxLines="1"
android:paddingHorizontal="50dp"
android:paddingTop="8dp"
android:paddingBottom="35dp"
android:paddingBottom="15dp"
android:text="经营设置"
android:textColor="@color/setting_radio"
android:textSize="30sp"
@@ -53,7 +53,7 @@
android:maxLines="1"
android:paddingHorizontal="50dp"
android:paddingTop="8dp"
android:paddingBottom="35dp"
android:paddingBottom="15dp"
android:text="餐品采集"
android:textColor="@color/setting_radio"
android:textSize="30sp"
@@ -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">
<Button
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_border_red"
android:text="取消"
android:textColor="#FFFF3232"
android:textSize="28sp" />
<Button
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_btn_save2"
android:text="确认"
android:textColor="#FFFFFF"
android:textSize="28sp" />
</LinearLayout>
</LinearLayout>
@@ -6,6 +6,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 结算设置模块 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -16,6 +17,85 @@
android:paddingTop="35dp"
android:paddingBottom="42dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="17dp"
android:text="结算设置"
android:textColor="@color/black"
android:textSize="36sp"
android:textStyle="bold"
app:drawableStartCompat="@drawable/line_shape" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="(不允许修改,由后端配置)"
android:textColor="#FFFF3232"
android:textSize="20sp" />
</LinearLayout>
<RadioGroup
android:id="@+id/rgSettlement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="35dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rbJointPayment"
android:layout_width="0dp"
android:layout_height="120dp"
android:layout_weight="1"
android:background="@drawable/setting_check_selector"
android:button="@null"
android:gravity="center"
android:maxLines="1"
android:text="联合支付"
android:textColor="@color/setting_radio"
android:textSize="30sp"
android:textStyle="bold" />
<RadioButton
android:id="@+id/rbIndependentPayment"
android:layout_width="0dp"
android:layout_height="120dp"
android:layout_marginStart="32dp"
android:layout_weight="1"
android:background="@drawable/setting_check_selector"
android:button="@null"
android:gravity="center"
android:maxLines="1"
android:text="独立支付"
android:textColor="@color/setting_radio"
android:textSize="30sp"
android:textStyle="bold"
tools:checked="true" />
</RadioGroup>
</LinearLayout>
<!-- 计费设置模块 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="42dp"
android:paddingHorizontal="32dp"
android:paddingTop="35dp"
android:paddingBottom="42dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+11 -14
View File
@@ -11,16 +11,14 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:paddingBottom="16dp">
android:paddingBottom="10dp">
<TextView
android:id="@+id/tvChargeSetting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="32dp"
android:layout_marginTop="35dp"
android:layout_marginTop="20dp"
android:drawablePadding="17dp"
android:text="餐品采集"
android:textColor="@color/black"
@@ -34,7 +32,6 @@
android:id="@+id/btnCollectedFood"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="32dp"
android:gravity="center_vertical"
android:includeFontPadding="false"
@@ -51,7 +48,7 @@
android:id="@+id/flPreview"
android:layout_width="456dp"
android:layout_height="342dp"
android:layout_marginTop="41dp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="@id/tvChargeSetting"
app:layout_constraintTop_toBottomOf="@id/tvChargeSetting">
@@ -114,9 +111,9 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="382dp"
android:layout_height="358dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginVertical="10dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -132,7 +129,7 @@
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="32dp"
android:layout_marginTop="20dp"
android:gravity="center_vertical"
android:orientation="horizontal">
@@ -167,22 +164,22 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="18dp"
android:layout_marginTop="12dp"
android:minHeight="348dp"
android:layout_weight="1"
app:spanCount="2"
tools:itemCount="10"
tools:itemCount="11"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:listitem="@layout/list_item_search_food"/>
tools:listitem="@layout/list_item_search_food3"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginHorizontal="32dp"
android:layout_marginBottom="32dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:text="保存"
android:background="@drawable/bg_btn_save"
android:textColor="@color/white"
@@ -34,17 +34,21 @@
android:textStyle="bold"
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
<EditText
android:id="@+id/etInputNumber"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginHorizontal="64dp"
android:inputType="textVisiblePassword"
android:layout_marginTop="32dp">
<EditText
android:id="@+id/etInputNumber"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPassword"
android:background="@drawable/setting_border_gray3"
android:hint="输入消费码"
android:textColorHint="#ffc4cfda"
android:textColor="#ff0a1428"
android:layout_marginTop="32dp"
android:textSize="36sp"
android:gravity="center"
android:maxLines="1"
@@ -53,6 +57,19 @@
android:textStyle="bold"
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
<ImageView
android:id="@+id/ivTogglePassword"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_vertical|end"
android:paddingEnd="16dp"
android:contentDescription="@string/app_name"
android:src="@drawable/ic_eye_close"
android:scaleType="centerInside"
tools:ignore="HardcodedText" />
</FrameLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="match_parent"
@@ -285,7 +285,7 @@
<TextView
android:id="@+id/meatTv"
style="@style/takeFoodNameTv"
android:text="肉"
android:text="肉蛋豆"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:ignore="HardcodedText,UseCompatTextViewDrawableXml">
<FrameLayout
android:id="@+id/llFoodTitle"
android:layout_width="match_parent"
android:layout_height="80dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="取餐明细"
android:textColor="#ff33446a"
android:textSize="30sp"
android:textStyle="bold"
android:drawablePadding="17dp"
android:drawableStart="@drawable/ic_take_food" />
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="30dp"
android:singleLine="true"
android:textColor="#FF889AC2"
android:textSize="26sp"
tools:text="张三" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="8dp"
android:background="#FFDDE5F0" />
<include layout="@layout/layout_food_list_header" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="#FFDDE5F0" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never"
tools:listitem="@layout/layout_food_list_header" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
<TextView
android:id="@+id/tvTotalWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ff889ac2"
android:textSize="26sp"
tools:text="总重量:680克"
android:drawablePadding="17dp"
android:drawableStart="@drawable/ic_flag_weight" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/tvTotalCalorie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ff889ac2"
android:textSize="26sp"
tools:text="总热量:793千卡"
android:drawablePadding="17dp"
android:drawableStart="@drawable/ic_flag_calorie" />
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,60 @@
<?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="50dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<!-- 菜品名称列,权重2.5 -->
<TextView
android:id="@+id/tvFoodName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2.1"
android:text="菜品名称"
android:textColor="#FF889AC2"
android:textSize="26sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center_vertical|start" />
<!-- 规格列,权重1.0 -->
<TextView
android:id="@+id/tvPriceNorm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.6"
android:text="规格"
android:textColor="#FF889AC2"
android:textSize="26sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center" />
<!-- 重量列,权重1.0 -->
<TextView
android:id="@+id/tvWeight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="重量(克)"
android:textColor="#FF889AC2"
android:textSize="26sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center" />
<!-- 小计列,权重1.0 -->
<TextView
android:id="@+id/tvAmount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="小计(元)"
android:textColor="#FF889AC2"
android:textSize="26sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center" />
</LinearLayout>
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/tvToSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="38dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:padding="10dp"
tools:text="以上都不是,手动搜索"
android:textColor="#FFFF3232"
android:textSize="31sp"
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/tvToSearch"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginHorizontal="14dp"
android:layout_marginTop="42dp"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="8"
tools:listitem="@layout/list_item_search_food" />
<include
android:id="@+id/detailInclude"
layout="@layout/layout_eat_detail"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,40 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="264dp"
android:layout_marginHorizontal="14dp"
android:layout_marginTop="42dp"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="8"
tools:listitem="@layout/list_item_search_food" />
<TextView
android:id="@+id/tvToSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="38dp"
android:layout_gravity="center_horizontal"
android:padding="10dp"
tools:text="以上都不是,手动搜索"
android:textColor="#FFFF3232"
android:textSize="31sp"
android:textStyle="bold"/>
<include
android:id="@+id/detailInclude"
layout="@layout/layout_eat_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="14dp"/>
</LinearLayout>
+1 -1
View File
@@ -2,7 +2,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="150dp">
android:layout_height="130dp">
<ImageView
android:id="@+id/ivPageBack"
@@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="212dp"
android:layout_height="159dp"
android:layout_margin="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="10dp"
android:background="@drawable/bg_gray2">
<ImageView
@@ -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="50dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:gravity="center_vertical"
xmlns:tools="http://schemas.android.com/tools">
<!-- 菜品名称列,权重2.5 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2.1"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvFoodName"
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="菜品名称"
android:textColor="#FF889AC2"
android:textSize="25sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:ellipsize="end"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintWidth_default="wrap"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/tvLabel"
android:gravity="center_vertical|start" />
<TextView
android:id="@+id/tvLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="档口"
android:textColor="@color/black"
android:textSize="13sp"
android:background="@drawable/bg_order_flag"
android:paddingHorizontal="5dp"
android:paddingVertical="1dp"
android:layout_marginHorizontal="2dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tvFoodName"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- 规格列,权重1.0 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.6"
android:gravity="center">
<TextView
android:id="@+id/tvPriceNorm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="规格"
android:textColor="#FF889AC2"
android:textSize="25sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center" />
<TextView
android:id="@+id/tvPriceFlag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="[大份]"
android:textColor="@color/black"
android:textSize="15sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center"
android:visibility="gone"/>
</LinearLayout>
<!-- 重量列,权重1.0 -->
<TextView
android:id="@+id/tvWeight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
tools:text="重量(克)"
android:textColor="#FF889AC2"
android:textSize="25sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center" />
<!-- 小计列,权重1.0 -->
<TextView
android:id="@+id/tvAmount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
tools:text="小计(元)"
android:textColor="#FF889AC2"
android:textSize="25sp"
android:paddingHorizontal="5dp"
android:singleLine="true"
android:gravity="center" />
</LinearLayout>
@@ -8,8 +8,8 @@
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:layout_marginHorizontal="14dp"
android:layout_marginVertical="14dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:paddingHorizontal="15dp"
android:textAlignment="center"
tools:textColor="@color/black"
@@ -8,8 +8,8 @@
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:layout_marginHorizontal="14dp"
android:layout_marginVertical="14dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:paddingHorizontal="15dp"
android:textAlignment="center"
android:textColor="@color/color_text_dialog"
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tvFoodName"
android:layout_width="match_parent"
android:layout_height="90dp"
tools:background="@drawable/bg_text_main"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
android:paddingHorizontal="15dp"
android:textAlignment="center"
tools:textColor="@color/black"
android:textSize="30sp"
android:textStyle="bold"
tools:text="肉沫干拌面" />
@@ -13,11 +13,13 @@
android:padding="28dp">
<LinearLayout
android:id="@+id/llTopInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:paddingBottom="26dp">
android:paddingBottom="26dp"
android:visibility="visible">
<TextView
android:id="@+id/tvFoodName"
@@ -30,10 +32,10 @@
android:textSize="40sp"
android:textStyle="bold" />
<!-- android:layout_width="300dp"-->
<!-- android:layout_height="450dp"-->
<!-- android:layout_width="330dp"-->
<!-- android:layout_height="495dp"-->
<!--android:layout_width="300dp"-->
<!--android:layout_height="450dp"-->
<!--android:layout_width="330dp"-->
<!--android:layout_height="495dp"-->
<FrameLayout
android:id="@+id/flCameraView"
android:layout_width="match_parent"
@@ -187,6 +189,20 @@
</LinearLayout>
<FrameLayout
android:id="@+id/flFoodDetail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/setting_border"
android:paddingHorizontal="15dp"
android:visibility="gone">
<include
android:id="@+id/detailInclude"
layout="@layout/layout_eat_detail"/>
</FrameLayout>
<FrameLayout
android:id="@+id/flBottomView"
android:layout_width="match_parent"
@@ -0,0 +1,418 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8">
<LinearLayout
android:id="@+id/flFacePay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="28dp"
android:layout_marginTop="28dp"
android:background="@drawable/setting_border"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingVertical="56dp">
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0A1428"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="32sp"
android:textStyle="bold"
tools:text="¥36.80" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="79dp"
android:text="会员验证"
android:textColor="#ff0a1428"
android:textSize="27sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<!-- android:layout_width="300dp"-->
<!-- android:layout_height="510dp"-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="510dp">
<FrameLayout
android:id="@+id/flFace"
android:layout_width="400dp"
android:layout_height="680dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="-85dp">
<TextureView
android:id="@+id/dual_camera_texture_preview_rgb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
<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
android:id="@+id/flRecognizeIr"
android:layout_width="1.33dp"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:visibility="visible">
<TextureView
android:id="@+id/dual_camera_texture_preview_ir"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.sw.plate.utils.arcface.FaceRectView
android:id="@+id/dual_camera_face_rect_view_ir"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/llCircleArea"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#FFF4FAFF" />
<com.sw.dualscreen.view.HollowOvalInRectView
android:layout_width="400dp"
android:layout_height="510dp"
android:layout_gravity="center_horizontal" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#FFF4FAFF" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请正视屏幕进行面部识别..."
android:textColor="#ff5e7585"
android:textSize="22sp"
android:textStyle="bold"
android:layout_gravity="bottom|center_horizontal"
tools:ignore="HardcodedText" />
</FrameLayout>
<!-- <View-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="85dp"-->
<!-- android:layout_gravity="bottom"-->
<!-- android:background="#FFF4FAFF"/>-->
</FrameLayout>
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/flCashPay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical">
<TextView
android:id="@+id/tvFoodName2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0A1428"
android:layout_marginTop="210dp"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="32sp"
android:textStyle="bold"
tools:text="¥36.80" />
<!-- <TextView-->
<!-- android:id="@+id/tvPayTip"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="55dp"-->
<!-- android:text="请扫码支付或出示付款码"-->
<!-- android:textColor="#FF5E7585"-->
<!-- android:textSize="24sp"-->
<!-- android:textStyle="bold"-->
<!-- tools:ignore="HardcodedText" />-->
</LinearLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/flQrCodePay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8"
android:visibility="gone">
<LinearLayout
android:id="@+id/layoutScanQrCode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:visibility="visible">
<TextView
android:id="@+id/tvFoodName3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="327dp"
android:textColor="#FF0A1428"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="48sp"
android:textStyle="bold"
tools:text="¥36.80" />
<ImageView
android:id="@+id/ivPayQrCode"
android:layout_width="333dp"
android:layout_height="333dp"
android:layout_marginTop="56dp"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_qrcode" />
<TextView
android:id="@+id/tvPayTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="请扫码支付或出示付款码"
android:textColor="#FF5E7585"
android:textSize="24sp"
tools:ignore="HardcodedText" />
<!-- android:textStyle="bold"-->
</LinearLayout>
<LinearLayout
android:id="@+id/layoutPaySuccess"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="471dp"
android:src="@drawable/ic_pay_success" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="支付成功"
android:textColor="#ff00c864"
android:textSize="48sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPayInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:textColor="#FF5E7585"
android:textSize="24sp"
tools:text="余额扣除 16.80 元,在线支付 20.00 元" />
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/layoutVip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/white"
android:layout_marginTop="56dp"
android:layout_marginHorizontal="56dp"
app:cardCornerRadius="5dp"
app:cardElevation="0dp"
android:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="28dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivHeadPic"
android:layout_width="84dp"
android:layout_height="84dp"
android:src="@drawable/ic_avatar_default"
android:layout_marginTop="28dp"
app:shapeAppearance="@style/CircleStyle"
app:strokeColor="@android:color/transparent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="27dp"
android:textColor="#ff0a1428"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tvUserPhone"
app:layout_constraintStart_toEndOf="@id/ivHeadPic"
app:layout_constraintTop_toTopOf="@id/ivHeadPic"
app:layout_constraintVertical_chainStyle="packed"
tools:text="DAMIHU" />
<TextView
android:id="@+id/tvUserPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:textColor="#ff889ac2"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@id/ivHeadPic"
app:layout_constraintStart_toStartOf="@id/tvUserName"
app:layout_constraintTop_toBottomOf="@id/tvUserName"
tools:text="138****8000" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="28dp"
android:background="#FFDBE5FB"
app:layout_constraintTop_toBottomOf="@id/ivHeadPic" />
<TextView
android:id="@+id/tvBalanceName"
android:layout_width="wrap_content"
android:layout_height="79dp"
android:text="可用余额"
android:gravity="center_vertical"
android:textColor="#ff889ac2"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="@id/ivHeadPic"
app:layout_constraintTop_toBottomOf="@id/divider" />
<TextView
android:id="@+id/tvUserBalance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0A1428"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@id/tvBalanceName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvBalanceName"
tools:text="¥16.80" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
</FrameLayout>
Binary file not shown.
+14 -3
View File
@@ -2,6 +2,7 @@
agp = "8.10.1"
core = "3.4.1"
androidCore = "3.3.0"
eventbus = "3.2.0"
glide = "4.16.0"
kotlin = "2.0.21"
coreKtx = "1.10.1"
@@ -13,7 +14,10 @@ material = "1.10.0"
activity = "1.8.0"
constraintlayout = "2.1.4"
accompanistPermissions = "0.37.3"
cameraCore = "1.3.0"
cameraCore = "1.4.1"
objectboxVersion = "5.0.1"
refreshLayoutKernel = "3.0.0-alpha"
refreshHeaderClassics = "3.0.0-alpha"
retrofit = "3.0.0"
okhttp = "4.12.0"
timber = "5.0.1"
@@ -30,11 +34,13 @@ pytorch = "1.13.0"
recyclerview = "1.3.2"
#baseQuickAdapter = "4.1.2"
baseQuickAdapter = "3.0.4"
baseQuickAdapter4 = "4.1.2"
[libraries]
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
core = { module = "com.google.zxing:core", version.ref = "core" }
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@@ -56,7 +62,10 @@ hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", ve
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
objectbox-android-objectbrowser = { module = "io.objectbox:objectbox-android-objectbrowser", version.ref = "objectboxVersion" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
refresh-header-classics = { module = "io.github.scwang90:refresh-header-classics", version.ref = "refreshHeaderClassics" }
refresh-layout-kernel = { module = "io.github.scwang90:refresh-layout-kernel", version.ref = "refreshLayoutKernel" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
@@ -68,10 +77,12 @@ androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
#noinspection Aligned16KB
pytorch-android = { group = "org.pytorch", name = "pytorch_android", version.ref = "pytorch" }
pytorch-android-torchvision = { group = "org.pytorch", name = "pytorch_android_torchvision", version.ref = "pytorch" }
#base-quick-adapter-ktx = { module = "io.github.cymchad:BaseRecyclerViewAdapterHelper4", version.ref = "baseQuickAdapter" }
#baseRecyclerViewAdapterHelper = { module = "com.github.CymChad:BaseRecyclerViewAdapterHelper", version.ref = "baseQuickAdapter" }
baseRecyclerViewAdapterHelper4 = { module = "io.github.cymchad:BaseRecyclerViewAdapterHelper4", version.ref = "baseQuickAdapter4" }
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
+2 -2
View File
@@ -1,8 +1,8 @@
#Mon Jul 14 11:28:37 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -5,7 +5,10 @@ import static android.content.Context.INPUT_SERVICE;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import java.util.Arrays;
@@ -14,172 +17,185 @@ import java.util.List;
/**
* 扫描枪事件处理
*/
public class ScanGunKeyEventHelper {
private final static long MESSAGE_DELAY = 500; //延迟500ms,判断扫码是否完成。
private final StringBuffer mStringBufferResult; //扫码内容
private boolean mCaps; //大小写区分
private final Handler mHandler;
private final Runnable mScanningFishedRunnable;
private OnScanSuccessListener mOnScanSuccessListener;
private final Context mContext;
// private String mDeviceName = "TMC HIDKeyBoard";
//private String mDeviceName = "Linux 3.4.35 with ak-hsudc Composite Gadget (ACM + HID)";
//private String mDeviceName1 = "USBKey Chip USBKey Module";
private static final long SCAN_DELAY = 200; // 扫码结束判断时间
private String[] deviceNameArray = new String[]{
"CROWN CROWN Barcode KeyBorad FS",
"Linux 3.4.35 with ak-hsudc Composite Gadget (ACM + HID)",
"USBKey Chip USBKey Module"
};
private long lastTime = 0L;
private final StringBuilder resultBuffer = new StringBuilder();
private boolean caps;
public ScanGunKeyEventHelper(Context context, OnScanSuccessListener onScanSuccessListener) {
mContext = context;
mOnScanSuccessListener = onScanSuccessListener;
mStringBufferResult = new StringBuffer();
mHandler = new Handler();
mScanningFishedRunnable = this::performScanSuccess;
private final Handler handler = new Handler(Looper.getMainLooper());
private final Runnable scanFinishRunnable;
private OnScanSuccessListener listener;
private long lastScanTime = 0;
public ScanGunKeyEventHelper(OnScanSuccessListener listener) {
this.listener = listener;
scanFinishRunnable = this::handleScanFinish;
}
/**
* 返回扫码成功后的结果
* 扫码结束
*/
private void performScanSuccess() {
String barcode = mStringBufferResult.toString();
L.d("performScanSuccess barcode = " + barcode);
long currentTime = System.currentTimeMillis();
if (currentTime - lastTime < 5 * 1000) {
L.d("performScanSuccess 操作太频繁");
mStringBufferResult.setLength(0);
private void handleScanFinish() {
String barcode = resultBuffer.toString();
if (barcode.isEmpty()) {
return;
}
lastTime = currentTime;
if (mOnScanSuccessListener != null && !TextUtils.isEmpty(barcode))
mOnScanSuccessListener.onScanSuccess(barcode);
mStringBufferResult.setLength(0);
long now = System.currentTimeMillis();
// 防止重复扫描
if (now - lastScanTime < 1000) {
resultBuffer.setLength(0);
return;
}
lastScanTime = now;
if (listener != null) {
listener.onScanSuccess(barcode);
}
resultBuffer.setLength(0);
}
/**
* 扫码枪事件解析
*
* @param event
* 解析 KeyEvent
*/
public void analysisKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
//字母大小写判断
checkLetterStatus(event);
if (event.getAction() == KeyEvent.ACTION_DOWN) {
char aChar = getInputCode(event);
if (aChar != 0) {
mStringBufferResult.append(aChar);
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return;
}
checkCaps(event);
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_ENTER) {
//若为回车键,直接返回
mHandler.removeCallbacks(mScanningFishedRunnable);
mHandler.post(mScanningFishedRunnable);
} else {
//延迟post,若500ms内,有其他事件
mHandler.removeCallbacks(mScanningFishedRunnable);
mHandler.postDelayed(mScanningFishedRunnable, MESSAGE_DELAY);
handler.removeCallbacks(scanFinishRunnable);
handler.post(scanFinishRunnable);
return;
}
}
char c = getInputCode(event);
if (c != 0) {
resultBuffer.append(c);
}
//检查shift键
private void checkLetterStatus(KeyEvent event) {
handler.removeCallbacks(scanFinishRunnable);
handler.postDelayed(scanFinishRunnable, SCAN_DELAY);
}
/**
* 判断是否为扫码枪
*/
public boolean isScanGunEvent(KeyEvent event) {
if (event == null) return false;
InputDevice device = event.getDevice();
if (device == null) return false;
int sources = device.getSources();
// HID keyboard
boolean isKeyboard =
(sources & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD;
// 排除虚拟键盘
boolean notVirtual =
event.getDeviceId() != KeyCharacterMap.VIRTUAL_KEYBOARD;
return isKeyboard && notVirtual;
}
/**
* SHIFT 状态
*/
private void checkCaps(KeyEvent event) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
//按着shift键,表示大写
mCaps = true;
} else {
//松开shift键,表示小写
mCaps = false;
}
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
caps = event.getAction() == KeyEvent.ACTION_DOWN;
}
}
/**
* 获取扫描内容
*
* @param event
* @return
* KeyEvent 转字符
*/
private char getInputCode(KeyEvent event) {
int keyCode = event.getKeyCode();
char aChar;
if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
//字母
aChar = (char) ((mCaps ? 'A' : 'a') + keyCode - KeyEvent.KEYCODE_A);
return (char) ((caps ? 'A' : 'a') + keyCode - KeyEvent.KEYCODE_A);
} else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
//数字
aChar = (char) ('0' + keyCode - KeyEvent.KEYCODE_0);
} else {
//其他符号
return (char) ('0' + keyCode - KeyEvent.KEYCODE_0);
}
switch (keyCode) {
case KeyEvent.KEYCODE_PERIOD:
aChar = '.';
break;
case KeyEvent.KEYCODE_MINUS:
aChar = mCaps ? '_' : '-';
break;
case KeyEvent.KEYCODE_SLASH:
aChar = '/';
break;
return caps ? '_' : '-';
case KeyEvent.KEYCODE_EQUALS:
return caps ? '+' : '=';
case KeyEvent.KEYCODE_LEFT_BRACKET:
return caps ? '{' : '[';
case KeyEvent.KEYCODE_RIGHT_BRACKET:
return caps ? '}' : ']';
case KeyEvent.KEYCODE_BACKSLASH:
aChar = mCaps ? '|' : '\\';
break;
return caps ? '|' : '\\';
case KeyEvent.KEYCODE_SEMICOLON:
return caps ? ':' : ';';
case KeyEvent.KEYCODE_APOSTROPHE:
return caps ? '"' : '\'';
case KeyEvent.KEYCODE_COMMA:
return caps ? '<' : ',';
case KeyEvent.KEYCODE_PERIOD:
return caps ? '>' : '.';
case KeyEvent.KEYCODE_SLASH:
return caps ? '?' : '/';
case KeyEvent.KEYCODE_SPACE:
return ' ';
default:
aChar = 0;
break;
return 0;
}
}
return aChar;
public void release() {
handler.removeCallbacks(scanFinishRunnable);
listener = null;
}
public interface OnScanSuccessListener {
void onScanSuccess(String barcode);
}
public void onDestroy() {
mHandler.removeCallbacks(mScanningFishedRunnable);
mOnScanSuccessListener = null;
}
/**
* 输入设备是否存在
*
* @param deviceName
* @return
*/
public boolean isInputDeviceExist(String deviceName) {
InputManager inputManager = (InputManager) mContext.getSystemService(INPUT_SERVICE);
int[] deviceIds = inputManager.getInputDeviceIds();
for (int id : deviceIds) {
if (inputManager.getInputDevice(id).getName().equals(deviceName)) {
return true;
}
}
return false;
}
/**
* 是否为扫码枪事件(部分机型KeyEvent获取的名字错误)
*
* @param event
* @return
*/
public boolean isScanGunEvent(KeyEvent event) {
if (event == null || event.getDevice() == null) return false;
String deviceName = event.getDevice().getName();
L.e("event===" + deviceName +
"===Char===" + event.getCharacters() +
"===Action===" + event.getAction());
//return deviceName.equals(mDeviceName) || deviceName.equals(mDeviceName1);
List<String> devices = Arrays.asList(deviceNameArray);
return devices.contains(deviceName);
}
}
@@ -341,6 +341,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() < 260) {//距离远(人脸小),不识别
clearLeftFace(facePreviewInfoList);
return facePreviewInfoList;
}
}
if (!onlyDetectLiveness) {
code = ftEngine.process(rgbNv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList,
@@ -397,6 +397,19 @@ 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();
}
}
/**
* 释放操作
*/