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
71 changed files with 2273 additions and 893 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
Generated
-1
View File
@@ -1 +0,0 @@
DualScreen
-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-03-10T03:02:12.156794100Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.211:5555;connection=7435869e" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>
-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>
-6
View File
@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="userId" value="29face46:19ce5e8d9d0:-7fe0" />
</MTProjectMetadataState>
</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>
+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")
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
}
-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": 5
}
],
"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
}
+2 -1
View File
@@ -61,7 +61,8 @@
<!-- </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"
@@ -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"
}
+13 -1
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
@@ -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
@@ -182,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)
}
}
@@ -4,7 +4,6 @@ import android.Manifest
import android.content.Intent
import android.os.CountDownTimer
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
@@ -12,46 +11,27 @@ 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>()
private var pageNum = 1
// 相机权限请求回调
private val requestCameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// 权限请求成功,打开 MainActivity
goMainActivity()
} else {
// 权限请求失败,显示弹窗
showPermissionDeniedDialog()
}
}
// 设置页面返回的回调
private val startSettingsLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
// 从设置页面返回,重新检查权限
checkCameraPermissionAfterSettings()
}
override fun getViewModel(): BaseViewModel {
return userViewModel
}
@@ -61,55 +41,22 @@ 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) {
@@ -146,6 +93,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
}
private fun getCollectedFoodVector() {
binding.okButton.invisible()
showWaitingDialog2("加载中……")
lifecycleScope.launch {
FoodModule.init(this@InitActivity) {
@@ -167,30 +115,30 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
finish()
}
// 检查相机权限
private fun checkCameraPermissionAndGo() {
val hasCameraPermission = ContextCompat.checkSelfPermission(
private fun hasCameraPermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == android.content.pm.PackageManager.PERMISSION_GRANTED
}
if (hasCameraPermission) {
// 检查相机权限
private fun checkCameraPermissionAndGo() {
val isGrantedCameraPermission = hasCameraPermission()
if (isGrantedCameraPermission) {
// 有权限,直接打开 MainActivity
goMainActivity()
} else {
// 无权限,请求相机权限
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
requestPermission(Manifest.permission.CAMERA) { isGranted ->
checkCameraPermission(isGranted)
}
}
}
// 从设置页面返回后检查权限
private fun checkCameraPermissionAfterSettings() {
val hasCameraPermission = ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == android.content.pm.PackageManager.PERMISSION_GRANTED
if (hasCameraPermission) {
private fun checkCameraPermission(isGranted: Boolean) {
if (isGranted) {
// 权限已授予,打开 MainActivity
goMainActivity()
} else {
@@ -209,7 +157,11 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = android.net.Uri.fromParts("package", packageName, null)
}
startSettingsLauncher.launch(intent)
startActivity(intent) {
// 从设置页面返回,重新检查权限
val isGrantedCameraPermission = hasCameraPermission()
checkCameraPermission(isGrantedCameraPermission)
}
}
).show()
}
@@ -230,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,6 +201,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initialize() {
super.initialize()
settlementMode = SpTool.settlementMode
isPageVisible = true
// 拦截扫码枪按键事件,防止触发页面按钮点击
registerKeyEvent()
@@ -162,24 +214,83 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
initData()
}
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() {
startFaceTask()
}
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))
@@ -190,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()
}
@@ -215,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 = "-"
@@ -225,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
@@ -240,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()) {
@@ -258,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
@@ -307,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()
@@ -331,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
@@ -356,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
@@ -369,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() {
@@ -541,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() {
@@ -576,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 {
@@ -585,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()
}
}
@@ -640,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) {
@@ -661,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,
@@ -675,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({
@@ -690,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)
}
@@ -704,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 = ""
@@ -729,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
@@ -762,46 +1028,50 @@ 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
shutdownCamera()
@@ -925,6 +1195,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 10 * 1000L
private val dealyMillis = 5 * 1000L
// private var taskPageNo = 1
fun startFaceTask() {
faceTaskJob =
@@ -944,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 ->
@@ -981,7 +1253,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
private fun sendFaceData(faceData: String) {
@@ -995,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}")
@@ -1033,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)) {
@@ -1045,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()
}
@@ -1071,4 +1341,5 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// ObjectBox.init(this)
// }
}
@@ -166,6 +166,8 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
super.onDestroy()
}
var memberDiscount = 1.0
var memberInfo: MemberInfo? = null
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
this.memberInfo = memberInfo
@@ -191,10 +193,20 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
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()
}
}
}
fun updateQrCodeImage(qrCodeUrl: String?) {
@@ -287,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 ->
@@ -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()
@@ -23,7 +23,9 @@ 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()
}
@@ -75,7 +75,9 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
}
if (memberInfo != null && memberInfo!!.member) {
totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
// 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
@@ -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
@@ -96,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}
@@ -131,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 = ""
@@ -139,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(
@@ -159,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
)
@@ -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
@@ -311,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)
// }
}
}
@@ -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)
}
@@ -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
@@ -352,4 +353,16 @@ class RemoteRepository constructor(
)
}
}
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)
}
}
@@ -1,5 +1,6 @@
package com.sw.dualscreen.utils
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.MyApp
import com.sw.dualscreen.utils.SPUtil.Companion.getInstance
@@ -27,4 +28,15 @@ object SpTool {
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)
}
}
@@ -10,6 +10,7 @@ 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
@@ -17,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
@@ -90,21 +92,37 @@ 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.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("查询人脸数据为空")
onFailure("查询人脸数据为空")
return@withContext
}
//val item = list.map { it.member }
@@ -128,9 +146,10 @@ class UserViewModel : BaseViewModel() {
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
}
SpTool.lastFaceTimestamp = lastFaceTimestamp
FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp")
_loadFaceResult.value = true
onSuccess()
}
// val nextPageIndex = response.result?.nextPageIndex ?: -1
// if (nextPageIndex > 0) {
// getUserFaceCache(nextPageIndex)
@@ -139,7 +158,6 @@ class UserViewModel : BaseViewModel() {
// }
}
}
}
// fun getUserFaceCache2(index: Int = 0) {
// Timber.d("getUserFaceCache index = $index")
@@ -206,6 +224,7 @@ class UserViewModel : BaseViewModel() {
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
}
SpTool.lastFaceTimestamp = lastFaceTimestamp
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
onAllQueryFinished()
}
}
@@ -620,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>
+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 -11
View File
@@ -11,14 +11,14 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:background="@drawable/setting_border"
android:paddingBottom="16dp">
android:paddingBottom="10dp">
<TextView
android:id="@+id/tvChargeSetting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="30dp"
android:layout_marginTop="20dp"
android:drawablePadding="17dp"
android:text="餐品采集"
android:textColor="@color/black"
@@ -48,7 +48,7 @@
android:id="@+id/flPreview"
android:layout_width="456dp"
android:layout_height="342dp"
android:layout_marginTop="30dp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="@id/tvChargeSetting"
app:layout_constraintTop_toBottomOf="@id/tvChargeSetting">
@@ -111,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"
@@ -129,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">
@@ -164,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"
@@ -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"
@@ -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"
+13 -2
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"
@@ -14,6 +15,9 @@ activity = "1.8.0"
constraintlayout = "2.1.4"
accompanistPermissions = "0.37.3"
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" }
+1 -1
View File
@@ -1,7 +1,7 @@
#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-all.zip
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.14.3-all.zip
zipStoreBase=GRADLE_USER_HOME
@@ -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();
}
}
/**
* 释放操作
*/