Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a769eae2a0 | ||
|
|
0241caa5fc | ||
|
|
512e0b5b4f | ||
|
|
a8ade16058 | ||
|
|
ffd00266b8 | ||
|
|
43f5434cbd | ||
|
|
76f60f8ee7 | ||
|
|
28a475ca43 | ||
|
|
16dba32eb7 | ||
|
|
5a7b090c02 | ||
|
|
b6ed47ff87 | ||
|
|
088aabc535 | ||
|
|
061680b32b | ||
|
|
5c3e51626c | ||
|
|
936bf206dc | ||
|
|
8e4dcb9f61 | ||
|
|
8ba4ba6db0 | ||
|
|
37f86e202f | ||
|
|
737baf8486 | ||
|
|
bf6b2547f3 | ||
|
|
6abd2f9b0b | ||
|
|
c19d84a7a6 | ||
|
|
8f1f15dd74 | ||
|
|
416a8366e1 | ||
|
|
03c5aee785 | ||
|
|
e3b01ff789 | ||
|
|
b6c9fe5aed | ||
|
|
45562a9fc1 | ||
|
|
7f455f2d8f | ||
|
|
dde0bbc4d5 | ||
|
|
d378cdcf97 | ||
|
|
e5b682f021 | ||
|
|
0498e124b3 | ||
|
|
30b2b178a9 | ||
|
|
2ba55ef027 | ||
|
|
a45690d3be | ||
|
|
ab0d9b35f5 | ||
|
|
0127697560 | ||
|
|
dbe3753821 | ||
|
|
b16147b1b2 | ||
|
|
d6528d33fc | ||
|
|
5479091959 | ||
|
|
12da153df3 | ||
|
|
3ef93c5f5e | ||
|
|
91800a6bb1 | ||
|
|
f40c2046f1 | ||
|
|
ee9f27bb9c | ||
|
|
5c230e38c3 | ||
|
|
10d1f966da | ||
|
|
57f8920442 | ||
|
|
fb4278d2b7 | ||
|
|
fcea0d785d | ||
|
|
c4a6101a7b | ||
|
|
bf8444e5d5 | ||
|
|
549e8d303f | ||
|
|
b51be58c64 | ||
|
|
a6d2441704 | ||
|
|
e7d11530a6 | ||
|
|
c56769769b | ||
|
|
8673cec545 | ||
|
|
8ec7ea6a79 | ||
|
|
a80e3d1ba6 | ||
|
|
6d825d7235 | ||
|
|
4f1860eb37 | ||
|
|
45a7bf8369 | ||
|
|
f0b50ef82f | ||
|
|
824dc5f867 | ||
|
|
7232735073 | ||
|
|
8a1630f2a4 | ||
|
|
39c0ca7d72 |
+3
-6
@@ -1,15 +1,12 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea/caches
|
/.idea/
|
||||||
/.idea/libraries
|
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/navEditor.xml
|
|
||||||
/.idea/assetWizardSettings.xml
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
app/objectbox-models/*.json
|
||||||
|
app/objectbox-models/*.json.bak
|
||||||
|
|||||||
Generated
-3
@@ -1,3 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
Generated
-6
@@ -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>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<bytecodeTargetLevel target="17" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-18
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetSelector">
|
|
||||||
<selectionStates>
|
|
||||||
<SelectionState runConfigName="app">
|
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
|
||||||
<DropdownSelection timestamp="2026-02-06T02:13:05.230327500Z">
|
|
||||||
<Target type="DEFAULT_BOOT">
|
|
||||||
<handle>
|
|
||||||
<DeviceId pluginId="Default" identifier="serial=192.168.1.170:5555;connection=1ec19d1e" />
|
|
||||||
</handle>
|
|
||||||
</Target>
|
|
||||||
</DropdownSelection>
|
|
||||||
<DialogSelection />
|
|
||||||
</SelectionState>
|
|
||||||
</selectionStates>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-20
@@ -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>
|
|
||||||
Generated
-10
@@ -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>
|
|
||||||
Generated
-9
@@ -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>
|
|
||||||
Generated
-17
@@ -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
@@ -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>
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||||
|
1. Kotlin compile daemon is ready
|
||||||
|
|
||||||
+12
-8
@@ -51,13 +51,15 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
}
|
}
|
||||||
viewBinding {
|
|
||||||
enable = true
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
@@ -108,17 +110,19 @@ dependencies {
|
|||||||
implementation(libs.pytorch.android)
|
implementation(libs.pytorch.android)
|
||||||
implementation(libs.pytorch.android.torchvision)
|
implementation(libs.pytorch.android.torchvision)
|
||||||
|
|
||||||
implementation("io.github.scwang90:refresh-layout-kernel:3.0.0-alpha")
|
implementation(libs.baseRecyclerViewAdapterHelper4)
|
||||||
implementation("io.github.scwang90:refresh-header-classics:3.0.0-alpha")
|
|
||||||
|
implementation(libs.refresh.layout.kernel)
|
||||||
|
implementation(libs.refresh.header.classics)
|
||||||
|
|
||||||
val objectboxVersion = "5.0.1"
|
val objectboxVersion = "5.0.1"
|
||||||
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
debugImplementation(libs.objectbox.android.objectbrowser)
|
||||||
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||||
// implementation("io.objectbox:objectbox-fulltext:5.0.1")
|
// implementation("io.objectbox:objectbox-fulltext:5.0.1")
|
||||||
|
|
||||||
implementation(libs.androidx.recyclerview)
|
implementation(libs.androidx.recyclerview)
|
||||||
|
|
||||||
implementation("org.greenrobot:eventbus:3.2.0")
|
implementation(libs.eventbus)
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(plugin = "io.objectbox")
|
apply(plugin = "io.objectbox")
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "com.sw.dualscreen",
|
||||||
|
"variantName": "debug",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"attributes": [],
|
||||||
|
"versionCode": 1,
|
||||||
|
"versionName": "1.0",
|
||||||
|
"outputFile": "档口双屏_1.0-debug.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elementType": "File",
|
||||||
|
"minSdkVersionForDexing": 29
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -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
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "com.sw.dualscreen",
|
||||||
|
"variantName": "release",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"attributes": [],
|
||||||
|
"versionCode": 1,
|
||||||
|
"versionName": "1.0",
|
||||||
|
"outputFile": "档口双屏_1.0-release.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elementType": "File",
|
||||||
|
"baselineProfiles": [
|
||||||
|
{
|
||||||
|
"minApi": 28,
|
||||||
|
"maxApi": 30,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/1/档口双屏_1.0-release.dm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minApi": 31,
|
||||||
|
"maxApi": 2147483647,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/0/档口双屏_1.0-release.dm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minSdkVersionForDexing": 29
|
||||||
|
}
|
||||||
@@ -61,7 +61,8 @@
|
|||||||
<!-- </activity>-->
|
<!-- </activity>-->
|
||||||
<!-- <activity android:name="com.sw.dualscreen.activity.CollectedDataActivity" />-->
|
<!-- <activity android:name="com.sw.dualscreen.activity.CollectedDataActivity" />-->
|
||||||
<activity android:name="com.sw.dualscreen.activity.SettingActivity"
|
<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"
|
<activity android:name="com.sw.dualscreen.activity.CollectedFoodActivity"
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||||
<activity android:name="com.sw.dualscreen.activity.PayActivity"
|
<activity android:name="com.sw.dualscreen.activity.PayActivity"
|
||||||
|
|||||||
@@ -17,11 +17,16 @@ object GlobalData {
|
|||||||
*/
|
*/
|
||||||
var appVersion: String = "1"
|
var appVersion: String = "1"
|
||||||
|
|
||||||
|
var appBaseUrl: String = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 具体业务baseurl
|
* 具体业务 BaseUrl
|
||||||
*/
|
*/
|
||||||
var appBaseUrl2: String = "http://192.168.1.201:14801"
|
// const val LOCAL_BASE_URL = "http://192.168.1.201:14801"
|
||||||
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
|
const val LOCAL_BASE_URL = "http://192.168.10.101:24801"
|
||||||
|
// const val TEST_BASE_URL = "https://dev.yixiong-tech.com:8083"
|
||||||
|
const val TEST_BASE_URL = "https://dev.yixiong-tech.com:8081"
|
||||||
|
const val PROD_BASE_URL = "https://platform-api.uat.shuziweidao.com"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜品识别模型版本号,后边以接口返回为准
|
* 菜品识别模型版本号,后边以接口返回为准
|
||||||
@@ -68,4 +73,8 @@ object GlobalKey {
|
|||||||
const val KEY_USER_INFO = "userInfoKey"
|
const val KEY_USER_INFO = "userInfoKey"
|
||||||
const val KEY_PICKUP_MODE = "pickupMode"
|
const val KEY_PICKUP_MODE = "pickupMode"
|
||||||
const val KEY_CHARGE_MODE = "chargeMode"
|
const val KEY_CHARGE_MODE = "chargeMode"
|
||||||
|
// 餐具重量(克),取餐提交时用于从就餐重量中扣减,默认 0 表示不扣减
|
||||||
|
const val KEY_DISH_WEIGHT = "dishWeight"
|
||||||
|
const val KEY_SETTLEMENT_MODE = "settlementMode"
|
||||||
|
const val KEY_BASE_URL = "baseUrlKey"
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import com.sw.dualscreen.objbox.ObjectBox
|
|||||||
import com.sw.dualscreen.sdk.SensorScaleUtils
|
import com.sw.dualscreen.sdk.SensorScaleUtils
|
||||||
import com.sw.dualscreen.utils.ActivityManager
|
import com.sw.dualscreen.utils.ActivityManager
|
||||||
import com.sw.dualscreen.utils.CrashHandler
|
import com.sw.dualscreen.utils.CrashHandler
|
||||||
|
import com.sw.dualscreen.utils.SpTool
|
||||||
import com.sw.plate.App
|
import com.sw.plate.App
|
||||||
import com.sw.plate.utils.AppUtil
|
import com.sw.plate.utils.AppUtil
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -13,7 +14,7 @@ import timber.log.Timber
|
|||||||
class MyApp : App() {
|
class MyApp : App() {
|
||||||
companion object {
|
companion object {
|
||||||
const val DEBUG: Boolean = true
|
const val DEBUG: Boolean = true
|
||||||
var instance: MyApp?=null
|
var instance: MyApp? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@@ -24,13 +25,23 @@ class MyApp : App() {
|
|||||||
Timber.d("deviceId = $deviceId")
|
Timber.d("deviceId = $deviceId")
|
||||||
// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
|
// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
|
||||||
GlobalData.deviceId = deviceId
|
GlobalData.deviceId = deviceId
|
||||||
GlobalData.appBaseUrl = if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) GlobalData.appBaseUrl2 else GlobalData.appBaseUrl
|
|
||||||
ObjectBox.init(this)
|
ObjectBox.init(this)
|
||||||
// 初始化崩溃处理器
|
// 初始化崩溃处理器
|
||||||
CrashHandler.init(this)
|
CrashHandler.init(this)
|
||||||
|
|
||||||
SensorScaleUtils.startScale()
|
SensorScaleUtils.startScale()
|
||||||
addActivityLifecycleListener()
|
addActivityLifecycleListener()
|
||||||
|
|
||||||
|
// 优先读取用户手动切换后持久化的 url
|
||||||
|
val savedUrl = SpTool.baseUrl
|
||||||
|
if (!savedUrl.isNullOrEmpty()) {
|
||||||
|
GlobalData.appBaseUrl = savedUrl
|
||||||
|
} else {
|
||||||
|
// 未保存过则使用默认逻辑:特定设备走测试环境,其余走 UAT 测试档口机:2987f0c5-5754-33e9-b00a-251db5e2e55f
|
||||||
|
GlobalData.appBaseUrl =
|
||||||
|
if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) GlobalData.TEST_BASE_URL else GlobalData.PROD_BASE_URL
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addActivityLifecycleListener() {
|
private fun addActivityLifecycleListener() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.sw.dualscreen.activity
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -59,7 +60,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
keyEventHelper?.onDestroy()
|
keyEventHelper?.release()
|
||||||
EventBus.getDefault().unregister(this)
|
EventBus.getDefault().unregister(this)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
@@ -153,7 +154,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
*/
|
*/
|
||||||
fun registerKeyEvent() {
|
fun registerKeyEvent() {
|
||||||
keyEventHelper =
|
keyEventHelper =
|
||||||
ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener {
|
ScanGunKeyEventHelper( object : ScanGunKeyEventHelper.OnScanSuccessListener {
|
||||||
override fun onScanSuccess(barcode: String?) {
|
override fun onScanSuccess(barcode: String?) {
|
||||||
Timber.d("onScanSuccess barcode = $barcode")
|
Timber.d("onScanSuccess barcode = $barcode")
|
||||||
if (barcode == null) return
|
if (barcode == null) return
|
||||||
@@ -166,9 +167,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
/**
|
/**
|
||||||
* 处理扫描枪数据
|
* 处理扫描枪数据
|
||||||
*/
|
*/
|
||||||
open fun scanGunKeyEventCallback(scanInfo: String) {
|
fun scanGunKeyEventCallback(scanInfo: String){
|
||||||
Timber.tag("TAG").d("handleScanKeyInfo: $scanInfo")
|
if (this is PayActivity) {
|
||||||
|
qrCodePay(scanInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
if (keyEventHelper != null) {
|
if (keyEventHelper != null) {
|
||||||
@@ -179,4 +183,52 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
return super.dispatchKeyEvent(event)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ import androidx.activity.viewModels
|
|||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.sw.dualscreen.GlobalData
|
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.adapter.CollectedFoodNewAdapter
|
import com.sw.dualscreen.adapter.CollectedFoodNewAdapter
|
||||||
import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding
|
import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding
|
||||||
@@ -14,10 +13,10 @@ import com.sw.dualscreen.ext.addOnActionSearchListener
|
|||||||
import com.sw.dualscreen.ext.gone
|
import com.sw.dualscreen.ext.gone
|
||||||
import com.sw.dualscreen.ext.hideKeyboard
|
import com.sw.dualscreen.ext.hideKeyboard
|
||||||
import com.sw.dualscreen.ext.visible
|
import com.sw.dualscreen.ext.visible
|
||||||
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||||
import com.sw.dualscreen.objbox.ObjectBox
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
private const val PAGE_SIZE = 100
|
private const val PAGE_SIZE = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel by viewModels<UserViewModel>()
|
private val viewModel by viewModels<NetViewModelV2>()
|
||||||
override fun getViewModel(): BaseViewModel {
|
override fun getViewModel(): BaseViewModel {
|
||||||
return viewModel
|
return viewModel
|
||||||
}
|
}
|
||||||
@@ -36,7 +35,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
return ActivityCollectedFoodBinding.inflate(layoutInflater)
|
return ActivityCollectedFoodBinding.inflate(layoutInflater)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val list: MutableList<CollectedFoodInfo> = mutableListOf()
|
private val list: MutableList<CollectedFoodV2> = mutableListOf()
|
||||||
private val adapter by lazy {
|
private val adapter by lazy {
|
||||||
CollectedFoodNewAdapter(list).apply {
|
CollectedFoodNewAdapter(list).apply {
|
||||||
// isStateViewEnable = true
|
// isStateViewEnable = true
|
||||||
@@ -48,6 +47,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
|
registerKeyEvent()
|
||||||
binding.root.setOnClickListener { it.hideKeyboard() }
|
binding.root.setOnClickListener { it.hideKeyboard() }
|
||||||
binding.rvFoodList.let {
|
binding.rvFoodList.let {
|
||||||
it.layoutManager = LinearLayoutManager(this)
|
it.layoutManager = LinearLayoutManager(this)
|
||||||
@@ -126,19 +126,26 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
// loadEmptyView()
|
// loadEmptyView()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
viewModel.getCollectedFoodList(
|
viewModel.getCollectPage(
|
||||||
pageNo = pageNo,
|
pageNum = pageNo.toLong(),
|
||||||
pageSize = PAGE_SIZE,
|
pageSize = PAGE_SIZE.toLong(),
|
||||||
foodName = searchName ?: ""
|
foodName = searchName,
|
||||||
) { items ->
|
onSuccess = { items ->
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
loadFoodList(items)
|
loadFoodList(items)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
runOnUiThread {
|
||||||
|
finishRefresh()
|
||||||
|
ToastUtils.showToast(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun loadFoodList(items: List<CollectedFoodInfo>) {
|
private fun loadFoodList(items: List<CollectedFoodV2>) {
|
||||||
finishRefresh()
|
finishRefresh()
|
||||||
if (pageNo == 1 && items.isEmpty()) {
|
if (pageNo == 1 && items.isEmpty()) {
|
||||||
list.clear()
|
list.clear()
|
||||||
@@ -190,7 +197,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
private fun deleteGoods(position: Int) {
|
private fun deleteGoods(position: Int) {
|
||||||
val food = list[position]
|
val food = list[position]
|
||||||
showWaitingDialog("加载中……")
|
showWaitingDialog("加载中……")
|
||||||
viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess ->
|
viewModel.deleteCollect(food.foodId, food.version) { deleteSuccess ->
|
||||||
if (deleteSuccess) {
|
if (deleteSuccess) {
|
||||||
// Thread {}.start()
|
// Thread {}.start()
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|||||||
@@ -1,30 +1,35 @@
|
|||||||
package com.sw.dualscreen.activity
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
|
import android.provider.Settings
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
import com.sw.dualscreen.databinding.ActivityInitBinding
|
import com.sw.dualscreen.databinding.ActivityInitBinding
|
||||||
import com.sw.dualscreen.dialog.RemindDialog
|
import com.sw.dualscreen.dialog.RemindDialog
|
||||||
import com.sw.dualscreen.ext.clickWithDebounce
|
import com.sw.dualscreen.ext.clickWithDebounce
|
||||||
|
import com.sw.dualscreen.ext.invisible
|
||||||
import com.sw.dualscreen.ext.visible
|
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.FoodModule
|
||||||
import com.sw.dualscreen.objbox.ObjectBox
|
import com.sw.dualscreen.utils.FoodVectorTool
|
||||||
import com.sw.dualscreen.utils.L
|
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.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
import com.sw.plate.utils.AppUtil
|
import com.sw.plate.utils.AppUtil
|
||||||
|
import com.sw.plate.utils.ToastUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class InitActivity : BaseActivity<ActivityInitBinding>() {
|
class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val PAGE_SIZE = 100
|
const val PAGE_SIZE = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
private val userViewModel by viewModels<UserViewModel>()
|
private val userViewModel by viewModels<NetViewModelV2>()
|
||||||
private var pageNum = 1
|
private var pageNum = 1
|
||||||
|
|
||||||
override fun getViewModel(): BaseViewModel {
|
override fun getViewModel(): BaseViewModel {
|
||||||
@@ -36,55 +41,23 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getCollectedFoodVector(block: () -> Unit) {
|
private fun getCollectedFoodVector(block: () -> Unit) {
|
||||||
userViewModel.getCollectedFoodVector(
|
FoodVectorTool.loadAndSaveFoodVector(
|
||||||
param = mutableMapOf(
|
userViewModel = userViewModel,
|
||||||
"pageNum" to "$pageNum",
|
lifecycleScope = lifecycleScope,
|
||||||
"pageSize" to "$PAGE_SIZE",
|
successBlock = { block() },
|
||||||
"version" to GlobalData.foodModelVersion
|
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 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() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
|
//写死,后续改为从接口获取结算类型 ------------------------------------------
|
||||||
|
//val settlementMode = 1
|
||||||
|
//SPUtil.getInstance().put(GlobalKey.KEY_SETTLEMENT_MODE, settlementMode)
|
||||||
|
|
||||||
|
registerKeyEvent()
|
||||||
initNetworkTimer()
|
initNetworkTimer()
|
||||||
binding.okButton.clickWithDebounce(500) {
|
binding.okButton.clickWithDebounce(500) {
|
||||||
checkNetworkTimer?.cancel()
|
checkNetworkTimer?.cancel()
|
||||||
@@ -92,21 +65,6 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
}
|
}
|
||||||
showWaitingDialog("网络连接中...")
|
showWaitingDialog("网络连接中...")
|
||||||
checkNetworkTimer?.start()
|
checkNetworkTimer?.start()
|
||||||
|
|
||||||
// val isSuccess = viewModel.checkEquipmentInfo()
|
|
||||||
// if (isSuccess) {
|
|
||||||
// goMainActivity()
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// binding.imgQr.setImageBitmap(
|
|
||||||
// QRCodeUtil.generateQRCode(
|
|
||||||
// content = GlobalData.deviceId,
|
|
||||||
// size = 200
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// binding.initButton.setOnClickListener {
|
|
||||||
// viewModel.getDeviceToken()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerDataChange() {
|
override fun registerDataChange() {
|
||||||
@@ -120,20 +78,21 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getCollectedFoodVector() {
|
private fun getCollectedFoodVector() {
|
||||||
|
binding.okButton.invisible()
|
||||||
showWaitingDialog2("加载中……")
|
showWaitingDialog2("加载中……")
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
FoodModule.init(this@InitActivity) {
|
FoodModule.init(this@InitActivity) {
|
||||||
getCollectedFoodVector {
|
getCollectedFoodVector {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
hideWaitingDialog()
|
hideWaitingDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
binding.root.postDelayed({
|
binding.root.postDelayed({
|
||||||
goMainActivity()
|
checkCameraPermissionAndGo()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun goMainActivity() {
|
private fun goMainActivity() {
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
@@ -141,6 +100,57 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hasCameraPermission(): Boolean {
|
||||||
|
return ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.CAMERA
|
||||||
|
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查相机权限
|
||||||
|
private fun checkCameraPermissionAndGo() {
|
||||||
|
val isGrantedCameraPermission = hasCameraPermission()
|
||||||
|
if (isGrantedCameraPermission) {
|
||||||
|
// 有权限,直接打开 MainActivity
|
||||||
|
goMainActivity()
|
||||||
|
} else {
|
||||||
|
// 无权限,请求相机权限
|
||||||
|
requestPermission(Manifest.permission.CAMERA) { isGranted ->
|
||||||
|
checkCameraPermission(isGranted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从设置页面返回后检查权限
|
||||||
|
private fun checkCameraPermission(isGranted: Boolean) {
|
||||||
|
if (isGranted) {
|
||||||
|
// 权限已授予,打开 MainActivity
|
||||||
|
goMainActivity()
|
||||||
|
} else {
|
||||||
|
// 权限仍未授予,再次显示弹窗
|
||||||
|
showPermissionDeniedDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示权限被拒绝的弹窗
|
||||||
|
private fun showPermissionDeniedDialog() {
|
||||||
|
RemindDialog(
|
||||||
|
context = context,
|
||||||
|
content = "暂无相机权限,请授权后重试",
|
||||||
|
confirmBlock = {
|
||||||
|
// 打开 App 设置页面
|
||||||
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
|
data = android.net.Uri.fromParts("package", packageName, null)
|
||||||
|
}
|
||||||
|
startActivity(intent) {
|
||||||
|
// 从设置页面返回,重新检查权限
|
||||||
|
val isGrantedCameraPermission = hasCameraPermission()
|
||||||
|
checkCameraPermission(isGrantedCameraPermission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
private var checkNetworkTimer: CountDownTimer? = null
|
private var checkNetworkTimer: CountDownTimer? = null
|
||||||
|
|
||||||
private fun initNetworkTimer() {
|
private fun initNetworkTimer() {
|
||||||
@@ -157,17 +167,57 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
loadNetworkErrorDialog()
|
loadNetworkErrorDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
getDeviceConfig()
|
||||||
getCollectedFoodVector()
|
getCollectedFoodVector()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// var arcsoftAppId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj"
|
||||||
|
// var arcsoftSdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k"
|
||||||
|
// var arcsoftActiveKey = "085F-118G-Q4GH-B2YH"
|
||||||
|
|
||||||
|
private fun getDeviceConfig() {
|
||||||
|
userViewModel.getDeviceConfig(
|
||||||
|
onSuccess = { deviceConfig ->
|
||||||
|
runOnUiThread {
|
||||||
|
if (deviceConfig == null) {
|
||||||
|
ToastUtils.showToast("获取设备配置数据失败")
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
|
val settlementMode = if (deviceConfig.payType == 2) 0 else 1
|
||||||
|
SpTool.settlementMode = settlementMode
|
||||||
|
|
||||||
|
// // TODO: 临时写死用于测试----------------------------------------
|
||||||
|
// deviceConfig.arcsoftAppId = arcsoftAppId
|
||||||
|
// deviceConfig.arcsoftSdkKey = arcsoftSdkKey
|
||||||
|
// deviceConfig.arcsoftActiveKey = arcsoftActiveKey
|
||||||
|
// // TODO: 临时写死用于测试----------------------------------------
|
||||||
|
|
||||||
|
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
|
||||||
|
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
|
||||||
|
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { runOnUiThread { ToastUtils.showToast(it) } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadNetworkErrorDialog() {
|
private fun loadNetworkErrorDialog() {
|
||||||
RemindDialog(
|
RemindDialog(
|
||||||
context = context,
|
context = context,
|
||||||
content = "网络连接异常,请检查WiFi连接状态后重试",
|
content = "网络连接异常,请检查WiFi连接状态后重试",
|
||||||
confirmBlock = {
|
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()
|
}).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,4 +234,27 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
checkNetworkTimer?.cancel()
|
checkNetworkTimer?.cancel()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// private fun loadTestDb() {
|
||||||
|
// // 从测试库导入人脸数据
|
||||||
|
// FaceDbImporter.importFromAssets(this, object : FaceDbImporter.ImportCallback {
|
||||||
|
// public override fun onProgress(current: Int, total: Int) {
|
||||||
|
// showWaitingDialog("导入人脸数据 " + current + "/" + total)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public override fun onError(message: String?) {
|
||||||
|
// hideWaitingDialog()
|
||||||
|
// ToastUtils.showToast("离线数据导入失败:" + message)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public override fun onComplete() {
|
||||||
|
// hideWaitingDialog()
|
||||||
|
// ToastUtils.showToast("离线数据导入成功")
|
||||||
|
// // 后续流程会自动跳转
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
package com.sw.dualscreen.activity
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.sw.dualscreen.GlobalKey
|
import com.sw.dualscreen.GlobalKey
|
||||||
@@ -15,10 +16,11 @@ import com.sw.dualscreen.model.response.FoodInfo
|
|||||||
import com.sw.dualscreen.model.response.MemberInfo
|
import com.sw.dualscreen.model.response.MemberInfo
|
||||||
import com.sw.dualscreen.model.response.PaySuccessEvent
|
import com.sw.dualscreen.model.response.PaySuccessEvent
|
||||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||||
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
import com.sw.dualscreen.utils.SPUtil
|
import com.sw.dualscreen.utils.SPUtil
|
||||||
import com.sw.dualscreen.utils.SoundPoolUtil
|
import com.sw.dualscreen.utils.SoundPoolUtil
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -41,7 +43,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
const val PAY_SUCCESS = "pay_success"
|
const val PAY_SUCCESS = "pay_success"
|
||||||
}
|
}
|
||||||
|
|
||||||
val userViewModel by viewModels<UserViewModel>()
|
val userViewModel by viewModels<NetViewModelV2>()
|
||||||
val recognizeViewModel by viewModels<RecognizeViewModel>()
|
val recognizeViewModel by viewModels<RecognizeViewModel>()
|
||||||
|
|
||||||
override fun getViewModel(): BaseViewModel {
|
override fun getViewModel(): BaseViewModel {
|
||||||
@@ -63,6 +65,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
|
|
||||||
var foodOrderId: String = ""
|
var foodOrderId: String = ""
|
||||||
var eatNum: Int = 1
|
var eatNum: Int = 1
|
||||||
|
var isMember: Boolean = false
|
||||||
|
|
||||||
val presentation by lazy {
|
val presentation by lazy {
|
||||||
PayPresentation(
|
PayPresentation(
|
||||||
@@ -81,7 +84,8 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
presentation.show()
|
presentation.show()
|
||||||
|
registerKeyEvent()
|
||||||
|
addBackEventListener()
|
||||||
val soundMap = hashMapOf<String, Int>()
|
val soundMap = hashMapOf<String, Int>()
|
||||||
soundMap[PAY_SUCCESS] = R.raw.pay_success
|
soundMap[PAY_SUCCESS] = R.raw.pay_success
|
||||||
SoundPoolUtil.getInstance().loadR(this, soundMap)
|
SoundPoolUtil.getInstance().loadR(this, soundMap)
|
||||||
@@ -95,12 +99,14 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
binding.include.ivPageBack.setOnClickListener { finish() }
|
binding.include.ivPageBack.setOnClickListener { finish() }
|
||||||
binding.include.tvPageTitle.text = "下单结算"
|
binding.include.tvPageTitle.text = "下单结算"
|
||||||
binding.btnPayQrCode.setOnClickListener {
|
binding.btnPayQrCode.setOnClickListener {
|
||||||
|
isMember = false
|
||||||
if (binding.btnPayQrCode.isChecked) {
|
if (binding.btnPayQrCode.isChecked) {
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
showScanQrCodePay()
|
showScanQrCodePay()
|
||||||
}
|
}
|
||||||
binding.btnPayCash.setOnClickListener {
|
binding.btnPayCash.setOnClickListener {
|
||||||
|
isMember = false
|
||||||
if (binding.btnPayCash.isChecked) {
|
if (binding.btnPayCash.isChecked) {
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
@@ -156,10 +162,12 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
presentation?.dismiss()
|
presentation.dismiss()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var memberDiscount = 1.0
|
||||||
|
|
||||||
var memberInfo: MemberInfo? = null
|
var memberInfo: MemberInfo? = null
|
||||||
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
|
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
|
||||||
this.memberInfo = memberInfo
|
this.memberInfo = memberInfo
|
||||||
@@ -171,37 +179,51 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
// display = displays[1],
|
// display = displays[1],
|
||||||
// type = type
|
// type = type
|
||||||
// ) {
|
// ) {
|
||||||
// scanQrCodePayPresentation?.dismiss()
|
// scanQrCodePaypresentation.dismiss()
|
||||||
// }.also {
|
// }.also {
|
||||||
// it.foodName = foodInfo?.foodName
|
// it.foodName = foodInfo?.foodName
|
||||||
// //确认使用vipPrice 还是 specPrice
|
// //确认使用vipPrice 还是 specPrice
|
||||||
// val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
// val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||||
// it.totalPrice = (onePrice ?: 0.0) * eatNum
|
// it.totalPrice = (onePrice ?: 0.0) * eatNum
|
||||||
// }
|
// }
|
||||||
// scanQrCodePayPresentation?.show()
|
// scanQrCodePaypresentation.show()
|
||||||
// }
|
// }
|
||||||
presentation.let {
|
presentation.let {
|
||||||
it.pageType = PayPresentation.QR_CODE_PAY
|
it.pageType = PayPresentation.QR_CODE_PAY
|
||||||
it.type = type
|
it.type = type
|
||||||
it.foodName = foodInfo?.foodName
|
it.foodName = foodInfo?.foodName
|
||||||
//确认使用vipPrice 还是 specPrice
|
//确认使用vipPrice 还是 specPrice
|
||||||
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
//val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||||
it.totalPrice = (onePrice ?: 0.0) * eatNum
|
if (isVip) {
|
||||||
|
userViewModel.getMemberDiscount(memberInfo?.faceUserId?:"") { discount ->
|
||||||
|
memberDiscount = discount ?: 1.0
|
||||||
|
var onePrice = foodInfo?.specPrice ?: 0.0
|
||||||
|
onePrice = if (isVip) onePrice * memberDiscount else onePrice
|
||||||
|
it.totalPrice = onePrice * eatNum
|
||||||
presentation.initView()
|
presentation.initView()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
it.totalPrice = (foodInfo?.specPrice ?: 0.0) * eatNum
|
||||||
|
presentation.initView()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateQrCodeImage(qrCodeUrl: String?) {
|
fun updateQrCodeImage(qrCodeUrl: String?) {
|
||||||
// scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl)
|
// scanQrCodePaypresentation.loadQrCodeImage(qrCodeUrl)
|
||||||
presentation?.loadQrCodeImage(qrCodeUrl)
|
presentation.loadQrCodeImage(qrCodeUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var debouncer = Debouncer(5000)
|
||||||
|
|
||||||
fun showPaySuccess(isVip: Boolean) {
|
fun showPaySuccess(isVip: Boolean) {
|
||||||
|
debouncer.debounce {
|
||||||
playSoundOnPaySuccess()
|
playSoundOnPaySuccess()
|
||||||
EventBus.getDefault().post(PaySuccessEvent())
|
EventBus.getDefault().post(PaySuccessEvent())
|
||||||
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
|
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
|
||||||
hidePayTab()
|
hidePayTab()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getQrCodeImg(
|
fun getQrCodeImg(
|
||||||
orderId: String,
|
orderId: String,
|
||||||
@@ -232,7 +254,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun memberPay(param: HashMap<String, String>, block: () -> Unit) {
|
fun memberPay(param: HashMap<String, String?>, block: () -> Unit) {
|
||||||
userViewModel.memberPay(param) {
|
userViewModel.memberPay(param) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (it.not()) {
|
if (it.not()) {
|
||||||
@@ -277,21 +299,29 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun bindOrder(userId: String, block: (Boolean) -> Unit) {
|
fun bindOrder(userId: String, block: (Boolean) -> Unit) {
|
||||||
|
//0-即放即取,1-余量计量
|
||||||
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
||||||
val mode = if (pickupMode == 1) 1 else 2
|
val mode = if (pickupMode == 1) 1 else 2
|
||||||
userViewModel.bindOrder(userId, foodOrderId, mode = mode) { bindResult ->
|
val uid = userId.toLongOrNull() ?: run {
|
||||||
runOnUiThread {
|
runOnUiThread { block(false) }
|
||||||
block(bindResult)
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
userViewModel.bindUserOrder(
|
||||||
|
userId = uid,
|
||||||
|
orderNo = foodOrderId,
|
||||||
|
mode = mode,
|
||||||
|
onSuccess = { runOnUiThread { block(true) } },
|
||||||
|
onFailure = { runOnUiThread { block(false) } }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun scanGunKeyEventCallback(scanInfo: String) {
|
fun qrCodePay(scanInfo: String) {
|
||||||
super.scanGunKeyEventCallback(scanInfo)
|
showWaitingDialog("支付中……")
|
||||||
userViewModel.qrCodePay(scanInfo, foodOrderId) { payState, backData ->
|
userViewModel.qrCodePay(scanInfo, foodOrderId, memberInfo?.id) { payState, backData ->
|
||||||
Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData")
|
Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData")
|
||||||
|
hideWaitingDialog()
|
||||||
if (payState) {
|
if (payState) {
|
||||||
showPaySuccess(false)
|
showPaySuccess(isMember)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,4 +370,12 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
fun addBackEventListener() {
|
||||||
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,12 +11,12 @@ import com.sw.dualscreen.model.response.FoodInfo
|
|||||||
import com.sw.dualscreen.model.response.ClickBackEvent
|
import com.sw.dualscreen.model.response.ClickBackEvent
|
||||||
import com.sw.dualscreen.model.response.UpdateRefreshEvent
|
import com.sw.dualscreen.model.response.UpdateRefreshEvent
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
|
||||||
class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
||||||
|
|
||||||
val viewModel by viewModels<UserViewModel>()
|
val viewModel by viewModels<NetViewModelV2>()
|
||||||
override fun getViewModel(): BaseViewModel {
|
override fun getViewModel(): BaseViewModel {
|
||||||
return viewModel
|
return viewModel
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@ class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
|||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
|
registerKeyEvent()
|
||||||
initBackDispatcher()
|
initBackDispatcher()
|
||||||
binding.include.let {
|
binding.include.let {
|
||||||
it.ivPageBack.setOnClickListener {
|
it.ivPageBack.setOnClickListener {
|
||||||
|
|||||||
@@ -6,32 +6,43 @@ import com.sw.dualscreen.R
|
|||||||
import com.sw.dualscreen.activity.SettingActivity
|
import com.sw.dualscreen.activity.SettingActivity
|
||||||
import com.sw.dualscreen.databinding.FragmentBusinessBinding
|
import com.sw.dualscreen.databinding.FragmentBusinessBinding
|
||||||
import com.sw.dualscreen.model.response.ChargeModeEvent
|
import com.sw.dualscreen.model.response.ChargeModeEvent
|
||||||
|
import com.sw.dualscreen.model.response.ResetRecognizeEvent
|
||||||
import com.sw.dualscreen.sdk.SensorScaleUtils
|
import com.sw.dualscreen.sdk.SensorScaleUtils
|
||||||
import com.sw.dualscreen.utils.SPUtil
|
import com.sw.dualscreen.utils.SPUtil
|
||||||
|
import com.sw.dualscreen.utils.SpTool
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
||||||
|
|
||||||
|
// 当前秤的实时读数(克),作为待保存的餐具重量
|
||||||
|
private var dishWeight: Int = 0
|
||||||
|
|
||||||
override fun inflateViewBinding(): FragmentBusinessBinding {
|
override fun inflateViewBinding(): FragmentBusinessBinding {
|
||||||
return FragmentBusinessBinding.inflate(LayoutInflater.from(context))
|
return FragmentBusinessBinding.inflate(LayoutInflater.from(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
|
// 初始化为已保存的餐具重量,未配置过则为 0
|
||||||
|
dishWeight = SPUtil.getInstance().get(GlobalKey.KEY_DISH_WEIGHT, 0) ?: 0
|
||||||
|
// 在标题后显示当前已保存的餐具重量
|
||||||
|
refreshDishWeightLabel()
|
||||||
|
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 {
|
binding.rgCharge.let {
|
||||||
// it.setOnCheckedChangeListener { group, checkedId ->
|
//0-计费,1-不计费
|
||||||
// val mode = if (checkedId == R.id.rbChargeYes) 0 else 1
|
|
||||||
// SPUtil.getInstance().put(GlobalKey.KEY_CHARGE_MODE, mode)
|
|
||||||
// }
|
|
||||||
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||||
it.check(if (mode == 0) R.id.rbChargeYes else R.id.rbChargeNot)
|
it.check(if (mode == 0) R.id.rbChargeYes else R.id.rbChargeNot)
|
||||||
}
|
}
|
||||||
binding.rgTakeFood.let {
|
binding.rgTakeFood.let {
|
||||||
// it.setOnCheckedChangeListener { group, checkedId ->
|
//0-即放即取,1-余量计量
|
||||||
// val mode = if (checkedId == R.id.rbPickAndPlace) 0 else 1
|
|
||||||
// SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode)
|
|
||||||
// }
|
|
||||||
val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
||||||
it.check(if (mode == 0) R.id.rbPickAndPlace else R.id.rbSurplusCalculate)
|
it.check(if (mode == 0) R.id.rbPickAndPlace else R.id.rbSurplusCalculate)
|
||||||
}
|
}
|
||||||
@@ -39,6 +50,10 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
|||||||
SensorScaleUtils.tare()
|
SensorScaleUtils.tare()
|
||||||
}
|
}
|
||||||
binding.btnConfirm.setOnClickListener { v ->
|
binding.btnConfirm.setOnClickListener { v ->
|
||||||
|
// if (binding.rgSettlement.checkedRadioButtonId == -1) {
|
||||||
|
// ToastUtils.showToast("请设置结算模式")
|
||||||
|
// return@setOnClickListener
|
||||||
|
// }
|
||||||
if (binding.rgCharge.checkedRadioButtonId == -1) {
|
if (binding.rgCharge.checkedRadioButtonId == -1) {
|
||||||
ToastUtils.showToast("请设置计费模式")
|
ToastUtils.showToast("请设置计费模式")
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
@@ -50,6 +65,7 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
|||||||
|
|
||||||
val chargeMode = if (binding.rgCharge.checkedRadioButtonId == R.id.rbChargeYes) 0 else 1
|
val chargeMode = if (binding.rgCharge.checkedRadioButtonId == R.id.rbChargeYes) 0 else 1
|
||||||
//原来的支付模式
|
//原来的支付模式
|
||||||
|
//0-计费,1-不计费
|
||||||
val oldChargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
val oldChargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||||
if (chargeMode != oldChargeMode) {
|
if (chargeMode != oldChargeMode) {
|
||||||
//支付模式有变化通知首页页面刷新
|
//支付模式有变化通知首页页面刷新
|
||||||
@@ -59,7 +75,11 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
|||||||
|
|
||||||
val pickupMode = if (binding.rgTakeFood.checkedRadioButtonId == R.id.rbPickAndPlace) 0 else 1
|
val pickupMode = if (binding.rgTakeFood.checkedRadioButtonId == R.id.rbPickAndPlace) 0 else 1
|
||||||
SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, pickupMode)
|
SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, pickupMode)
|
||||||
|
// 持久化餐具重量(克)
|
||||||
|
SPUtil.getInstance().put(GlobalKey.KEY_DISH_WEIGHT, dishWeight)
|
||||||
|
// 刷新标签文字,显示当前已保存的餐具重量
|
||||||
|
refreshDishWeightLabel()
|
||||||
|
EventBus.getDefault().post(ResetRecognizeEvent())
|
||||||
//ToastUtils.showToast("设置已保存")
|
//ToastUtils.showToast("设置已保存")
|
||||||
(activity as SettingActivity).let {
|
(activity as SettingActivity).let {
|
||||||
it.showWaitingDialog("设置保存中")
|
it.showWaitingDialog("设置保存中")
|
||||||
@@ -70,8 +90,18 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SensorScaleUtils.addWeightListener { value ->
|
SensorScaleUtils.addWeightListener { value ->
|
||||||
val realWeight = (value * 1000).roundToInt()
|
//val realWeight = (value * 1000).roundToInt()
|
||||||
binding.tvFoodWeight.text = "$realWeight"
|
// 追踪秤的实时读数(克),作为待保存的餐具重量
|
||||||
|
dishWeight = value
|
||||||
|
binding.tvFoodWeight.text = "$value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新"餐具重量(克)"标签,在末尾显示当前已保存的配置值。
|
||||||
|
* 未配置(0)时只显示原标题。
|
||||||
|
*/
|
||||||
|
private fun refreshDishWeightLabel() {
|
||||||
|
binding.tvDishWeightLabel.text = if (dishWeight > 0) "餐具重量(克):${dishWeight}g" else "餐具重量(克)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -34,10 +34,9 @@ import com.sw.dualscreen.utils.Debouncer
|
|||||||
import com.sw.dualscreen.utils.ImageUploader
|
import com.sw.dualscreen.utils.ImageUploader
|
||||||
import com.sw.dualscreen.utils.ImageUtil
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.RequestBody
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@@ -101,13 +100,14 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
|
|
||||||
private val cameraCallback: (Uri) -> Unit = { uri ->
|
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||||
try {
|
try {
|
||||||
val index = foodCollectionList.indexOfFirst { it.imageFile == null }
|
activity?.lifecycleScope?.launch(Dispatchers.Main) {
|
||||||
|
// 主线程原子操作:查找空闲槽位并立即标记,避免竞态
|
||||||
|
val index = foodCollectionList.indexOfFirst { it.isShowCamera }
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||||
settingActivity?.hideWaitingDialog()
|
settingActivity?.hideWaitingDialog()
|
||||||
rerurn@ cameraCallback
|
return@launch
|
||||||
}
|
}
|
||||||
activity?.runOnUiThread {
|
|
||||||
foodCollectionList[index].let {
|
foodCollectionList[index].let {
|
||||||
it.imageVector = null
|
it.imageVector = null
|
||||||
it.bitmap = null
|
it.bitmap = null
|
||||||
@@ -116,13 +116,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
it.imageUri = uri
|
it.imageUri = uri
|
||||||
}
|
}
|
||||||
collectionAdapter.notifyItemChanged(index)
|
collectionAdapter.notifyItemChanged(index)
|
||||||
}
|
// IO线程处理bitmap
|
||||||
// settingActivity?.hideWaitingDialog()
|
launch(Dispatchers.IO) {
|
||||||
Thread {
|
|
||||||
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
|
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
|
||||||
getImageVector(index, bitmap)
|
getImageVector(index, bitmap)
|
||||||
}
|
}
|
||||||
}.start()
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
settingActivity?.hideWaitingDialog()
|
settingActivity?.hideWaitingDialog()
|
||||||
@@ -131,6 +131,10 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val photoFailCallback: (String) -> Unit = { errMsg ->
|
||||||
|
settingActivity?.hideWaitingDialog()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getImageVector(index: Int, bitmap: Bitmap) {
|
private fun getImageVector(index: Int, bitmap: Bitmap) {
|
||||||
// val bitmap = BitmapCropper.cropCenter(
|
// val bitmap = BitmapCropper.cropCenter(
|
||||||
// original = srcBmp,
|
// original = srcBmp,
|
||||||
@@ -141,7 +145,9 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
FoodModule.bitmap2FloatArray(bitmap, false)
|
FoodModule.bitmap2FloatArray(bitmap, false)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
activity?.lifecycleScope?.launch(Dispatchers.Main) {
|
||||||
ToastUtils.showToast("操作失败")
|
ToastUtils.showToast("操作失败")
|
||||||
|
}
|
||||||
settingActivity?.log("操作失败:${e.message}")
|
settingActivity?.log("操作失败:${e.message}")
|
||||||
settingActivity?.hideWaitingDialog()
|
settingActivity?.hideWaitingDialog()
|
||||||
return
|
return
|
||||||
@@ -151,19 +157,16 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
)
|
)
|
||||||
settingActivity?.log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
settingActivity?.log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||||
|
|
||||||
activity?.runOnUiThread {
|
|
||||||
foodCollectionList[index].let {
|
foodCollectionList[index].let {
|
||||||
it.imageVector = imageVector
|
it.imageVector = imageVector
|
||||||
it.bitmap = null
|
it.bitmap = null
|
||||||
it.isShowCamera = false
|
it.isShowCamera = false
|
||||||
it.imageFile = file
|
it.imageFile = file
|
||||||
}
|
}
|
||||||
collectionAdapter.notifyItemChanged(index)
|
settingActivity?.hideWaitingDialog()
|
||||||
}
|
|
||||||
if (bitmap.isRecycled.not()) {
|
if (bitmap.isRecycled.not()) {
|
||||||
bitmap.recycle()
|
bitmap.recycle()
|
||||||
}
|
}
|
||||||
settingActivity?.hideWaitingDialog()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@@ -173,19 +176,9 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settingActivity?.showWaitingDialog("采集中……")
|
settingActivity?.showWaitingDialog("采集中……")
|
||||||
cameraUtils.takePhoto(cameraCallback) { errMsg ->
|
cameraUtils.takePhoto(succCallback = cameraCallback, failCallback = photoFailCallback)
|
||||||
settingActivity?.hideWaitingDialog()
|
|
||||||
// ToastUtils.showToast("拍照异常,请重新操作")
|
|
||||||
// if (cameraErrorCount >= 10) {
|
|
||||||
// ToastUtils.showToast("拍照异常,请重新操作")
|
|
||||||
// settingActivity?.log("拍照异常,请重新操作:$errMsg")
|
|
||||||
// cameraErrorCount = 0
|
|
||||||
// return@takePhoto
|
|
||||||
// }
|
|
||||||
// cameraErrorCount++
|
|
||||||
// takePhoto()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cameraErrorCount = 0
|
private var cameraErrorCount = 0
|
||||||
@@ -263,14 +256,15 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val totalFileCount = foodCollectionList.count { it.imageFile != null }
|
val totalFileCount = foodCollectionList.count { it.imageFile != null }
|
||||||
settingActivity?.showWaitingDialog2("图片上传中0/$totalFileCount")
|
settingActivity?.showWaitingDialog2("图片上传中0/$totalFileCount")
|
||||||
val params = HashMap<String, RequestBody>()
|
val foodId = checkedItem!!.foodId.toLongOrNull() ?: run {
|
||||||
//params["placeId"] = restId.toRequestBody()
|
ToastUtils.showToast("foodId 格式错误")
|
||||||
params["foodId"] = checkedItem!!.foodId.toRequestBody()
|
return@launch
|
||||||
params["foodName"] = checkedItem!!.foodName!!.toRequestBody()
|
}
|
||||||
params["version"] = GlobalData.foodModelVersion.toRequestBody()
|
val foodName = checkedItem!!.foodName ?: ""
|
||||||
|
val version = GlobalData.foodModelVersion
|
||||||
|
|
||||||
ImageUploader(totalList = foodCollectionList, uploadImage = { batch ->
|
ImageUploader(totalList = foodCollectionList, uploadImage = { batch ->
|
||||||
val files = batch.map { it.imageFile }
|
val files = batch.mapNotNull { it.imageFile }
|
||||||
val foodVectorList = batch.filter { it.imageVector != null }.map {
|
val foodVectorList = batch.filter { it.imageVector != null }.map {
|
||||||
it.imageVector!!.joinToString(
|
it.imageVector!!.joinToString(
|
||||||
separator = ",", prefix = "[", postfix = "]"
|
separator = ",", prefix = "[", postfix = "]"
|
||||||
@@ -279,8 +273,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
val foodVectorJson =
|
val foodVectorJson =
|
||||||
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||||
Timber.tag(TAG).d("json=$foodVectorJson")
|
Timber.tag(TAG).d("json=$foodVectorJson")
|
||||||
params["foodVector"] = foodVectorJson.toRequestBody()
|
settingActivity?.viewModel?.uploadCollect(
|
||||||
settingActivity?.viewModel?.uploadCollectFoodPics(files, params)
|
foodId = foodId,
|
||||||
|
foodName = foodName,
|
||||||
|
version = version,
|
||||||
|
foodVector = foodVectorJson,
|
||||||
|
fileList = files
|
||||||
|
)
|
||||||
}, onProgress = { count, batch, idList ->
|
}, onProgress = { count, batch, idList ->
|
||||||
//batch.forEach {
|
//batch.forEach {
|
||||||
// it.uploadSuccess = true
|
// it.uploadSuccess = true
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
|||||||
// }
|
// }
|
||||||
payActivity.foodInfo?.let {
|
payActivity.foodInfo?.let {
|
||||||
foodName = it.foodName
|
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()
|
showSubScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.sw.dualscreen.activity.fragment.pay
|
package com.sw.dualscreen.activity.fragment.pay
|
||||||
|
|
||||||
|
import android.text.InputType
|
||||||
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.activity.PayActivity
|
import com.sw.dualscreen.activity.PayActivity
|
||||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||||
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
|
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
|
||||||
@@ -13,6 +15,8 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
|||||||
private lateinit var payActivity: PayActivity
|
private lateinit var payActivity: PayActivity
|
||||||
private var foodName: String? = null
|
private var foodName: String? = null
|
||||||
private var payAmount: Double? = null
|
private var payAmount: Double? = null
|
||||||
|
// 密码是否显示的标志
|
||||||
|
private var isPasswordVisible = false
|
||||||
override fun inflateViewBinding(): FragmentNumberPayBinding {
|
override fun inflateViewBinding(): FragmentNumberPayBinding {
|
||||||
return FragmentNumberPayBinding.inflate(layoutInflater)
|
return FragmentNumberPayBinding.inflate(layoutInflater)
|
||||||
}
|
}
|
||||||
@@ -27,6 +31,12 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
|||||||
foodName = it.foodName
|
foodName = it.foodName
|
||||||
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 密码显示/隐藏切换
|
||||||
|
binding.ivTogglePassword.setOnClickListener {
|
||||||
|
togglePasswordVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
binding.btnConfirm.setOnClickListener { v ->
|
binding.btnConfirm.setOnClickListener { v ->
|
||||||
val phone = binding.etInputPhone.text.toString().trim()
|
val phone = binding.etInputPhone.text.toString().trim()
|
||||||
val key = binding.etInputNumber.text.toString().trim()
|
val key = binding.etInputNumber.text.toString().trim()
|
||||||
@@ -41,7 +51,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
|||||||
// ToastUtils.showToast("会员验证成功")
|
// ToastUtils.showToast("会员验证成功")
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
payActivity.isMember = false
|
||||||
payActivity.getMemberInfoByPhone(phone, key) { memberInfo ->
|
payActivity.getMemberInfoByPhone(phone, key) { memberInfo ->
|
||||||
if (memberInfo == null) {
|
if (memberInfo == null) {
|
||||||
binding.root.postDelayed({
|
binding.root.postDelayed({
|
||||||
@@ -52,6 +62,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
|||||||
}
|
}
|
||||||
//肯定是会员
|
//肯定是会员
|
||||||
memberInfo.member = true
|
memberInfo.member = true
|
||||||
|
payActivity.isMember = true
|
||||||
//绑定订单使用faceUserId
|
//绑定订单使用faceUserId
|
||||||
payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
|
payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
|
||||||
if (bindResult.not()) {
|
if (bindResult.not()) {
|
||||||
@@ -126,6 +137,30 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换密码显示/隐藏
|
||||||
|
private fun togglePasswordVisibility() {
|
||||||
|
val editText = binding.etInputNumber
|
||||||
|
val imageView = binding.ivTogglePassword
|
||||||
|
|
||||||
|
// 保存当前光标位置
|
||||||
|
val cursorPosition = editText.selectionStart
|
||||||
|
|
||||||
|
if (isPasswordVisible) {
|
||||||
|
// 当前显示,改为隐藏
|
||||||
|
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
|
imageView.setImageResource(R.drawable.ic_eye_close)
|
||||||
|
isPasswordVisible = false
|
||||||
|
} else {
|
||||||
|
// 当前隐藏,改为显示
|
||||||
|
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||||
|
imageView.setImageResource(R.drawable.ic_eye_open)
|
||||||
|
isPasswordVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复光标位置
|
||||||
|
editText.setSelection(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
// private fun bindOrder(userId: String, block: () -> Unit) {
|
// private fun bindOrder(userId: String, block: () -> Unit) {
|
||||||
// payActivity.bindOrder(userId) { bindResult ->
|
// payActivity.bindOrder(userId) { bindResult ->
|
||||||
// if (bindResult.not()) {
|
// if (bindResult.not()) {
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (memberInfo != null && memberInfo!!.member) {
|
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()
|
binding.layoutVip.visible()
|
||||||
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
|
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
|
||||||
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||||
@@ -289,7 +291,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
|||||||
//订单号
|
//订单号
|
||||||
"orderNo" to payActivity.foodOrderId,
|
"orderNo" to payActivity.foodOrderId,
|
||||||
//用户id
|
//用户id
|
||||||
"memberId" to memberInfo!!.id!!
|
"memberId" to memberInfo?.id
|
||||||
),
|
),
|
||||||
block = callback
|
block = callback
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ import androidx.core.graphics.toColorInt
|
|||||||
import com.chad.library.adapter4.BaseQuickAdapter
|
import com.chad.library.adapter4.BaseQuickAdapter
|
||||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
import com.sw.dualscreen.R
|
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.ext.format2String
|
||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
|
|
||||||
class CollectFoodListAdapter(var list: MutableList<FoodInfo>) :
|
class CollectFoodListAdapter(var list: MutableList<FoodInfo>) :
|
||||||
BaseQuickAdapter<FoodInfo, CollectFoodListAdapter.VH>(list) {
|
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 {
|
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
val binding = ListItemSearchFoodBinding.inflate(inflater, parent, false)
|
val binding = ListItemSearchFood3Binding.inflate(inflater, parent, false)
|
||||||
return VH(binding)
|
return VH(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import com.chad.library.adapter4.viewholder.QuickViewHolder
|
|||||||
import com.sw.dualscreen.databinding.ListItemCollectedFoodBinding
|
import com.sw.dualscreen.databinding.ListItemCollectedFoodBinding
|
||||||
import com.sw.dualscreen.ext.gone
|
import com.sw.dualscreen.ext.gone
|
||||||
import com.sw.dualscreen.ext.visible
|
import com.sw.dualscreen.ext.visible
|
||||||
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||||
|
|
||||||
class CollectedFoodNewAdapter(var list: MutableList<CollectedFoodInfo>) :
|
class CollectedFoodNewAdapter(var list: MutableList<CollectedFoodV2>) :
|
||||||
BaseQuickAdapter<CollectedFoodInfo, CollectedFoodNewAdapter.VH>(list) {
|
BaseQuickAdapter<CollectedFoodV2, CollectedFoodNewAdapter.VH>(list) {
|
||||||
|
|
||||||
inner class VH(var binding: ListItemCollectedFoodBinding) : QuickViewHolder(binding.root)
|
inner class VH(var binding: ListItemCollectedFoodBinding) : QuickViewHolder(binding.root)
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class CollectedFoodNewAdapter(var list: MutableList<CollectedFoodInfo>) :
|
|||||||
return VH(binding)
|
return VH(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int, item: CollectedFoodInfo?) {
|
override fun onBindViewHolder(holder: VH, position: Int, item: CollectedFoodV2?) {
|
||||||
holder.binding.tvFoodName.text = item?.foodName
|
holder.binding.tvFoodName.text = item?.foodName
|
||||||
holder.binding.tvCollectedNum.text = "已采集${item?.foodCount}"
|
holder.binding.tvCollectedNum.text = "已采集${item?.foodCount}"
|
||||||
holder.binding.divider.run {
|
holder.binding.divider.run {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
|
|||||||
//setImageURI(it.imageUri)
|
//setImageURI(it.imageUri)
|
||||||
//setImageBitmap(it.bitmap)
|
//setImageBitmap(it.bitmap)
|
||||||
if (it.imageUri!=null) {
|
if (it.imageUri!=null) {
|
||||||
setImageURI(it.imageUri)
|
load(it.imageUri)
|
||||||
} else {
|
} else {
|
||||||
load(it.imageFile)
|
load(it.imageFile)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
// 营养秤结算:规格栏显示规格重量 specWeight
|
||||||
|
binding.tvPriceNorm.text = "${it.specWeight}克"
|
||||||
|
}
|
||||||
|
//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.LOCAL_BASE_URL -> binding.rbTest.isChecked = true
|
||||||
|
GlobalData.TEST_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.LOCAL_BASE_URL
|
||||||
|
binding.rbUat.id -> GlobalData.TEST_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.ImageFormat
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.YuvImage
|
import android.graphics.YuvImage
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
|
import android.view.TouchDelegate
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
@@ -159,3 +162,35 @@ fun View.invisible() {
|
|||||||
fun View.gone() {
|
fun View.gone() {
|
||||||
visibility = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.sw.dualscreen.model.request.v2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统绑定用户与订单请求参数
|
||||||
|
* 对应接口:/neglect/booth/order/bind-user
|
||||||
|
*/
|
||||||
|
data class BindUserOrderRequest(
|
||||||
|
val userId: Long,
|
||||||
|
val orderNo: String,
|
||||||
|
val mode: Int? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.sw.dualscreen.model.request.v2
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统开餐下单请求参数
|
||||||
|
* 对应接口:/neglect/booth/order/place
|
||||||
|
*/
|
||||||
|
data class PlaceOrderRequest(
|
||||||
|
val foodId: Long,
|
||||||
|
val foodName: String,
|
||||||
|
val foodWeight: BigDecimal,
|
||||||
|
val eatWeight: BigDecimal,
|
||||||
|
val eatNum: Int,
|
||||||
|
val notPay: Boolean,
|
||||||
|
val mode: Int,
|
||||||
|
val specId: Long?,
|
||||||
|
val userId: Long?,
|
||||||
|
val paymentFrom: Int? = null,
|
||||||
|
val foodMaterialId: Long? = null,
|
||||||
|
val member: Boolean? = null,
|
||||||
|
val remark: String? = null,
|
||||||
|
val deviceId: String? = null,
|
||||||
|
val cookOrderId: String? = null
|
||||||
|
)
|
||||||
@@ -34,7 +34,11 @@ data class FoodOrder(
|
|||||||
var remark: String? = "",
|
var remark: String? = "",
|
||||||
|
|
||||||
//即放即取-1,称重-2
|
//即放即取-1,称重-2
|
||||||
var mode: Int
|
var mode: Int,
|
||||||
|
//设备模式,1 结算终端,2档口机
|
||||||
|
var paymentFrom: Int,
|
||||||
|
//是否会员
|
||||||
|
var member: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -96,12 +100,14 @@ data class UserNutrition(
|
|||||||
val fruitsVegetablesNearExcess: String? = null,
|
val fruitsVegetablesNearExcess: String? = null,
|
||||||
//果蔬超量
|
//果蔬超量
|
||||||
val fruitsVegetablesExcess: String? = null,
|
val fruitsVegetablesExcess: String? = null,
|
||||||
//肉蛋推荐值
|
//肉蛋豆推荐值
|
||||||
val meatEggsRecommend: String? = null,
|
val meatEggsBeansRecommend: String? = null,
|
||||||
//肉蛋即将超量
|
//肉蛋豆即将超量
|
||||||
val meatEggsNearExcess: String? = null,
|
val meatEggsBeansNearExcess: String? = null,
|
||||||
//肉蛋超量
|
//肉蛋豆超量
|
||||||
val meatEggsExcess: 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}
|
//{"code":"00000","data":"{\"orderType\":0,\"orderPayFrom\":2,\"payType\":\"1\",\"paySuc\":0,\"totalFee\":0.01,\"orderCode\":\"1998193614899372032\",\"message\":\"支付成功\",\"payCode\":\"1998193614899372032508227\"}","msg":"成功","total":0}
|
||||||
@@ -123,7 +129,7 @@ data class RespCodeMsg(
|
|||||||
|
|
||||||
|
|
||||||
data class PostEvent(var name: String = "")
|
data class PostEvent(var name: String = "")
|
||||||
data class UpdateRefreshEvent(var name:String = "")
|
data class UpdateRefreshEvent(var name: String = "")
|
||||||
data class PaySuccessEvent(
|
data class PaySuccessEvent(
|
||||||
var name: String = ""
|
var name: String = ""
|
||||||
)
|
)
|
||||||
@@ -131,6 +137,9 @@ data class PaySuccessEvent(
|
|||||||
data class ChargeModeEvent(
|
data class ChargeModeEvent(
|
||||||
var chargeMode: Int = 0
|
var chargeMode: Int = 0
|
||||||
)
|
)
|
||||||
|
data class ResetRecognizeEvent(
|
||||||
|
var state: Int = 0
|
||||||
|
)
|
||||||
|
|
||||||
data class ClickBackEvent(
|
data class ClickBackEvent(
|
||||||
var name: String = ""
|
var name: String = ""
|
||||||
@@ -139,7 +148,9 @@ data class ClickBackEvent(
|
|||||||
data class DeviceConfig(
|
data class DeviceConfig(
|
||||||
var arcsoftAppId: String? = null,
|
var arcsoftAppId: String? = null,
|
||||||
var arcsoftSdkKey: String? = null,
|
var arcsoftSdkKey: String? = null,
|
||||||
var arcsoftActiveKey: String? = null
|
var arcsoftActiveKey: String? = null,
|
||||||
|
//结算模式:1-独立支付模式,2-联合结算模式
|
||||||
|
var payType: Int = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
data class FoodVector(
|
data class FoodVector(
|
||||||
@@ -159,3 +170,32 @@ data class WeightRecord(
|
|||||||
var lastWeight: Int = 0,
|
var lastWeight: Int = 0,
|
||||||
var currentWeight: 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
|
||||||
|
)
|
||||||
@@ -43,6 +43,8 @@ data class FoodInfo(
|
|||||||
val recommendCalorie: Double? = null,
|
val recommendCalorie: Double? = null,
|
||||||
//图片
|
//图片
|
||||||
var foodImg: String? = "",
|
var foodImg: String? = "",
|
||||||
|
//按单烹制 ID(菜品列表返回,就餐时原样回传,用于溯源码追踪)
|
||||||
|
var cookOrderId: String? = "",
|
||||||
|
|
||||||
//-----------------------------------
|
//-----------------------------------
|
||||||
var score: Int = 0,
|
var score: Int = 0,
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.sw.dualscreen.model.response.v2
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统采集菜品信息
|
||||||
|
* 对应接口:/neglect/booth/collect/page
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class CollectedFoodV2(
|
||||||
|
val foodId: String?,
|
||||||
|
val foodName: String?,
|
||||||
|
val version: String?,
|
||||||
|
val foodVector: String?,
|
||||||
|
val picUrls: List<String>?,
|
||||||
|
/** 已采集数量(客户端手动赋值,非接口字段,用于兼容旧版 UI) */
|
||||||
|
var foodCount: Int = 0
|
||||||
|
) : Parcelable
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.sw.dualscreen.model.response.v2
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统人脸数据 VO
|
||||||
|
* 对应接口:/nutrition/neglect/common/face/page 和 /nutrition/neglect/common/face/increment
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class FaceVO(
|
||||||
|
val userFaceId: String?,
|
||||||
|
val userId: String?,
|
||||||
|
val faceFeature: String?,
|
||||||
|
/** 后端返回 String 类型(防止 JS 大数精度丢失),映射时自行转为 Long */
|
||||||
|
val faceUpdateTimestamp: String?,
|
||||||
|
val cardNo: String?,
|
||||||
|
val member: Boolean?,
|
||||||
|
val faceDeleted: Boolean?,
|
||||||
|
/** 人员类型:由后端定义,如 "1"-会员、"2"-临时用户、"3"-其他等 */
|
||||||
|
val personType: String?
|
||||||
|
) : Parcelable
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.sw.dualscreen.model.response.v2
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统菜品信息
|
||||||
|
* 对应接口:/neglect/booth/food/by-names
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class NewFoodInfo(
|
||||||
|
val foodId: Long,
|
||||||
|
val foodName: String?,
|
||||||
|
val calorie: BigDecimal?,
|
||||||
|
val protein: BigDecimal?,
|
||||||
|
val fat: BigDecimal?,
|
||||||
|
val carbohydrate: BigDecimal?,
|
||||||
|
val price: BigDecimal?,
|
||||||
|
val specPrice: BigDecimal?,
|
||||||
|
val specWeight: BigDecimal?,
|
||||||
|
val stapleFood: BigDecimal?,
|
||||||
|
val fruitsVegetables: BigDecimal?,
|
||||||
|
val meatEggs: BigDecimal?,
|
||||||
|
val foodMaterialId: Long?,
|
||||||
|
val specId: Long?,
|
||||||
|
val foodImg: String?,
|
||||||
|
val foodLabel: String?,
|
||||||
|
val vipPrice: BigDecimal? = null,
|
||||||
|
val recommendCalorie: Int = 0,
|
||||||
|
val tablewareStatus: Boolean = false,
|
||||||
|
val tablewareWeight: Int = 0,
|
||||||
|
//按单烹制 ID(菜品列表返回,就餐时原样回传,用于溯源码追踪)
|
||||||
|
var cookOrderId: String? = "",
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
/** NewFoodInfo → FoodInfo 映射(V2 数据模型兼容旧版 UI) */
|
||||||
|
fun NewFoodInfo.toFoodInfo() = FoodInfo(
|
||||||
|
foodId = foodId.toString(),
|
||||||
|
foodName = foodName,
|
||||||
|
calorie = calorie?.toDouble(),
|
||||||
|
protein = protein?.toDouble(),
|
||||||
|
fat = fat?.toDouble(),
|
||||||
|
carbohydrate = carbohydrate?.toDouble(),
|
||||||
|
specPrice = specPrice?.toDouble(),
|
||||||
|
vipPrice = vipPrice?.toDouble(),
|
||||||
|
foodMaterialId = foodMaterialId?.toString(),
|
||||||
|
specId = specId?.toString(),
|
||||||
|
specWeight = specWeight?.toDouble(),
|
||||||
|
stapleFood = stapleFood?.toDouble(),
|
||||||
|
fruitsVegetables = fruitsVegetables?.toDouble(),
|
||||||
|
meatEggs = meatEggs?.toDouble(),
|
||||||
|
recommendCalorie = recommendCalorie.toDouble(),
|
||||||
|
foodImg = foodImg,
|
||||||
|
cookOrderId = cookOrderId,
|
||||||
|
)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.sw.dualscreen.model.response.v2
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import com.sw.dualscreen.model.response.MemberInfo
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统会员信息
|
||||||
|
* 对应接口:/neglect/booth/member/info 和 /neglect/booth/member/info-by-phone
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class NewMemberInfo(
|
||||||
|
val id: String?,
|
||||||
|
val phone: String?,
|
||||||
|
val name: String?,
|
||||||
|
val faceUrl: String?,
|
||||||
|
val topUpBalance: BigDecimal?,
|
||||||
|
val rewardBalance: BigDecimal? = BigDecimal.ZERO,
|
||||||
|
val integralBalance: Int?,
|
||||||
|
val member: Boolean?
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
/** NewMemberInfo → MemberInfo 映射(V2 数据模型兼容旧版 UI,faceUserId 复用 id) */
|
||||||
|
fun NewMemberInfo.toMemberInfo() = MemberInfo(
|
||||||
|
id = id,
|
||||||
|
faceUserId = id,
|
||||||
|
phone = phone,
|
||||||
|
name = name,
|
||||||
|
faceUrl = faceUrl,
|
||||||
|
topUpBalance = topUpBalance?.toDouble(),
|
||||||
|
rewardBalance = rewardBalance?.toDouble(),
|
||||||
|
integralBalance = integralBalance,
|
||||||
|
member = member ?: false
|
||||||
|
)
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.sw.dualscreen.model.response.v2
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.sw.dualscreen.model.response.FoodItem
|
||||||
|
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统结算订单数据
|
||||||
|
* 对应接口:/neglect/booth/order/settlement
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class SettlementOrder(
|
||||||
|
val calorie: BigDecimal?,
|
||||||
|
val incomeSum: BigDecimal?,
|
||||||
|
val discountSum: BigDecimal?,
|
||||||
|
val eatWeightSum: Int?,
|
||||||
|
val orderNo: String?,
|
||||||
|
val list: List<SettlementFoodItem>?
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class SettlementFoodItem(
|
||||||
|
val foodId: String?,
|
||||||
|
val foodName: String?,
|
||||||
|
val specId: String?,
|
||||||
|
val specName: String?,
|
||||||
|
val specWeight: BigDecimal?,
|
||||||
|
val eatNum: Int?,
|
||||||
|
val eatWeight: Int?,
|
||||||
|
val price: BigDecimal?
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
/** SettlementOrder → FoodOrderModel 映射(V2 数据模型兼容旧版 UI) */
|
||||||
|
fun SettlementOrder.toFoodOrderModel() = FoodOrderModel(
|
||||||
|
calorie = calorie?.toDouble() ?: 0.0,
|
||||||
|
incomeSum = incomeSum?.toDouble() ?: 0.0,
|
||||||
|
discountSum = discountSum?.toDouble() ?: 0.0,
|
||||||
|
eatWeightSum = eatWeightSum ?: 0,
|
||||||
|
orderNo = orderNo,
|
||||||
|
list = list?.map { it.toFoodItem() }
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun SettlementFoodItem.toFoodItem() = FoodItem(
|
||||||
|
foodId = foodId,
|
||||||
|
foodName = foodName,
|
||||||
|
specId = specId,
|
||||||
|
specName = specName,
|
||||||
|
specWeight = specWeight?.toInt() ?: 0,
|
||||||
|
eatNum = eatNum ?: 0,
|
||||||
|
price = price?.toDouble() ?: 0.0,
|
||||||
|
// 重量栏读取 num 字段,这里用实际食用重量 eatWeight 赋值
|
||||||
|
num = eatWeight ?: 0
|
||||||
|
)
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
package com.sw.dualscreen.network
|
package com.sw.dualscreen.network
|
||||||
|
|
||||||
|
import com.sw.dualscreen.BuildConfig
|
||||||
|
import com.sw.dualscreen.GlobalData
|
||||||
import com.sw.dualscreen.MyApp
|
import com.sw.dualscreen.MyApp
|
||||||
import com.sw.dualscreen.network.api.ApiService
|
import com.sw.dualscreen.network.api.ApiService
|
||||||
|
import com.sw.dualscreen.network.api.ApiServiceV2
|
||||||
import com.sw.dualscreen.network.interceptor.RequestInterceptor
|
import com.sw.dualscreen.network.interceptor.RequestInterceptor
|
||||||
|
import com.sw.dualscreen.repository.RemoteRepository
|
||||||
|
import com.sw.dualscreen.repository.v2.RemoteRepositoryV2
|
||||||
|
import com.sw.dualscreen.utils.FileUtil
|
||||||
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
@@ -11,22 +18,36 @@ import timber.log.Timber
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object ApiClient {
|
object ApiClient {
|
||||||
private const val BASE_URL = "http://device.shuziweidao.com:8889/"
|
// private const val BASE_URL = "http://device.shuziweidao.com:8889/"
|
||||||
|
private const val BASE_URL = GlobalData.PROD_BASE_URL
|
||||||
|
|
||||||
private const val TIME_OUT = 30L // 超时时间(秒)
|
private const val TIME_OUT = 30L
|
||||||
|
|
||||||
private val okHttpClient = OkHttpClient.Builder()
|
private val okHttpClient = OkHttpClient.Builder()
|
||||||
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
|
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
|
||||||
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
|
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
|
||||||
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
|
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
|
||||||
.addNetworkInterceptor(HttpLoggingInterceptor(logger = {
|
.addNetworkInterceptor(Interceptor { chain ->
|
||||||
Timber.d("okhttp logger ==>${it}")
|
val request = chain.request()
|
||||||
|
val url = request.url.toString()
|
||||||
|
val tag = if (
|
||||||
|
url.contains("terminal/neglect/common/app/faceFeature/increment/list") ||
|
||||||
|
url.contains("nutrition/neglect/serve/face/increment")
|
||||||
|
) "faceIncrement"
|
||||||
|
else if (url.contains("terminal/neglect/pay/app/turnOrderInfo")) "turnOrderInfo"
|
||||||
|
else if(url.contains("nutrition/neglect")) "V2"
|
||||||
|
else "ApiClient"
|
||||||
|
val loggingInterceptor = HttpLoggingInterceptor(logger = {
|
||||||
|
Timber.tag(tag).d("okhttp logger ==>${it}")
|
||||||
|
FileUtil.saveLog("okhttp logger ==>${it}")
|
||||||
}).apply {
|
}).apply {
|
||||||
level = if (MyApp.DEBUG) {
|
level = if (BuildConfig.DEBUG) {
|
||||||
HttpLoggingInterceptor.Level.BODY
|
HttpLoggingInterceptor.Level.BODY
|
||||||
} else {
|
} else {
|
||||||
HttpLoggingInterceptor.Level.NONE
|
HttpLoggingInterceptor.Level.NONE
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
loggingInterceptor.intercept(chain)
|
||||||
})
|
})
|
||||||
.addInterceptor(RequestInterceptor())
|
.addInterceptor(RequestInterceptor())
|
||||||
.build()
|
.build()
|
||||||
@@ -35,10 +56,21 @@ object ApiClient {
|
|||||||
.baseUrl(BASE_URL)
|
.baseUrl(BASE_URL)
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
// .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 协程适配器
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val apiService: ApiService by lazy {
|
val apiService: ApiService by lazy {
|
||||||
retrofit.create(ApiService::class.java)
|
retrofit.create(ApiService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val apiServiceV2: ApiServiceV2 by lazy {
|
||||||
|
retrofit.create(ApiServiceV2::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
val repository: RemoteRepository by lazy {
|
||||||
|
RemoteRepository(apiService)
|
||||||
|
}
|
||||||
|
|
||||||
|
val repositoryV2: RemoteRepositoryV2 by lazy {
|
||||||
|
RemoteRepositoryV2(apiServiceV2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import com.sw.dualscreen.model.response.DinnerType
|
|||||||
import com.sw.dualscreen.model.response.FaceData
|
import com.sw.dualscreen.model.response.FaceData
|
||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.model.response.FoodOrder
|
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.FoodSearchReq
|
||||||
import com.sw.dualscreen.model.response.FoodVector
|
import com.sw.dualscreen.model.response.FoodVector
|
||||||
import com.sw.dualscreen.model.response.MemberInfo
|
import com.sw.dualscreen.model.response.MemberInfo
|
||||||
@@ -202,7 +203,7 @@ interface ApiService {
|
|||||||
@POST
|
@POST
|
||||||
suspend fun memberPay(
|
suspend fun memberPay(
|
||||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/memberPay",
|
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/memberPay",
|
||||||
@Body param: HashMap<String, String>
|
@Body param: HashMap<String, String?>
|
||||||
): ApiResponse<Any?>
|
): ApiResponse<Any?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,12 +221,10 @@ interface ApiService {
|
|||||||
* 二维码支付
|
* 二维码支付
|
||||||
* @param orderType 0付款 1会员充值
|
* @param orderType 0付款 1会员充值
|
||||||
*/
|
*/
|
||||||
@GET
|
@POST
|
||||||
suspend fun qrCodePay(
|
suspend fun qrCodePay(
|
||||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay",
|
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay",
|
||||||
@Query("authCode") authCode: String,
|
@Body param: HashMap<String, String?>
|
||||||
@Query("outTradeNo") orderNo: String,
|
|
||||||
@Query("orderType") orderType:Int
|
|
||||||
): ApiResponse<String?>
|
): ApiResponse<String?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -313,4 +312,22 @@ interface ApiService {
|
|||||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/deleteBoothMachineCollection",
|
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/deleteBoothMachineCollection",
|
||||||
@QueryMap param: MutableMap<String, String?>
|
@QueryMap param: MutableMap<String, String?>
|
||||||
): ApiResponse<Any?>
|
): 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?>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.sw.dualscreen.network.api
|
||||||
|
|
||||||
|
import com.sw.dualscreen.GlobalData
|
||||||
|
import com.sw.dualscreen.model.request.v2.BindUserOrderRequest
|
||||||
|
import com.sw.dualscreen.model.request.v2.PlaceOrderRequest
|
||||||
|
import com.sw.dualscreen.model.response.ApiResponse
|
||||||
|
import com.sw.dualscreen.model.response.DeviceConfig
|
||||||
|
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||||
|
import com.sw.dualscreen.model.response.UserNutrition
|
||||||
|
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||||
|
import com.sw.dualscreen.model.response.v2.FaceVO
|
||||||
|
import com.sw.dualscreen.model.response.v2.NewFoodInfo
|
||||||
|
import com.sw.dualscreen.model.response.v2.NewMemberInfo
|
||||||
|
import com.sw.dualscreen.model.response.v2.SettlementOrder
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Multipart
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Part
|
||||||
|
import retrofit2.http.PartMap
|
||||||
|
import retrofit2.http.Url
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新系统 API 接口服务
|
||||||
|
* 基于 /nutrition/neglect/booth 模块前缀
|
||||||
|
* 文档版本:2026-05-27
|
||||||
|
*/
|
||||||
|
interface ApiServiceV2 {
|
||||||
|
|
||||||
|
@GET
|
||||||
|
suspend fun getDeviceConfig(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/device/config"
|
||||||
|
): ApiResponse<DeviceConfig?>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全量人脸数据
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
suspend fun getFacePage(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/common/face/page",
|
||||||
|
@Body request: Map<String, Long>
|
||||||
|
): ApiResponse<List<FaceVO>?>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取增量人脸数据
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
suspend fun getFaceIncrement(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/common/face/increment",
|
||||||
|
@Body request: Map<String, Long>
|
||||||
|
): ApiResponse<List<FaceVO>?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getFoodByNames(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/food/by-names",
|
||||||
|
@Body request: FoodSearchReq
|
||||||
|
): ApiResponse<List<NewFoodInfo>?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun searchFood(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/food/search",
|
||||||
|
@Body request: Map<String, @JvmSuppressWildcards Any>
|
||||||
|
): ApiResponse<List<NewFoodInfo>?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getUserCurrentFood(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/user/current-food",
|
||||||
|
@Body request: Map<String, Long>
|
||||||
|
): ApiResponse<UserNutrition?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun placeOrder(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/order/place",
|
||||||
|
@Body request: PlaceOrderRequest
|
||||||
|
): ApiResponse<String?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getSettlementOrders(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/order/settlement",
|
||||||
|
@Body request: Map<String, Long>
|
||||||
|
): ApiResponse<SettlementOrder?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getMemberInfo(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/member/info",
|
||||||
|
@Body request: Map<String, Long>
|
||||||
|
): ApiResponse<NewMemberInfo?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getMemberInfoByPhone(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/member/info-by-phone",
|
||||||
|
@Body request: Map<String, String>
|
||||||
|
): ApiResponse<NewMemberInfo?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getMemberDiscount(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/member/discount",
|
||||||
|
@Body request: Map<String, Long>
|
||||||
|
): ApiResponse<String?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun bindUserOrder(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/order/bind-user",
|
||||||
|
@Body request: BindUserOrderRequest
|
||||||
|
): ApiResponse<Any?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getCollectPage(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/collect/page",
|
||||||
|
@Body request: Map<String, @JvmSuppressWildcards Any>
|
||||||
|
): ApiResponse<List<CollectedFoodV2>?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun getCollectVectorPage(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/collect/vector-page",
|
||||||
|
@Body request: Map<String, Long>
|
||||||
|
): ApiResponse<List<CollectedFoodV2>?>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST
|
||||||
|
suspend fun uploadCollect(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/collect/upload",
|
||||||
|
@PartMap params: HashMap<String, RequestBody>,
|
||||||
|
@Part foodPics: List<MultipartBody.Part>
|
||||||
|
): ApiResponse<List<String>?>
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun deleteCollect(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/collect/delete",
|
||||||
|
@Body request: Map<String, @JvmSuppressWildcards Any>
|
||||||
|
): ApiResponse<Any?>
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.pytorch.IValue
|
import org.pytorch.IValue
|
||||||
import org.pytorch.Module
|
import org.pytorch.Module
|
||||||
|
import org.pytorch.Tensor
|
||||||
import org.pytorch.torchvision.TensorImageUtils
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -29,6 +30,9 @@ object FoodModule {
|
|||||||
// private const val THRESHOLD = 0.0
|
// private const val THRESHOLD = 0.0
|
||||||
private var module: Module? = null
|
private var module: Module? = null
|
||||||
|
|
||||||
|
// 专用推理线程:与 Kotlin IO 调度器完全隔离,避免 ArcSoft 污染该线程的 FPU 状态(FPSCR)
|
||||||
|
private val inferenceExecutor = java.util.concurrent.Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
// private lateinit var embeddingsList: List<List<Float>>
|
// private lateinit var embeddingsList: List<List<Float>>
|
||||||
// private lateinit var labelsList: IntArray
|
// private lateinit var labelsList: IntArray
|
||||||
// private lateinit var classInfo: FoodClassInfo
|
// private lateinit var classInfo: FoodClassInfo
|
||||||
@@ -46,6 +50,23 @@ object FoodModule {
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
||||||
module = Module.load(modelPath)
|
module = Module.load(modelPath)
|
||||||
|
// 预热:在相机启动前先跑一次 forward,触发 PyTorch 线程池创建
|
||||||
|
// 在预热前将当前线程优先级调低,使 PyTorch 内部新建的工作线程也继承低优先级
|
||||||
|
// 从而降低后续推理时的瞬间功率,避免触发 PMIC 过流保护
|
||||||
|
Timber.tag(TAG).d("预热 forward() 开始")
|
||||||
|
val warmupTensor = Tensor.fromBlob(
|
||||||
|
FloatArray(1 * 3 * 224 * 224),
|
||||||
|
longArrayOf(1, 3, 224, 224)
|
||||||
|
)
|
||||||
|
inferenceExecutor.submit { module?.forward(IValue.from(warmupTensor)) }.get()
|
||||||
|
context.let { ctx ->
|
||||||
|
val am = ctx.getSystemService(android.content.Context.ACTIVITY_SERVICE)
|
||||||
|
as android.app.ActivityManager
|
||||||
|
val mi = android.app.ActivityManager.MemoryInfo()
|
||||||
|
am.getMemoryInfo(mi)
|
||||||
|
Timber.tag(TAG).d("预热完成后系统可用内存: ${mi.availMem / 1024 / 1024} MB / 总: ${mi.totalMem / 1024 / 1024} MB")
|
||||||
|
}
|
||||||
|
Timber.tag(TAG).d("预热 forward() 完成")
|
||||||
//初始化默认重新拉取数据,先清空本地数据
|
//初始化默认重新拉取数据,先清空本地数据
|
||||||
val list = ObjectBox.getAll()
|
val list = ObjectBox.getAll()
|
||||||
if (list.isNotEmpty()) {
|
if (list.isNotEmpty()) {
|
||||||
@@ -86,8 +107,28 @@ object FoodModule {
|
|||||||
if (module == null) {
|
if (module == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val outputTensor = module?.forward(IValue.from(inputTensor))?.toTensor()
|
Timber.tag(TAG).d("module:${module}")
|
||||||
return outputTensor?.dataAsFloatArray
|
val iValue = IValue.from(inputTensor)
|
||||||
|
Timber.tag(TAG).d("inputTensor:${inputTensor}")
|
||||||
|
|
||||||
|
// 推理前记录系统可用内存,排查是否因内存不足触发 OOM 重启
|
||||||
|
MyApp.instance?.let { ctx ->
|
||||||
|
val am = ctx.getSystemService(android.content.Context.ACTIVITY_SERVICE)
|
||||||
|
as android.app.ActivityManager
|
||||||
|
val mi = android.app.ActivityManager.MemoryInfo()
|
||||||
|
am.getMemoryInfo(mi)
|
||||||
|
Timber.tag(TAG).d("forward() 前系统可用内存: ${mi.availMem / 1024 / 1024} MB / 总: ${mi.totalMem / 1024 / 1024} MB, lowMemory=${mi.lowMemory}")
|
||||||
|
}
|
||||||
|
Timber.tag(TAG).d("forward() 开始: ${System.currentTimeMillis()}")
|
||||||
|
val outputIValue = inferenceExecutor.submit<IValue?> { module?.forward(iValue) }
|
||||||
|
.get(10, java.util.concurrent.TimeUnit.SECONDS) ?: return null
|
||||||
|
Timber.tag(TAG).d("forward() 结束: ${System.currentTimeMillis()}")
|
||||||
|
|
||||||
|
|
||||||
|
Timber.tag(TAG).d("outputIValue:${outputIValue}")
|
||||||
|
val outputTensor = outputIValue.toTensor()
|
||||||
|
Timber.tag(TAG).d("outputTensor:${outputTensor}")
|
||||||
|
return outputTensor.dataAsFloatArray
|
||||||
} catch (e: OutOfMemoryError) {
|
} catch (e: OutOfMemoryError) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -133,18 +174,10 @@ object FoodModule {
|
|||||||
//查询比较分数
|
//查询比较分数
|
||||||
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
||||||
// val idScoreList = query.findIdsWithScores()
|
// val idScoreList = query.findIdsWithScores()
|
||||||
val objScoreList = ObjectBox.query(floatArray, queryCount)
|
val tripleList = ObjectBox.query(floatArray, queryCount)
|
||||||
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}")
|
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(tripleList)}")
|
||||||
val nameScoreList = mutableListOf<IdNameScore>()
|
val nameScoreList = tripleList.map { (id, name, score) ->
|
||||||
objScoreList.forEach {
|
IdNameScore(id = id, name = name ?: "", score = score)
|
||||||
val food = it.get()
|
|
||||||
nameScoreList.add(
|
|
||||||
IdNameScore(
|
|
||||||
id = food.id,
|
|
||||||
name = food.foodName ?: "",
|
|
||||||
score = it.score
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
||||||
Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
||||||
@@ -153,7 +186,9 @@ object FoodModule {
|
|||||||
|
|
||||||
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
val floatArray = bitmap2FloatArray(bitmap, false)
|
val floatArray = withContext(Dispatchers.IO) {
|
||||||
|
bitmap2FloatArray(bitmap, false)
|
||||||
|
}
|
||||||
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
|
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
|
||||||
|
|
||||||
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||||
@@ -161,28 +196,26 @@ object FoodModule {
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
val maxScoreList = nameScoreList
|
val maxScoreList = nameScoreList
|
||||||
.filter { it.score < 1 - THRESHOLD }
|
// .filter { it.score < 1 - THRESHOLD }
|
||||||
.groupBy { it.name }
|
.groupBy { it.name }
|
||||||
.map { (_, value) -> value.minByOrNull { it.score }!! }
|
.map { (_, value) -> value.minByOrNull { it.score }!! }
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
val map = mutableMapOf<String, Int>()
|
// val map = mutableMapOf<String, Int>()
|
||||||
nameScoreList.forEach {
|
// nameScoreList.forEach {
|
||||||
val key = it.name
|
// val key = it.name
|
||||||
val count = map[key] ?: 0
|
// val count = map[key] ?: 0
|
||||||
map[key] = count + 1
|
// map[key] = count + 1
|
||||||
}
|
// }
|
||||||
val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
|
// val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
|
||||||
val firstFood = nameScoreList[0].name
|
// val firstFood = nameScoreList[0].name
|
||||||
orderList.remove(firstFood)
|
// orderList.remove(firstFood)
|
||||||
orderList.add(0, firstFood)
|
// orderList.add(0, firstFood)
|
||||||
|
//
|
||||||
val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
// val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
||||||
orderList.indexOf(it.name)
|
// orderList.indexOf(it.name)
|
||||||
})
|
// })
|
||||||
if (bitmap.isRecycled.not()) {
|
val sortedScoreList = maxScoreList.sortedBy { it.score }
|
||||||
bitmap.recycle()
|
Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toString()}")
|
||||||
}
|
|
||||||
//Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
|
||||||
return sortedScoreList
|
return sortedScoreList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ package com.sw.dualscreen.objbox
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.sw.dualscreen.BuildConfig
|
||||||
import com.sw.plate.App
|
import com.sw.plate.App
|
||||||
import io.objectbox.Box
|
import io.objectbox.Box
|
||||||
import io.objectbox.BoxStore
|
import io.objectbox.BoxStore
|
||||||
import io.objectbox.BoxStoreBuilder
|
import io.objectbox.BoxStoreBuilder
|
||||||
import io.objectbox.android.Admin
|
import io.objectbox.android.Admin
|
||||||
import io.objectbox.android.ObjectBoxLiveData
|
|
||||||
import io.objectbox.config.DebugFlags
|
import io.objectbox.config.DebugFlags
|
||||||
import io.objectbox.exception.DbException
|
import io.objectbox.exception.DbException
|
||||||
import io.objectbox.exception.FileCorruptException
|
import io.objectbox.exception.FileCorruptException
|
||||||
@@ -91,14 +92,14 @@ object ObjectBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
|
val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
|
||||||
Timber.tag(TAG)
|
Timber.tag(TAG)
|
||||||
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
|
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
|
||||||
// Enable ObjectBox Admin on debug builds.
|
// Enable ObjectBox Admin on debug builds.
|
||||||
// https://docs.objectbox.io/data-browser
|
// https://docs.objectbox.io/data-browser
|
||||||
Admin(boxStore).start(context.applicationContext)
|
Admin(boxStore).start(context.applicationContext)
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -143,24 +144,126 @@ object ObjectBox {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
|
} finally {
|
||||||
|
// 线程资源清理
|
||||||
|
boxStore?.closeThreadResources()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
||||||
val query: Query<Food>? = getBox<Food>()?.query()
|
val box = getBox<Food>() ?: return@safeDbOp emptyList<Triple<Long, String?, Double>>()
|
||||||
?.equal(Food_.isDel, false)
|
|
||||||
?.and()
|
val query: Query<Food> = box.query()
|
||||||
?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
.equal(Food_.isDel, false)
|
||||||
?.build()
|
.and()
|
||||||
|
.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||||
|
.build()
|
||||||
try {
|
try {
|
||||||
query?.findWithScores()
|
// 统一使用 findIdsWithScores,避免在 32 位系统上因加载大对象而崩溃
|
||||||
|
val idScoreList = query.findIdsWithScores()
|
||||||
|
if (idScoreList.isNotEmpty()) {
|
||||||
|
// 根据 ID 查询完整对象,仅提取 id 和 name,丢弃 foodVector 以节省内存
|
||||||
|
val foodMap = box.get(idScoreList.map { it.id }).associateBy { it.id }
|
||||||
|
idScoreList.map { scoreId ->
|
||||||
|
val food = foodMap[scoreId.id]
|
||||||
|
Triple(scoreId.id, food?.foodName, scoreId.score)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
} catch (e: DbException) {
|
||||||
|
Timber.tag(TAG).e(e, "向量查询失败")
|
||||||
|
emptyList()
|
||||||
} finally {
|
} finally {
|
||||||
// 先关闭Query,释放Cursor
|
query.close()
|
||||||
query?.close()
|
|
||||||
}
|
}
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|
||||||
|
// suspend fun queryWithScore(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
||||||
|
// val box = getBox<Food>() ?: return@safeDbOp emptyList()
|
||||||
|
//
|
||||||
|
// val query: Query<Food>? = box.query()
|
||||||
|
// ?.equal(Food_.isDel, false)
|
||||||
|
// ?.and()
|
||||||
|
// ?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||||
|
// ?.build()
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// // 检测是否为 32 位系统
|
||||||
|
// val is32Bit = Build.SUPPORTED_ABIS.any {
|
||||||
|
// it.contains("armeabi") && !it.contains("arm64")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (is32Bit) {
|
||||||
|
// // 32位系统:使用 findWithScoresAndIds 替代 findWithScores
|
||||||
|
// val scoreIds = query?.findWithScoresAndIds()
|
||||||
|
// if (scoreIds != null && scoreIds.isNotEmpty()) {
|
||||||
|
// // 如果需要分数,返回 ScoreId 对象
|
||||||
|
// // 注意:这需要修改返回类型或使用其他方式传递分数
|
||||||
|
// scoreIds.toList()
|
||||||
|
// } else {
|
||||||
|
// emptyList()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // 64位系统:正常使用 findWithScores
|
||||||
|
// query?.findWithScores() ?: emptyList()
|
||||||
|
// }
|
||||||
|
// } catch (e: DbException) {
|
||||||
|
// // 如果仍然失败,记录日志并返回空列表
|
||||||
|
// Timber.tag(TAG).e(e, "Vector query with score failed, returning empty list")
|
||||||
|
// emptyList()
|
||||||
|
// } finally {
|
||||||
|
// query?.close()
|
||||||
|
// }
|
||||||
|
// } ?: emptyList()
|
||||||
|
|
||||||
|
// suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
||||||
|
// val box = getBox<Food>() ?: return@safeDbOp emptyList()
|
||||||
|
//
|
||||||
|
// val query: Query<Food>? = box.query()
|
||||||
|
// ?.equal(Food_.isDel, false)
|
||||||
|
// ?.and()
|
||||||
|
// ?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||||
|
// ?.build()
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// // 检测是否为 32 位系统
|
||||||
|
// val is32Bit = Build.SUPPORTED_ABIS.any {
|
||||||
|
// it.contains("armeabi") && !it.contains("arm64")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (is32Bit) {
|
||||||
|
// // 32位系统:先获取带分数的IDs,再批量获取完整对象
|
||||||
|
// val scoreIds = query?.findWithScoresAndIds()
|
||||||
|
// if (scoreIds != null && scoreIds.isNotEmpty()) {
|
||||||
|
// // 提取 IDs
|
||||||
|
// val ids = scoreIds.map { it.id }.toLongArray()
|
||||||
|
// // 批量获取完整对象
|
||||||
|
// val foods = box.get(ids.toList()).associateBy { it.id }
|
||||||
|
// // 按照分数顺序组装结果(保持排序)
|
||||||
|
// scoreIds.mapNotNull { scoreId ->
|
||||||
|
// foods[scoreId.id]
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// emptyList()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // 64位系统:直接使用 findWithScores
|
||||||
|
// query?.findWithScores() ?: emptyList()
|
||||||
|
// }
|
||||||
|
// } catch (e: DbException) {
|
||||||
|
// // 如果仍然失败,记录日志并返回空列表
|
||||||
|
// Timber.tag(TAG).e(e, "Vector query failed, returning empty list")
|
||||||
|
// emptyList()
|
||||||
|
// } finally {
|
||||||
|
// query?.close()
|
||||||
|
// }
|
||||||
|
// } ?: emptyList()
|
||||||
|
// ... existing code ...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun get(id: Long) = safeDbOp {
|
suspend fun get(id: Long) = safeDbOp {
|
||||||
getBox<Food>()?.get(id)
|
getBox<Food>()?.get(id)
|
||||||
}
|
}
|
||||||
@@ -177,16 +280,13 @@ object ObjectBox {
|
|||||||
getBox<Food>()?.all
|
getBox<Food>()?.all
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|
||||||
suspend fun filter(name: String?, isDistinct: Boolean = false) = safeDbOp {
|
suspend fun filter(name: String?) = safeDbOp {
|
||||||
val list = if (isDistinct) {
|
if (!name.isNullOrBlank()) {
|
||||||
getBox<Food>()?.all?.distinctBy { it.foodName }
|
getBox<Food>()?.query(Food_.foodName.contains(name))?.build()?.find()
|
||||||
|
?.distinctBy { it.foodName }
|
||||||
} else {
|
} else {
|
||||||
getBox<Food>()?.all
|
getBox<Food>()?.all?.distinctBy { it.foodName }
|
||||||
}
|
} ?: emptyList()
|
||||||
if (name.isNullOrBlank().not()) {
|
|
||||||
list?.filter { it.foodName?.contains(name) == true }
|
|
||||||
}
|
|
||||||
list
|
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|
||||||
suspend fun removeAll() = safeDbOp {
|
suspend fun removeAll() = safeDbOp {
|
||||||
@@ -194,10 +294,7 @@ object ObjectBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun remove(name: String) = safeDbOp {
|
suspend fun remove(name: String) = safeDbOp {
|
||||||
getBox<Food>()?.run {
|
getBox<Food>()?.query(Food_.foodName.equal(name))?.build()?.remove()
|
||||||
val filterIdList = all.filter { it.foodName == name }.map { it.id }
|
|
||||||
removeByIds(filterIdList)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
|
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
|
||||||
@@ -207,9 +304,7 @@ object ObjectBox {
|
|||||||
// 检查存储是否可读写(操作数据库前调用)
|
// 检查存储是否可读写(操作数据库前调用)
|
||||||
fun isStorageAvailable(context: Context): Boolean {
|
fun isStorageAvailable(context: Context): Boolean {
|
||||||
return try {
|
return try {
|
||||||
val state = Environment.getExternalStorageState()
|
context.filesDir.canRead() && context.filesDir.canWrite()
|
||||||
val innerDir = context.filesDir
|
|
||||||
Environment.MEDIA_MOUNTED == state && innerDir.canRead() && innerDir.canWrite()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,51 @@
|
|||||||
package com.sw.dualscreen.presentation
|
package com.sw.dualscreen.presentation
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Presentation
|
import android.app.Presentation
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Outline
|
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
import android.hardware.Camera
|
import android.hardware.Camera
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.util.Log
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewOutlineProvider
|
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.arcsoft.face.ErrorInfo
|
import com.arcsoft.face.ErrorInfo
|
||||||
|
import com.sw.dualscreen.BuildConfig
|
||||||
import com.sw.dualscreen.GlobalKey
|
import com.sw.dualscreen.GlobalKey
|
||||||
import com.sw.dualscreen.MyApp
|
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.activity.MainActivity
|
import com.sw.dualscreen.activity.MainActivity
|
||||||
|
import com.sw.dualscreen.adapter.FoodOrderAdapter
|
||||||
import com.sw.dualscreen.databinding.PresentationMainScreenBinding
|
import com.sw.dualscreen.databinding.PresentationMainScreenBinding
|
||||||
import com.sw.dualscreen.ext.dp
|
import com.sw.dualscreen.ext.dp
|
||||||
import com.sw.dualscreen.ext.format2String
|
import com.sw.dualscreen.ext.format2String
|
||||||
import com.sw.dualscreen.ext.gone
|
import com.sw.dualscreen.ext.gone
|
||||||
|
import com.sw.dualscreen.ext.invisible
|
||||||
import com.sw.dualscreen.ext.load
|
import com.sw.dualscreen.ext.load
|
||||||
import com.sw.dualscreen.ext.maskName
|
import com.sw.dualscreen.ext.maskName
|
||||||
import com.sw.dualscreen.ext.roundedDecimalPlace
|
import com.sw.dualscreen.ext.roundedDecimalPlace
|
||||||
|
import com.sw.dualscreen.ext.screenSize
|
||||||
import com.sw.dualscreen.ext.visible
|
import com.sw.dualscreen.ext.visible
|
||||||
import com.sw.dualscreen.model.response.DinnerType
|
import com.sw.dualscreen.model.response.DinnerType
|
||||||
import com.sw.dualscreen.model.response.FoodInfo
|
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.UserEnergy
|
||||||
import com.sw.dualscreen.model.response.UserNutrition
|
import com.sw.dualscreen.model.response.UserNutrition
|
||||||
import com.sw.dualscreen.utils.Debouncer
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
import com.sw.dualscreen.utils.SPUtil
|
import com.sw.dualscreen.utils.SPUtil
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.utils.countDownByFlow
|
||||||
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
import com.sw.plate.utils.LightManager
|
import com.sw.plate.utils.LightManager
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import com.sw.plate.utils.arcface.ConfigUtil
|
import com.sw.plate.utils.arcface.ConfigUtil
|
||||||
@@ -59,24 +61,25 @@ 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.FacePreviewInfo
|
||||||
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
|
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
|
||||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.DecimalFormat
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
|
||||||
class MainScreenPresentation(
|
class MainScreenPresentation(
|
||||||
val activity: MainActivity,
|
val activity: MainActivity,
|
||||||
display: Display,
|
display: Display,
|
||||||
val userViewModel: UserViewModel,
|
val userViewModel: NetViewModelV2,
|
||||||
val recognizeViewModel: RecognizeViewModel,
|
val recognizeViewModel: RecognizeViewModel,
|
||||||
private val onDismissListener: () -> Unit = {}
|
private val onDismissListener: () -> Unit = {}
|
||||||
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "SubScreenPresentation"
|
const val TAG = "MainScreenPresentation"
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var binding: PresentationMainScreenBinding
|
private lateinit var binding: PresentationMainScreenBinding
|
||||||
@@ -98,14 +101,14 @@ class MainScreenPresentation(
|
|||||||
private var irFaceRectTransformer: FaceRectTransformer? = null
|
private var irFaceRectTransformer: FaceRectTransformer? = null
|
||||||
|
|
||||||
private var detectWeight = 0.0 //识别菜品时的重量
|
private var detectWeight = 0.0 //识别菜品时的重量
|
||||||
var lastWeight = 0.0 // 上一次的计算热量结果
|
var lastWeight = 0 // 上一次的计算热量结果
|
||||||
|
|
||||||
// private var userNutritionData: UserNutritionData? = null
|
// private var userNutritionData: UserNutritionData? = null
|
||||||
private var userNutrition: UserNutrition? = null
|
private var userNutrition: UserNutrition? = null
|
||||||
private var dinnerTypeInfo: DinnerType? = null
|
private var dinnerTypeInfo: DinnerType? = null
|
||||||
private val debouncer = Debouncer(500)
|
private val debouncer = Debouncer(1500)
|
||||||
private var recognitionTime: Long = 0L // 人脸识别时的时间
|
private var recognitionTime: Long = 0L // 人脸识别时的时间
|
||||||
var recognitionWeight: Double = 0.0 // 人脸识别时的重量
|
var recognitionWeight: Int = 0 // 人脸识别时的重量
|
||||||
|
|
||||||
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
|
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
|
||||||
var mealPickupMode: Int = 0 // 取餐模式 0 即放即取 1 余量取餐
|
var mealPickupMode: Int = 0 // 取餐模式 0 即放即取 1 余量取餐
|
||||||
@@ -114,6 +117,7 @@ class MainScreenPresentation(
|
|||||||
var isKeepFaceState = false
|
var isKeepFaceState = false
|
||||||
|
|
||||||
var currentUserId: String? = null
|
var currentUserId: String? = null
|
||||||
|
var isMember: Boolean = false
|
||||||
|
|
||||||
var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
|
var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
|
||||||
|
|
||||||
@@ -121,11 +125,13 @@ class MainScreenPresentation(
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = PresentationMainScreenBinding.inflate(layoutInflater)
|
binding = PresentationMainScreenBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
//0-即放即取,1-余量计量
|
||||||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||||
window?.setBackgroundDrawableResource(android.R.color.white)
|
window?.setBackgroundDrawableResource(android.R.color.white)
|
||||||
initView()
|
initView()
|
||||||
|
//0-计费,1-不计费
|
||||||
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||||
if (chargeMode == 1) {
|
if (activity.settlementMode == 0 || chargeMode == 1) {
|
||||||
setupArcCamera()
|
setupArcCamera()
|
||||||
}
|
}
|
||||||
registerDataChange()
|
registerDataChange()
|
||||||
@@ -134,71 +140,83 @@ class MainScreenPresentation(
|
|||||||
|
|
||||||
private var isFirstGetWeight = true
|
private var isFirstGetWeight = true
|
||||||
|
|
||||||
fun updateWeight(weight: Double) {
|
fun updateWeight(weight: Int) {
|
||||||
lastWeight = weight * 1000 // 将千克转成克
|
|
||||||
Timber.tag(TAG)
|
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
|
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||||
|
//0-计费,1-不计费
|
||||||
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||||
// if (mealPickupMode == 1 && chargeMode == 0) {
|
if (currentStep == 2 && weight <= MainActivity.WEIGHT_RESET_RECOGNIZE && isGoStep1) {
|
||||||
// //余量计量+计费模式
|
|
||||||
// if (isFirstGetWeight && lastWeight > 5) {
|
|
||||||
// recognitionWeight = lastWeight
|
|
||||||
// isFirstGetWeight = false
|
|
||||||
// }
|
|
||||||
// if (lastWeight > 5) {
|
|
||||||
// //余量计量,有重量的情况下不回到重新识别状态
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// isFirstGetWeight = true
|
|
||||||
// }
|
|
||||||
if (currentStep == 2 && lastWeight <= 5 && isGoStep1) {
|
|
||||||
//人脸识别过程中,秤上物品拿走
|
//人脸识别过程中,秤上物品拿走
|
||||||
isGoStep1 = false
|
isGoStep1 = false
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
Timber.tag(TAG).e("切回step1")
|
Timber.tag(TAG).e("切回step1,------2000000000000")
|
||||||
step1FoodRecognizing()
|
step1FoodRecognizing()
|
||||||
}
|
}
|
||||||
} else if (lastWeight <= 5) {
|
} else if (weight <= MainActivity.WEIGHT_RESET_RECOGNIZE) {
|
||||||
isGoStep1 = false
|
isGoStep1 = false
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
Timber.tag(TAG).e("切回step1")
|
Timber.tag(TAG).e("切回step1,------2000000000001")
|
||||||
if (mealPickupMode == 0) {
|
if (mealPickupMode == 0) {
|
||||||
//即放即取
|
//即放即取
|
||||||
step1FoodRecognizing()
|
step1FoodRecognizing()
|
||||||
} else {
|
} else {
|
||||||
//秤上菜品不足,请补充
|
if (currentStep == 1 && currentFood == null) {
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
|
//余量计量
|
||||||
|
initCountDownResetJob(weight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (currentStep != 3) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
if (currentFood == null) {
|
if (currentFood == null) {
|
||||||
Timber.tag(TAG).e("updateWeight currentFood is null")
|
Timber.tag(TAG).e("updateWeight currentFood is null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (userNutritionData == null) {
|
|
||||||
if (userNutrition == null) {
|
if (userNutrition == null) {
|
||||||
Timber.tag(TAG).e("updateWeight userNutritionData is null")
|
Timber.tag(TAG).e("updateWeight userNutritionData is null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if (dinnerTypeInfo == null) {
|
if (chargeMode == 1 || activity.settlementMode == 0) {
|
||||||
// Timber.tag(TAG).e("updateWeight dinnerType is null")
|
//不计费或者联合支付模式更新营养热量数据
|
||||||
// return
|
|
||||||
// }
|
|
||||||
if (chargeMode == 1) {
|
|
||||||
//不计费
|
|
||||||
if (mealPickupMode == 0) {
|
if (mealPickupMode == 0) {
|
||||||
//即放即取
|
//即放即取
|
||||||
calculateNutrition(lastWeight)
|
calculateNutrition(weight)
|
||||||
} else {
|
} 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) {
|
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) {
|
fun step3ShowRecognizeResult(userId: String) {
|
||||||
|
step3Debouncer.debounce {
|
||||||
Timber.tag(TAG).d("main,step3,耗时:${System.currentTimeMillis() - startTime}")
|
Timber.tag(TAG).d("main,step3,耗时:${System.currentTimeMillis() - startTime}")
|
||||||
LightManager.closeRedLight()
|
LightManager.closeRedLight()
|
||||||
LightManager.openGreenLight()
|
LightManager.openGreenLight()
|
||||||
currentStep = 3
|
currentStep = 3
|
||||||
stepChangeCallback(currentStep)
|
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 ->
|
userViewModel.getUserNutritionData(userId = userId) { nutrition ->
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
if (nutrition == null) {
|
if (nutrition == null) {
|
||||||
@@ -401,7 +461,7 @@ class MainScreenPresentation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var isLoadUnbilledMode = false
|
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()) {
|
if (currentUserId.isNullOrBlank()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//todo 清除人脸数据
|
|
||||||
// binding.dualCameraFaceRectView.clearFaceInfo()
|
|
||||||
// clearLeftFaceData()
|
|
||||||
// facePreviewInfoList?.clear()
|
|
||||||
// recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
|
|
||||||
|
|
||||||
// pauseCamera()
|
|
||||||
|
|
||||||
// resumeCamera()
|
|
||||||
|
|
||||||
// TODO: 如何清除人脸数据重新识别
|
|
||||||
// stopCamera()
|
|
||||||
// setupArcCamera()
|
|
||||||
|
|
||||||
isLoadUnbilledMode = true
|
isLoadUnbilledMode = true
|
||||||
Timber.tag(TAG).d("loadUnbilledMode")
|
Timber.tag(TAG).d("loadUnbilledMode")
|
||||||
@@ -440,15 +487,16 @@ class MainScreenPresentation(
|
|||||||
|
|
||||||
//updateFoodInfo(foodInfo)
|
//updateFoodInfo(foodInfo)
|
||||||
binding.nutritionInclude.tvUserName.text = userNutrition?.name.maskName()
|
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 =
|
binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${recommendCalorie}kcal"
|
||||||
"推荐热量:${df.format(recommendCalorie)}kcal"
|
|
||||||
// binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${recommendCalorie.removeTrailingZeros()}kcal"
|
//0-即放即取,1-余量计量
|
||||||
//updateWeight(lastWeight / 1000)
|
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||||
// -------------------------------------------------
|
calculateNutrition(
|
||||||
// 删除userNutritionData,改为userNutrition
|
if (mealPickupMode == 1) recognitionWeight - lastWeight
|
||||||
calculateNutrition(recognitionWeight - lastWeight)
|
else recognitionWeight
|
||||||
|
)
|
||||||
binding.root.postDelayed({
|
binding.root.postDelayed({
|
||||||
isLoadUnbilledMode = false
|
isLoadUnbilledMode = false
|
||||||
}, 10000)
|
}, 10000)
|
||||||
@@ -465,24 +513,24 @@ class MainScreenPresentation(
|
|||||||
private var isFirstOpen = true
|
private var isFirstOpen = true
|
||||||
var startTime = 0L
|
var startTime = 0L
|
||||||
|
|
||||||
|
var jointPaymentQueryFinish = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 餐品识别中
|
* 餐品识别中
|
||||||
*/
|
*/
|
||||||
fun step1FoodRecognizing() {
|
fun step1FoodRecognizing() {
|
||||||
|
if (currentStep == 1 && currentFood == null) {
|
||||||
|
activity.clearRecognizeFood()
|
||||||
|
return
|
||||||
|
}
|
||||||
Timber.tag(TAG).d("main,step1,耗时:${System.currentTimeMillis() - startTime}")
|
Timber.tag(TAG).d("main,step1,耗时:${System.currentTimeMillis() - startTime}")
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
|
jointPaymentQueryFinish = false
|
||||||
hideStandbyScreen()
|
hideStandbyScreen()
|
||||||
activity.resetTouchTime()
|
activity.resetTouchTime()
|
||||||
if (isFirstOpen.not()) {
|
if (isFirstOpen.not()) {
|
||||||
activity.resumeAnalysis()
|
activity.resumeAnalysis()
|
||||||
}
|
}
|
||||||
//if (abs(lastWeight.toInt()) <= 5) {
|
|
||||||
// activity.runOnUiThread {
|
|
||||||
// ToastUtils.showToast("秤上重量几乎为0,请确认")
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
currentStep = 1
|
|
||||||
LightManager.closeGreenLight()
|
LightManager.closeGreenLight()
|
||||||
LightManager.closeRedLight()
|
LightManager.closeRedLight()
|
||||||
|
|
||||||
@@ -490,9 +538,9 @@ class MainScreenPresentation(
|
|||||||
currentFood = null
|
currentFood = null
|
||||||
// userNutritionData = null
|
// userNutritionData = null
|
||||||
userNutrition = null
|
userNutrition = null
|
||||||
recognitionWeight = 0.0
|
recognitionWeight = 0
|
||||||
|
|
||||||
stepChangeCallback(currentStep)
|
setTopInfoVisible()
|
||||||
|
|
||||||
binding.calorieInclude.root.gone()
|
binding.calorieInclude.root.gone()
|
||||||
binding.nutritionInclude.root.gone()
|
binding.nutritionInclude.root.gone()
|
||||||
@@ -511,31 +559,23 @@ class MainScreenPresentation(
|
|||||||
binding.flRecognizeIr.gone()
|
binding.flRecognizeIr.gone()
|
||||||
|
|
||||||
pauseCamera()
|
pauseCamera()
|
||||||
activity.clearFoodList()
|
activity.clearRecognizeFood()
|
||||||
}
|
|
||||||
|
|
||||||
// fun foodRecSuccess(foodInfo: FoodInfo) {
|
currentStep = 1
|
||||||
// //识别逻辑完成根据是否计费显示不同页面
|
stepChangeCallback(currentStep)
|
||||||
// val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
}
|
||||||
// if (mode == 0) {
|
|
||||||
// //计费
|
|
||||||
// loadBilledMode()
|
|
||||||
// } else {
|
|
||||||
// //不计费
|
|
||||||
// //userViewModel.getUserNutritionData(
|
|
||||||
// // userId = userId,
|
|
||||||
// // foodId = currentFood!!.foodId!!
|
|
||||||
// //)
|
|
||||||
// loadUnbilledMode()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户人脸识别中
|
* 用户人脸识别中
|
||||||
*/
|
*/
|
||||||
fun step2FaceRecognizing(foodInfo: FoodInfo) {
|
fun step2FaceRecognizing(foodInfo: FoodInfo, hideFoodDetail: Boolean = false) {
|
||||||
isGoStep1 = true
|
isGoStep1 = true
|
||||||
Timber.tag(TAG).d("main,step2,耗时:${System.currentTimeMillis() - startTime}")
|
Timber.tag(TAG).d("main,step2,耗时:${System.currentTimeMillis() - startTime}")
|
||||||
|
|
||||||
|
if (hideFoodDetail) {
|
||||||
|
setTopInfoVisible()
|
||||||
|
}
|
||||||
|
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
currentStep = 2
|
currentStep = 2
|
||||||
LightManager.closeRedLight()
|
LightManager.closeRedLight()
|
||||||
@@ -559,7 +599,7 @@ class MainScreenPresentation(
|
|||||||
dinnerTypeInfo = null
|
dinnerTypeInfo = null
|
||||||
// userNutritionData = null
|
// userNutritionData = null
|
||||||
userNutrition = null
|
userNutrition = null
|
||||||
recognitionWeight = 0.0
|
recognitionWeight = 0
|
||||||
|
|
||||||
stepChangeCallback(currentStep)
|
stepChangeCallback(currentStep)
|
||||||
currentFood = foodInfo
|
currentFood = foodInfo
|
||||||
@@ -567,6 +607,10 @@ class MainScreenPresentation(
|
|||||||
// userViewModel.getDinnerType()
|
// userViewModel.getDinnerType()
|
||||||
|
|
||||||
resumeCamera()
|
resumeCamera()
|
||||||
|
|
||||||
|
if (activity.settlementMode == 0) {
|
||||||
|
activity.layoutEatDetailBinding.root.invisible()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideRecImage() {
|
fun hideRecImage() {
|
||||||
@@ -574,7 +618,7 @@ class MainScreenPresentation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 当前食物信息
|
// 当前食物信息
|
||||||
private var currentFood: FoodInfo? = null
|
var currentFood: FoodInfo? = null
|
||||||
fun updateFood(foodInfo: FoodInfo?) {
|
fun updateFood(foodInfo: FoodInfo?) {
|
||||||
//if (isLoadUnbilledMode) return
|
//if (isLoadUnbilledMode) return
|
||||||
Timber.tag(TAG).d("main,updateFood,耗时:${System.currentTimeMillis() - startTime}")
|
Timber.tag(TAG).d("main,updateFood,耗时:${System.currentTimeMillis() - startTime}")
|
||||||
@@ -586,6 +630,7 @@ class MainScreenPresentation(
|
|||||||
binding.tvFoodName.text = foodInfo.foodName
|
binding.tvFoodName.text = foodInfo.foodName
|
||||||
binding.ivPreviewImage.gone()
|
binding.ivPreviewImage.gone()
|
||||||
binding.ivFrame.gone()
|
binding.ivFrame.gone()
|
||||||
|
//0-计费,1-不计费
|
||||||
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||||
if (chargeMode == 0) {
|
if (chargeMode == 0) {
|
||||||
binding.ivRecImage.let {
|
binding.ivRecImage.let {
|
||||||
@@ -597,22 +642,25 @@ class MainScreenPresentation(
|
|||||||
} else {
|
} else {
|
||||||
binding.ivRecImage.gone()
|
binding.ivRecImage.gone()
|
||||||
}
|
}
|
||||||
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
//独立支付+计费模式,使用人脸识别
|
||||||
if (mode == 0) {
|
if (activity.settlementMode == 1 && chargeMode == 0) {
|
||||||
//计费,查询营养信息、价格信息------------------------
|
//计费,查询营养信息、价格信息------------------------
|
||||||
loadBilledMode()
|
loadBilledMode()
|
||||||
} else {
|
} else {
|
||||||
//不计费
|
//联合支付,进行人脸识别
|
||||||
//开启人脸识别
|
if (activity.settlementMode == 0) {
|
||||||
|
if (activity.isSwitchFood.not()) {
|
||||||
|
step2FaceRecognizing(foodInfo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//不计费,开启人脸识别
|
||||||
if (currentStep == 1 || binding.tvFaceTip.isVisible.not()) {
|
if (currentStep == 1 || binding.tvFaceTip.isVisible.not()) {
|
||||||
|
if (activity.isSwitchFood.not()) {
|
||||||
step2FaceRecognizing(foodInfo)
|
step2FaceRecognizing(foodInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if (currentStep == 1) {
|
}
|
||||||
// step2FaceRecognizing(foodInfo)
|
}
|
||||||
//} else {
|
|
||||||
// updateFoodInfo(foodInfo)
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFoodInfo(foodInfo: FoodInfo) {
|
private fun updateFoodInfo(foodInfo: FoodInfo) {
|
||||||
@@ -636,12 +684,89 @@ 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 <= MainActivity.MIN_BILLABLE_WEIGHT) {
|
||||||
|
//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
|
||||||
|
}
|
||||||
|
// 按重量计价:每克单价 = 规格价 / 规格重量,再按会员折扣折算
|
||||||
|
// 单价 = specPrice / specWeight,金额 = 当前秤上净重 × 单价 × 会员折扣
|
||||||
|
val specWeight = foodInfo.specWeight ?: 0.0
|
||||||
|
val specPrice = foodInfo.specPrice ?: 0.0
|
||||||
|
val price = if (specWeight > 0.0) {
|
||||||
|
weight * (specPrice / specWeight) * memberDiscount
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
// eatNum 仅用于本地明细展示(份数),不再参与金额计算
|
||||||
|
val eatNum = activity.getEatNum(weight.toDouble(), foodInfo.specWeight)
|
||||||
|
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) {
|
if (currentFood == null || userNutrition == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var weight = weight1
|
var weight = value
|
||||||
if (weight < 0) weight = 0.0
|
if (weight < 0) weight = 0
|
||||||
|
// 扣减餐具重量,后续营养计算、柱图绘制、取餐明细均以净重为准
|
||||||
|
weight = (weight - activity.getDishWeight()).coerceAtLeast(0)
|
||||||
|
if (activity.settlementMode == 0) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
addFoodToOrder(weight, currentFood!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
debouncer.debounce {
|
debouncer.debounce {
|
||||||
Timber.tag(TAG)
|
Timber.tag(TAG)
|
||||||
.d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight")
|
.d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight")
|
||||||
@@ -652,7 +777,7 @@ class MainScreenPresentation(
|
|||||||
currentFood!!,
|
currentFood!!,
|
||||||
// userNutritionData!!,
|
// userNutritionData!!,
|
||||||
userNutrition!!,
|
userNutrition!!,
|
||||||
weight,
|
weight.toDouble(),
|
||||||
dinnerType = dinnerType ?: ""
|
dinnerType = dinnerType ?: ""
|
||||||
)
|
)
|
||||||
// Timber.tag(TAG).d("calculateNutrition calcResultInfo = $calcResultInfo")
|
// Timber.tag(TAG).d("calculateNutrition calcResultInfo = $calcResultInfo")
|
||||||
@@ -666,6 +791,15 @@ class MainScreenPresentation(
|
|||||||
val fruitsVegetables = energy.fruitsVegetables
|
val fruitsVegetables = energy.fruitsVegetables
|
||||||
val meatEggs = energy.meatEggs
|
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
|
val include = binding.nutritionInclude
|
||||||
|
|
||||||
// 设置热量---------------------------------------------------------------------
|
// 设置热量---------------------------------------------------------------------
|
||||||
@@ -684,7 +818,7 @@ class MainScreenPresentation(
|
|||||||
// 避免0作为被除数
|
// 避免0作为被除数
|
||||||
maxKcal = if (maxKcal <= 0.0) 1.0 else maxKcal
|
maxKcal = if (maxKcal <= 0.0) 1.0 else maxKcal
|
||||||
Timber.tag(TAG).d("maxKcal 1 = $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()
|
var totalKcalHeight = (totalKcal / maxKcal).toFloat()
|
||||||
if (totalKcalHeight == 0F) {
|
if (totalKcalHeight == 0F) {
|
||||||
include.divKcalView.visible()
|
include.divKcalView.visible()
|
||||||
@@ -702,7 +836,7 @@ class MainScreenPresentation(
|
|||||||
)
|
)
|
||||||
val grainIndex = UserNutritionUtils.findCalorieIndex(energy.grain, grainArray)
|
val grainIndex = UserNutritionUtils.findCalorieIndex(energy.grain, grainArray)
|
||||||
include.customFoodColumn.setImageDrawable(false, grainIndex)
|
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
|
columnMax = if (columnMax <= 0.0) 1.0 else columnMax
|
||||||
var grainHeight = (grain / columnMax).toFloat()
|
var grainHeight = (grain / columnMax).toFloat()
|
||||||
if (grainHeight == 0F) {
|
if (grainHeight == 0F) {
|
||||||
@@ -722,7 +856,7 @@ class MainScreenPresentation(
|
|||||||
val fruitsIndex =
|
val fruitsIndex =
|
||||||
UserNutritionUtils.findCalorieIndex(energy.fruitsVegetables, fruitsArray)
|
UserNutritionUtils.findCalorieIndex(energy.fruitsVegetables, fruitsArray)
|
||||||
include.customVegetableColumn.setImageDrawable(false, fruitsIndex)
|
include.customVegetableColumn.setImageDrawable(false, fruitsIndex)
|
||||||
include.totalVegetableTv.text = fruitsVegetables.format2String(2)
|
include.totalVegetableTv.text = fruitsVegetables.roundedDecimalPlace(1).toString()
|
||||||
var fruitsHeight = (fruitsVegetables / columnMax).toFloat()
|
var fruitsHeight = (fruitsVegetables / columnMax).toFloat()
|
||||||
if (fruitsHeight == 0F) {
|
if (fruitsHeight == 0F) {
|
||||||
include.divVegetableView.visible()
|
include.divVegetableView.visible()
|
||||||
@@ -734,14 +868,14 @@ class MainScreenPresentation(
|
|||||||
|
|
||||||
// 设置肉蛋-----------------------------------------------------------
|
// 设置肉蛋-----------------------------------------------------------
|
||||||
val meatEggsArray = UserNutritionUtils.getCalorieArray(
|
val meatEggsArray = UserNutritionUtils.getCalorieArray(
|
||||||
left = userNutrition?.meatEggsRecommend,
|
left = userNutrition?.meatEggsBeansRecommend,
|
||||||
mid = userNutrition?.meatEggsNearExcess,
|
mid = userNutrition?.meatEggsBeansNearExcess,
|
||||||
right = userNutrition?.meatEggsExcess
|
right = userNutrition?.meatEggsBeansExcess
|
||||||
)
|
)
|
||||||
val meatEggsIndex =
|
val meatEggsIndex =
|
||||||
UserNutritionUtils.findCalorieIndex(energy.meatEggs, meatEggsArray)
|
UserNutritionUtils.findCalorieIndex(energy.meatEggs, meatEggsArray)
|
||||||
include.customMeatColumn.setImageDrawable(false, meatEggsIndex)
|
include.customMeatColumn.setImageDrawable(false, meatEggsIndex)
|
||||||
include.totalMeatTv.text = meatEggs.format2String(2)
|
include.totalMeatTv.text = meatEggs.roundedDecimalPlace(1).toString()
|
||||||
var meatHeight = (meatEggs / columnMax).toFloat()
|
var meatHeight = (meatEggs / columnMax).toFloat()
|
||||||
if (meatHeight == 0F) {
|
if (meatHeight == 0F) {
|
||||||
include.divMeatView.visible()
|
include.divMeatView.visible()
|
||||||
@@ -765,6 +899,7 @@ class MainScreenPresentation(
|
|||||||
*/
|
*/
|
||||||
fun updateMealPickupMode(mode: Int) {
|
fun updateMealPickupMode(mode: Int) {
|
||||||
Timber.tag(TAG).d("updateMealPickupMode mode = $mode")
|
Timber.tag(TAG).d("updateMealPickupMode mode = $mode")
|
||||||
|
Timber.tag(TAG).e("切回step1,------2000000000003")
|
||||||
mealPickupMode = mode
|
mealPickupMode = mode
|
||||||
step1FoodRecognizing()
|
step1FoodRecognizing()
|
||||||
}
|
}
|
||||||
@@ -785,7 +920,16 @@ class MainScreenPresentation(
|
|||||||
fun resumeCamera() {
|
fun resumeCamera() {
|
||||||
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
|
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
|
||||||
isRecognition = true
|
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()
|
rgbCameraHelper!!.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -795,6 +939,23 @@ class MainScreenPresentation(
|
|||||||
isRecognition = false
|
isRecognition = false
|
||||||
|
|
||||||
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
|
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
|
private var recognizeTime = 0L
|
||||||
@@ -842,23 +1003,30 @@ class MainScreenPresentation(
|
|||||||
|
|
||||||
recognizeViewModel.recognizeUserId.observe(
|
recognizeViewModel.recognizeUserId.observe(
|
||||||
activity,
|
activity,
|
||||||
Observer { compareResult: CompareResult ->
|
Observer { result: CompareResult? ->
|
||||||
|
if (result == null) return@Observer
|
||||||
if (currentStep != 2 || System.currentTimeMillis() - recognizeTime <= 2000) {
|
if (currentStep != 2 || System.currentTimeMillis() - recognizeTime <= 2000) {
|
||||||
//忽略非识别中及两秒内的再次识别
|
//忽略非识别中及5秒内的再次识别
|
||||||
return@Observer
|
return@Observer
|
||||||
}
|
}
|
||||||
|
// if (isGoRecognize.not()) {
|
||||||
|
// return@Observer
|
||||||
|
// }
|
||||||
recognizeTime = System.currentTimeMillis()
|
recognizeTime = System.currentTimeMillis()
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
activity.showWaitingDialog("加载中,请稍后……")
|
activity.showWaitingDialog("加载中,请稍后……")
|
||||||
}
|
}
|
||||||
Timber.tag(TAG)
|
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()
|
recognitionTime = System.currentTimeMillis()
|
||||||
|
//识别成功时更新lastWeight,解决使用过程中给秤上放东西,导致重量错误问题
|
||||||
|
lastWeight = activity.currentWeight
|
||||||
recognitionWeight = lastWeight
|
recognitionWeight = lastWeight
|
||||||
lastFaceTrackId = compareResult.trackId
|
lastFaceTrackId = result.trackId
|
||||||
val faceEntity = compareResult.faceEntity
|
val faceEntity = result.faceEntity
|
||||||
val userId = faceEntity.userName
|
val userId = faceEntity.userName
|
||||||
currentUserId = userId
|
currentUserId = userId
|
||||||
|
isMember = faceEntity.userType == "1"
|
||||||
isAgainLoadData = false
|
isAgainLoadData = false
|
||||||
if (userId == null) return@Observer
|
if (userId == null) return@Observer
|
||||||
//ToastUtils.showToast("人脸识别成功,userId = $userId")
|
//ToastUtils.showToast("人脸识别成功,userId = $userId")
|
||||||
@@ -872,7 +1040,12 @@ class MainScreenPresentation(
|
|||||||
return@runOnUiThread
|
return@runOnUiThread
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// pauseCamera()
|
||||||
|
//先查询会员折扣
|
||||||
|
userViewModel.getMemberDiscount(userId) { discount ->
|
||||||
|
memberDiscount = discount ?: 1.0
|
||||||
step3ShowRecognizeResult(userId)
|
step3ShowRecognizeResult(userId)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
|
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
|
||||||
@@ -978,6 +1151,7 @@ class MainScreenPresentation(
|
|||||||
}
|
}
|
||||||
if (facePreviewInfoList.isNullOrEmpty()) {
|
if (facePreviewInfoList.isNullOrEmpty()) {
|
||||||
currentUserId = null
|
currentUserId = null
|
||||||
|
isMember = false
|
||||||
}
|
}
|
||||||
//recognizeViewModel.clearLeftFace(facePreviewInfoList)
|
//recognizeViewModel.clearLeftFace(facePreviewInfoList)
|
||||||
clearLeftFaceData()
|
clearLeftFaceData()
|
||||||
@@ -1135,52 +1309,101 @@ class MainScreenPresentation(
|
|||||||
|
|
||||||
val listIsEmpty = facePreviewInfoList.isEmpty()
|
val listIsEmpty = facePreviewInfoList.isEmpty()
|
||||||
val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0].trackId else null
|
val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0].trackId else null
|
||||||
Timber.tag(TAG)
|
// Timber.tag(TAG)
|
||||||
.d("listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId")
|
// .d("drawPreviewInfo,listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId")
|
||||||
if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) {
|
if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) {
|
||||||
if (lastFaceTrackId != -1 && currentUserId != null) {
|
if (lastFaceTrackId != -1 && currentUserId != null) {
|
||||||
|
//0-即放即取,1-余量计量
|
||||||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||||
Timber.tag(TAG).i("$lastFaceTrackId 用户离开")
|
Timber.tag(TAG).i("drawPreviewInfo,$lastFaceTrackId 用户离开")
|
||||||
lastFaceTrackId = -1
|
lastFaceTrackId = -1
|
||||||
Timber.tag(TAG)
|
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) {
|
if (userNutrition == null || currentFood == null || currentUserId == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//重置人脸
|
||||||
|
recognizeViewModel.resetFaceState()
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,开始提交订单")
|
||||||
createOrder(notEnoughOneBlock = {
|
createOrder(notEnoughOneBlock = {
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock")
|
||||||
|
if (activity.settlementMode == 0) {
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock,联合模式")
|
||||||
|
setTopInfoVisible()
|
||||||
|
}
|
||||||
// if (mealPickupMode == 0) {
|
// if (mealPickupMode == 0) {
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock,step2FaceRecognizing")
|
||||||
currentStep = 2
|
currentStep = 2
|
||||||
step2FaceRecognizing(currentFood!!)
|
step2FaceRecognizing(currentFood!!)
|
||||||
// }
|
// }
|
||||||
}) {
|
}, successBlock = {
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成")
|
||||||
// TODO: 测试人脸识别问题--------------------
|
// TODO: 测试人脸识别问题--------------------
|
||||||
//recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
|
//recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
|
||||||
if (MyApp.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
ToastUtils.showToast("订单已生成")
|
ToastUtils.showToast("订单已生成")
|
||||||
}
|
}
|
||||||
currentUserId = null
|
currentUserId = null
|
||||||
|
isMember = false
|
||||||
//ToastUtils.showToast("isKeepFaceState=$isKeepFaceState,mealPickupMode=$mealPickupMode")
|
//ToastUtils.showToast("isKeepFaceState=$isKeepFaceState,mealPickupMode=$mealPickupMode")
|
||||||
//|| isKeepFaceState
|
//|| isKeepFaceState
|
||||||
|
// if (activity.settlementMode == 0) {
|
||||||
|
// activity.isAnalyzing = false
|
||||||
|
// step1FoodRecognizing()
|
||||||
|
// } else {
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,mealPickupMode=$mealPickupMode")
|
||||||
if (mealPickupMode == 1) {
|
if (mealPickupMode == 1) {
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step2FaceRecognizing")
|
||||||
|
if (activity.currentWeight > MainActivity.WEIGHT_RESET_RECOGNIZE) {
|
||||||
|
//余量计量拿走秤上最后一点餐品时,不用再进行人脸识别了
|
||||||
step2FaceRecognizing(currentFood!!)
|
step2FaceRecognizing(currentFood!!)
|
||||||
|
}
|
||||||
isKeepFaceState = false
|
isKeepFaceState = false
|
||||||
|
if (activity.settlementMode == 0) {
|
||||||
|
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step2FaceRecognizing,联合支付")
|
||||||
|
setTopInfoVisible()
|
||||||
|
}
|
||||||
} else {
|
} 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
|
activity.isAnalyzing = false
|
||||||
step1FoodRecognizing()
|
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
|
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||||
val eatWeight = if (mealPickupMode == 0) {
|
// 餐具重量(克),从就餐重量中扣减
|
||||||
|
val dishWeight = activity.getDishWeight()
|
||||||
|
//if (settlementMode == 0) {
|
||||||
|
// //使用联合支付方式
|
||||||
|
// ToastUtils.showToast("使用联合支付方式")
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
val rawEatWeight = if (mealPickupMode == 0) {
|
||||||
//即放即取
|
//即放即取
|
||||||
recognitionWeight
|
recognitionWeight
|
||||||
} else {
|
} else {
|
||||||
recognitionWeight - lastWeight
|
recognitionWeight - lastWeight
|
||||||
}
|
}
|
||||||
|
// 扣减餐具重量(不足则归零),foodWeight 保持不变
|
||||||
|
val eatWeight = (rawEatWeight - dishWeight).coerceAtLeast(0)
|
||||||
|
activity.log("createOrder,rawEatWeight=$rawEatWeight,dishWeight=$dishWeight,eatWeight=$eatWeight")
|
||||||
//val userNutritionParam = UserNutritionParam(
|
//val userNutritionParam = UserNutritionParam(
|
||||||
// userId = userNutritionData?.userId!!,
|
// userId = userNutritionData?.userId!!,
|
||||||
// foodId = currentFood?.foodId!!,
|
// foodId = currentFood?.foodId!!,
|
||||||
@@ -1190,16 +1413,21 @@ class MainScreenPresentation(
|
|||||||
// foodWeight = lastWeight
|
// foodWeight = lastWeight
|
||||||
//)
|
//)
|
||||||
//userViewModel.postUserNutritionData(listOf(userNutritionParam))
|
//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(
|
activity.createOrder(
|
||||||
foodInfo = currentFood!!,
|
foodInfo = currentFood!!,
|
||||||
foodWeight = lastWeight.roundToInt(),
|
foodWeight = lastWeight,
|
||||||
eatWeight = eatWeight.roundToInt(),
|
eatWeight = eatWeight,
|
||||||
eatNum = activity.getEatNum(eatWeight, currentFood!!.specWeight),
|
eatNum = eatNum,
|
||||||
userId = currentUserId,
|
userId = currentUserId,
|
||||||
|
isMember = isMember,
|
||||||
notEnoughOneBlock = notEnoughOneBlock,
|
notEnoughOneBlock = notEnoughOneBlock,
|
||||||
block = {
|
successBlock = {
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
block()
|
successBlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1244,4 +1472,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)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package com.sw.dualscreen.presentation.pay
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
|
||||||
import android.app.Presentation
|
import android.app.Presentation
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
@@ -11,24 +10,20 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.SpannedString
|
import android.text.SpannedString
|
||||||
import android.text.TextUtils
|
|
||||||
import android.text.style.AbsoluteSizeSpan
|
import android.text.style.AbsoluteSizeSpan
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.text.buildSpannedString
|
import androidx.core.text.buildSpannedString
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.arcsoft.face.ErrorInfo
|
import com.arcsoft.face.ErrorInfo
|
||||||
import com.sw.dualscreen.GlobalKey
|
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.activity.PayActivity
|
import com.sw.dualscreen.activity.PayActivity
|
||||||
import com.sw.dualscreen.databinding.PresentationFacePayBinding
|
|
||||||
import com.sw.dualscreen.databinding.PresentationPayBinding
|
import com.sw.dualscreen.databinding.PresentationPayBinding
|
||||||
import com.sw.dualscreen.ext.dp
|
import com.sw.dualscreen.ext.dp
|
||||||
import com.sw.dualscreen.ext.format2String
|
import com.sw.dualscreen.ext.format2String
|
||||||
@@ -38,10 +33,8 @@ import com.sw.dualscreen.ext.load
|
|||||||
import com.sw.dualscreen.ext.visible
|
import com.sw.dualscreen.ext.visible
|
||||||
import com.sw.dualscreen.model.response.MemberInfo
|
import com.sw.dualscreen.model.response.MemberInfo
|
||||||
import com.sw.dualscreen.model.response.TextBean
|
import com.sw.dualscreen.model.response.TextBean
|
||||||
import com.sw.dualscreen.utils.SPUtil
|
|
||||||
import com.sw.dualscreen.utils.SpannedUtils
|
import com.sw.dualscreen.utils.SpannedUtils
|
||||||
import com.sw.dualscreen.view.CustomDialog
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import com.sw.plate.utils.arcface.ConfigUtil
|
import com.sw.plate.utils.arcface.ConfigUtil
|
||||||
import com.sw.plate.utils.arcface.ErrorCodeUtil
|
import com.sw.plate.utils.arcface.ErrorCodeUtil
|
||||||
@@ -58,11 +51,12 @@ 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
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class PayPresentation(
|
class PayPresentation(
|
||||||
val activity: PayActivity,
|
val activity: PayActivity,
|
||||||
display: Display,
|
display: Display,
|
||||||
val userViewModel: UserViewModel,
|
val userViewModel: NetViewModelV2,
|
||||||
val recognizeViewModel: RecognizeViewModel,
|
val recognizeViewModel: RecognizeViewModel,
|
||||||
private val onDismissListener: () -> Unit = {}
|
private val onDismissListener: () -> Unit = {}
|
||||||
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
@@ -299,13 +293,16 @@ class PayPresentation(
|
|||||||
.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}")
|
.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}")
|
||||||
// recognitionTime = System.currentTimeMillis()
|
// recognitionTime = System.currentTimeMillis()
|
||||||
// recognitionWeight = lastWeight
|
// recognitionWeight = lastWeight
|
||||||
|
if (isExistNotPayOrder.get()) return@Observer
|
||||||
lastFaceTrackId = compareResult.trackId
|
lastFaceTrackId = compareResult.trackId
|
||||||
val faceEntity = compareResult.faceEntity
|
val faceEntity = compareResult.faceEntity
|
||||||
val userId = faceEntity.userName
|
val userId = faceEntity.userName
|
||||||
val isMember = faceEntity.userType == "1"
|
val isMember = faceEntity.userType == "1"
|
||||||
if (userId == null) return@Observer
|
activity.isMember = isMember
|
||||||
|
if (userId.isNullOrBlank()) return@Observer
|
||||||
// TODO: 测试支付用户id
|
// TODO: 测试支付用户id
|
||||||
// userId = "1987710988425662466"
|
// userId = "1987710988425662466"
|
||||||
|
isExistNotPayOrder.set(true)
|
||||||
faceRecSuccess(userId, isMember)
|
faceRecSuccess(userId, isMember)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -314,6 +311,7 @@ class PayPresentation(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isExistNotPayOrder = AtomicBoolean(false)
|
||||||
|
|
||||||
private fun initArcView() {
|
private fun initArcView() {
|
||||||
//在布局结束后才做初始化操作
|
//在布局结束后才做初始化操作
|
||||||
@@ -605,6 +603,7 @@ class PayPresentation(
|
|||||||
//ToastUtils.showToast("订单绑定失败")
|
//ToastUtils.showToast("订单绑定失败")
|
||||||
return@bindOrder
|
return@bindOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.root.postDelayed({
|
binding.root.postDelayed({
|
||||||
activity.hideWaitingDialog()
|
activity.hideWaitingDialog()
|
||||||
// TODO: 测试 memberInfo.member是否返回正常数据---------------
|
// TODO: 测试 memberInfo.member是否返回正常数据---------------
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.sw.dualscreen.model.response.FaceData
|
|||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.model.response.FoodSearchReq
|
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||||
import com.sw.dualscreen.model.response.FoodOrder
|
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.FoodVector
|
||||||
import com.sw.dualscreen.model.response.MemberInfo
|
import com.sw.dualscreen.model.response.MemberInfo
|
||||||
import com.sw.dualscreen.model.response.UserFaceModel
|
import com.sw.dualscreen.model.response.UserFaceModel
|
||||||
@@ -76,6 +77,7 @@ class RemoteRepository constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸数据
|
* 获取人脸数据
|
||||||
*/
|
*/
|
||||||
@@ -134,16 +136,18 @@ class RemoteRepository constructor(
|
|||||||
suspend fun getRestInfoFoodsByType(
|
suspend fun getRestInfoFoodsByType(
|
||||||
foodName: String,
|
foodName: String,
|
||||||
pageNum: Int = 1,
|
pageNum: Int = 1,
|
||||||
pageSize:Int = 50
|
pageSize: Int = 50
|
||||||
): ApiResponse<List<FoodInfo>> {
|
): ApiResponse<List<FoodInfo>> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.getRestInfoFoodsByType(param = hashMapOf(
|
apiService.getRestInfoFoodsByType(
|
||||||
|
param = hashMapOf(
|
||||||
"name" to foodName,
|
"name" to foodName,
|
||||||
"pageNum" to "$pageNum",
|
"pageNum" to "$pageNum",
|
||||||
"pageSize" to "$pageSize",
|
"pageSize" to "$pageSize",
|
||||||
//"订单来源权举:1-营养秤,2-档口
|
//"订单来源权举:1-营养秤,2-档口
|
||||||
"deviceType" to "2"
|
"deviceType" to "2"
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,9 +193,20 @@ class RemoteRepository constructor(
|
|||||||
/**
|
/**
|
||||||
* 二维码支付
|
* 二维码支付
|
||||||
*/
|
*/
|
||||||
suspend fun qrCodePay(authCode: String,orderNo: String): ApiResponse<String?> {
|
suspend fun qrCodePay(
|
||||||
|
authCode: String,
|
||||||
|
orderNo: String,
|
||||||
|
memberId: String?
|
||||||
|
): ApiResponse<String?> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.qrCodePay(authCode = authCode, orderNo=orderNo, orderType = 0)
|
apiService.qrCodePay(
|
||||||
|
param = hashMapOf(
|
||||||
|
"authCode" to authCode,
|
||||||
|
"outTradeNo" to orderNo,
|
||||||
|
"memberId" to memberId,
|
||||||
|
"orderType" to "0"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +271,7 @@ class RemoteRepository constructor(
|
|||||||
/**
|
/**
|
||||||
* 会员支付
|
* 会员支付
|
||||||
*/
|
*/
|
||||||
suspend fun memberPay(param: HashMap<String, String>): ApiResponse<Any?> {
|
suspend fun memberPay(param: HashMap<String, String?>): ApiResponse<Any?> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.memberPay(param = param)
|
apiService.memberPay(param = param)
|
||||||
}
|
}
|
||||||
@@ -285,10 +300,12 @@ class RemoteRepository constructor(
|
|||||||
*/
|
*/
|
||||||
suspend fun getMemberInfoByPhone(phone: String, key: String): ApiResponse<MemberInfo?> {
|
suspend fun getMemberInfoByPhone(phone: String, key: String): ApiResponse<MemberInfo?> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.getMemberInfoByPhone(param = hashMapOf(
|
apiService.getMemberInfoByPhone(
|
||||||
|
param = hashMapOf(
|
||||||
"phone" to phone,
|
"phone" to phone,
|
||||||
"password" to key
|
"password" to key
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,12 +343,26 @@ class RemoteRepository constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteCollectFood(foodId:String?, version: String?): ApiResponse<Any?> {
|
suspend fun deleteCollectFood(foodId: String?, version: String?): ApiResponse<Any?> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.deleteCollectFood(param = mutableMapOf(
|
apiService.deleteCollectFood(
|
||||||
|
param = mutableMapOf(
|
||||||
"foodId" to foodId,
|
"foodId" to foodId,
|
||||||
"version" to version
|
"version" to version
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFoodOrderList(userId: String): ApiResponse<FoodOrderModel?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getFoodOrderList(userId = userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMemberDiscount(userId: String): ApiResponse<Double?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getMemberDiscount(userId = userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package com.sw.dualscreen.repository.v2
|
||||||
|
|
||||||
|
import com.sw.dualscreen.model.request.v2.BindUserOrderRequest
|
||||||
|
import com.sw.dualscreen.model.request.v2.PlaceOrderRequest
|
||||||
|
import com.sw.dualscreen.model.response.ApiResponse
|
||||||
|
import com.sw.dualscreen.model.response.DeviceConfig
|
||||||
|
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||||
|
import com.sw.dualscreen.model.response.UserNutrition
|
||||||
|
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||||
|
import com.sw.dualscreen.model.response.v2.FaceVO
|
||||||
|
import com.sw.dualscreen.model.response.v2.NewFoodInfo
|
||||||
|
import com.sw.dualscreen.model.response.v2.NewMemberInfo
|
||||||
|
import com.sw.dualscreen.model.response.v2.SettlementOrder
|
||||||
|
import com.sw.dualscreen.network.api.ApiServiceV2
|
||||||
|
import com.sw.dualscreen.repository.BaseRepository
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class RemoteRepositoryV2 constructor(
|
||||||
|
private val apiService: ApiServiceV2
|
||||||
|
) : BaseRepository() {
|
||||||
|
|
||||||
|
suspend fun getDeviceConfig(): ApiResponse<DeviceConfig?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getDeviceConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFacePage(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = 100L
|
||||||
|
): ApiResponse<List<FaceVO>?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getFacePage(
|
||||||
|
request = mapOf(
|
||||||
|
"pageNum" to pageNum,
|
||||||
|
"pageSize" to pageSize
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFaceIncrement(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = 100L,
|
||||||
|
timestamp: Long
|
||||||
|
): ApiResponse<List<FaceVO>?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getFaceIncrement(
|
||||||
|
request = mapOf(
|
||||||
|
"pageNum" to pageNum,
|
||||||
|
"pageSize" to pageSize,
|
||||||
|
"timestamp" to timestamp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFoodByNames(
|
||||||
|
nameList: List<String>,
|
||||||
|
deviceType: Int = 2
|
||||||
|
): ApiResponse<List<NewFoodInfo>?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getFoodByNames(
|
||||||
|
request = FoodSearchReq(nameList, deviceType)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun searchFood(
|
||||||
|
name: String,
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = 100L
|
||||||
|
): ApiResponse<List<NewFoodInfo>?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.searchFood(
|
||||||
|
request = mapOf(
|
||||||
|
"name" to name,
|
||||||
|
"pageNum" to pageNum,
|
||||||
|
"pageSize" to pageSize
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUserCurrentFood(
|
||||||
|
userId: Long
|
||||||
|
): ApiResponse<UserNutrition?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getUserCurrentFood(
|
||||||
|
request = mapOf("id" to userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun placeOrder(
|
||||||
|
orderRequest: PlaceOrderRequest
|
||||||
|
): ApiResponse<String?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.placeOrder(request = orderRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSettlementOrders(
|
||||||
|
userId: Long
|
||||||
|
): ApiResponse<SettlementOrder?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getSettlementOrders(
|
||||||
|
request = mapOf("id" to userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMemberInfo(
|
||||||
|
userId: Long
|
||||||
|
): ApiResponse<NewMemberInfo?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getMemberInfo(
|
||||||
|
request = mapOf("id" to userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMemberInfoByPhone(
|
||||||
|
phone: String,
|
||||||
|
password: String = ""
|
||||||
|
): ApiResponse<NewMemberInfo?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getMemberInfoByPhone(
|
||||||
|
request = mapOf(
|
||||||
|
"phone" to phone,
|
||||||
|
"password" to password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMemberDiscount(
|
||||||
|
userId: Long
|
||||||
|
): ApiResponse<String?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getMemberDiscount(
|
||||||
|
request = mapOf("id" to userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun bindUserOrder(
|
||||||
|
userId: Long,
|
||||||
|
orderNo: String,
|
||||||
|
mode: Int? = null
|
||||||
|
): ApiResponse<Any?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.bindUserOrder(
|
||||||
|
request = BindUserOrderRequest(userId, orderNo, mode)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCollectPage(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = 100L,
|
||||||
|
foodName: String? = null
|
||||||
|
): ApiResponse<List<CollectedFoodV2>?> {
|
||||||
|
return safeApiCall {
|
||||||
|
val request = mutableMapOf<String, Any>(
|
||||||
|
"pageNum" to pageNum,
|
||||||
|
"pageSize" to pageSize
|
||||||
|
)
|
||||||
|
if (!foodName.isNullOrEmpty()) {
|
||||||
|
request["foodName"] = foodName
|
||||||
|
}
|
||||||
|
apiService.getCollectPage(request = request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCollectVectorPage(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = 100L
|
||||||
|
): ApiResponse<List<CollectedFoodV2>?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.getCollectVectorPage(
|
||||||
|
request = mutableMapOf(
|
||||||
|
"pageNum" to pageNum,
|
||||||
|
"pageSize" to pageSize
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun uploadCollect(
|
||||||
|
foodId: Long,
|
||||||
|
foodName: String,
|
||||||
|
version: String,
|
||||||
|
foodVector: String,
|
||||||
|
fileList: List<File>
|
||||||
|
): ApiResponse<List<String>?> {
|
||||||
|
val params = hashMapOf<String, RequestBody>()
|
||||||
|
params["foodId"] = foodId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
params["foodName"] = foodName.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
params["version"] = version.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
params["foodVector"] = foodVector.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
|
||||||
|
val fileParts = mutableListOf<MultipartBody.Part>()
|
||||||
|
fileList.forEach { file ->
|
||||||
|
val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
|
||||||
|
val filePart = MultipartBody.Part.createFormData(
|
||||||
|
"foodPics",
|
||||||
|
file.name,
|
||||||
|
requestFile
|
||||||
|
)
|
||||||
|
fileParts.add(filePart)
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.uploadCollect(params = params, foodPics = fileParts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteCollect(
|
||||||
|
foodId: Long,
|
||||||
|
version: String
|
||||||
|
): ApiResponse<Any?> {
|
||||||
|
return safeApiCall {
|
||||||
|
apiService.deleteCollect(
|
||||||
|
request = mapOf<String, Any>(
|
||||||
|
"foodId" to foodId,
|
||||||
|
"version" to version
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import timber.log.Timber
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
typealias Callback = (Double) -> Unit
|
typealias Callback = (Double) -> Unit
|
||||||
typealias WeightCallback = (Double) -> Unit
|
typealias WeightCallback = (Int) -> Unit
|
||||||
|
|
||||||
private const val TAG = "SensorScaleUtils"
|
private const val TAG = "SensorScaleUtils"
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ object SensorScaleUtils {
|
|||||||
var isZero = false
|
var isZero = false
|
||||||
private var callback: Callback? = {}
|
private var callback: Callback? = {}
|
||||||
private var lastWeight: Double? = null
|
private var lastWeight: Double? = null
|
||||||
|
private var lastCallbackWeight: Double? = null
|
||||||
|
|
||||||
private fun init() {
|
private fun init() {
|
||||||
mSensorScale = SensorScale(object : OnScaleResult {
|
mSensorScale = SensorScale(object : OnScaleResult {
|
||||||
@@ -31,21 +32,28 @@ object SensorScaleUtils {
|
|||||||
* 读取重量
|
* 读取重量
|
||||||
*/
|
*/
|
||||||
override fun readWeight(state: Int, value: Double) {
|
override fun readWeight(state: Int, value: Double) {
|
||||||
val stateStr = when (state) {
|
// val stateStr = when (state) {
|
||||||
SensorScale.STATE_STABLE -> "稳定"
|
// SensorScale.STATE_STABLE -> "稳定"
|
||||||
SensorScale.STATE_UNSTABLE -> "不稳定"
|
// SensorScale.STATE_UNSTABLE -> "不稳定"
|
||||||
SensorScale.STATE_OVER_WEIGHT -> "量程溢出"
|
// SensorScale.STATE_OVER_WEIGHT -> "量程溢出"
|
||||||
else -> "未知"
|
// else -> "未知"
|
||||||
}
|
// }
|
||||||
// Log.d(TAG, "readWeight state = ${stateStr}, weight = $value")
|
// 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
|
lastWeight = value
|
||||||
callback?.invoke(value)
|
callback?.invoke(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 去重:仅在重量变化时回调 weightCallbackList,避免同一重量反复触发
|
||||||
|
if (lastCallbackWeight != value) {
|
||||||
|
lastCallbackWeight = value
|
||||||
weightCallbackList.forEach {
|
weightCallbackList.forEach {
|
||||||
it.invoke(value)
|
val currentWeight = value * 1000
|
||||||
|
it.invoke(currentWeight.roundToInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +95,7 @@ object SensorScaleUtils {
|
|||||||
// Thread.sleep(1000)
|
// Thread.sleep(1000)
|
||||||
|
|
||||||
// TODO: 暂时不标定---------
|
// TODO: 暂时不标定---------
|
||||||
//zero()
|
zero()
|
||||||
|
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,15 +137,17 @@ object SensorScaleUtils {
|
|||||||
/**
|
/**
|
||||||
* 零位标定
|
* 零位标定
|
||||||
*/
|
*/
|
||||||
fun zero() {
|
fun zero(isShowToastRemind: Boolean = false) {
|
||||||
Timber.d("zero isOpened = $isOpened, mSensorScale = $mSensorScale")
|
Timber.d("zero isOpened = $isOpened, mSensorScale = $mSensorScale")
|
||||||
if (!isOpened) return
|
if (!isOpened) return
|
||||||
mSensorScale?.zero {
|
mSensorScale?.zero {
|
||||||
isZero = true
|
isZero = true
|
||||||
Timber.d("zero 零位标定操作成功")
|
Timber.d("zero 零位标定操作成功")
|
||||||
|
if (isShowToastRemind) {
|
||||||
ToastUtils.showToast("零位标定操作成功")
|
ToastUtils.showToast("零位标定操作成功")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 去皮置零
|
* 去皮置零
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class CameraUtils(private var activity: ComponentActivity) {
|
|||||||
photoCaptureHelper = PhotoCaptureHelper(
|
photoCaptureHelper = PhotoCaptureHelper(
|
||||||
context = activity,
|
context = activity,
|
||||||
cameraController = cameraController!!,
|
cameraController = cameraController!!,
|
||||||
onSuccess = {},
|
|
||||||
onError = { msg ->
|
onError = { msg ->
|
||||||
//toast(msg)
|
//toast(msg)
|
||||||
failCallback?.invoke(msg)
|
failCallback?.invoke(msg)
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.sw.dualscreen.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import com.sw.plate.utils.arcface.facedb.FaceDatabase;
|
||||||
|
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 assets 目录下的测试人脸数据库导入数据到 Room 数据库
|
||||||
|
*
|
||||||
|
* <p>以分批方式读取,每批 {@link #BATCH_SIZE} 条,边读边写,
|
||||||
|
* 避免 4350 条 + 17MB 特征数据一次性加载到内存。</p>
|
||||||
|
*/
|
||||||
|
public class FaceDbImporter {
|
||||||
|
|
||||||
|
private static final String TAG = "FaceDbImporter";
|
||||||
|
|
||||||
|
/** 每批写入条数 */
|
||||||
|
private static final int BATCH_SIZE = 200;
|
||||||
|
|
||||||
|
/** assets 中测试数据库的相对路径 */
|
||||||
|
private static final String ASSETS_DB_PATH = "db/faceDB.db";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入回调
|
||||||
|
*/
|
||||||
|
public interface ImportCallback {
|
||||||
|
/** 进度更新,已切到主线程 */
|
||||||
|
void onProgress(int current, int total);
|
||||||
|
/** 导入失败,已切到主线程 */
|
||||||
|
void onError(String message);
|
||||||
|
/** 导入完成,已切到主线程 */
|
||||||
|
void onComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FaceDbImporter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 assets 导入测试人脸数据到 Room 数据库
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
* @param callback 回调(所有回调均已在主线程)
|
||||||
|
*/
|
||||||
|
public static void importFromAssets(Context context, ImportCallback callback) {
|
||||||
|
Context appContext = context.getApplicationContext();
|
||||||
|
new Thread(() -> {
|
||||||
|
File tempFile = null;
|
||||||
|
SQLiteDatabase testDb = null;
|
||||||
|
Cursor cursor = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 从 assets 复制到缓存目录(SQLiteDatabase 需要文件路径)
|
||||||
|
tempFile = new File(appContext.getCacheDir(), "faceDB_import_temp.db");
|
||||||
|
copyAssetToFile(appContext, ASSETS_DB_PATH, tempFile);
|
||||||
|
|
||||||
|
// 2. 以只读方式打开测试数据库
|
||||||
|
testDb = SQLiteDatabase.openDatabase(
|
||||||
|
tempFile.getAbsolutePath(),
|
||||||
|
null,
|
||||||
|
SQLiteDatabase.OPEN_READONLY
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. 查询总记录数
|
||||||
|
int totalCount = 0;
|
||||||
|
Cursor countCursor = testDb.rawQuery("SELECT COUNT(*) FROM face", null);
|
||||||
|
if (countCursor.moveToFirst()) {
|
||||||
|
totalCount = countCursor.getInt(0);
|
||||||
|
}
|
||||||
|
countCursor.close();
|
||||||
|
|
||||||
|
if (totalCount == 0) {
|
||||||
|
postToMain(() -> {
|
||||||
|
if (callback != null) callback.onComplete();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 分批读取 + 批量写入
|
||||||
|
cursor = testDb.rawQuery(
|
||||||
|
"SELECT user_name, feature_data, register_time FROM face ORDER BY faceId",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
List<FaceEntity> batch = new ArrayList<>(BATCH_SIZE);
|
||||||
|
int processedCount = 0;
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
String userName = cursor.getString(0);
|
||||||
|
byte[] featureData = cursor.getBlob(1);
|
||||||
|
long registerTime = cursor.getLong(2);
|
||||||
|
|
||||||
|
FaceEntity entity = new FaceEntity(userName, null, featureData);
|
||||||
|
entity.setUserType("2");
|
||||||
|
entity.setRegisterTime(registerTime);
|
||||||
|
batch.add(entity);
|
||||||
|
|
||||||
|
if (batch.size() >= BATCH_SIZE) {
|
||||||
|
FaceDatabase.getInstance(appContext).faceDao().insert(batch);
|
||||||
|
processedCount += batch.size();
|
||||||
|
notifyProgress(callback, processedCount, totalCount);
|
||||||
|
batch.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 写入剩余不足一批的数据
|
||||||
|
if (!batch.isEmpty()) {
|
||||||
|
FaceDatabase.getInstance(appContext).faceDao().insert(batch);
|
||||||
|
processedCount += batch.size();
|
||||||
|
notifyProgress(callback, processedCount, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
L.d(TAG, "导入完成,共 " + processedCount + " 条记录");
|
||||||
|
postToMain(() -> {
|
||||||
|
if (callback != null) callback.onComplete();
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
L.e(TAG, "导入失败: " + e.getMessage());
|
||||||
|
postToMain(() -> {
|
||||||
|
if (callback != null) callback.onError(e.getMessage());
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) cursor.close();
|
||||||
|
if (testDb != null && testDb.isOpen()) testDb.close();
|
||||||
|
if (tempFile != null && tempFile.exists()) tempFile.delete();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 assets 复制文件到指定路径
|
||||||
|
*/
|
||||||
|
private static void copyAssetToFile(Context context, String assetPath, File destFile)
|
||||||
|
throws Exception {
|
||||||
|
try (InputStream is = context.getAssets().open(assetPath);
|
||||||
|
FileOutputStream fos = new FileOutputStream(destFile)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = is.read(buffer)) > 0) {
|
||||||
|
fos.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
fos.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void notifyProgress(ImportCallback callback, int current, int total) {
|
||||||
|
postToMain(() -> {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onProgress(current, total);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void postToMain(Runnable runnable) {
|
||||||
|
new Handler(Looper.getMainLooper()).post(runnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,11 +19,18 @@ import java.io.StringReader;
|
|||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class FileUtil {
|
public class FileUtil {
|
||||||
private static boolean isSaveLog = true;
|
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_B = 1;//获取文件大小单位为B的double值
|
||||||
public static final int SIZETYPE_KB = 2;//获取文件大小单位为KB的double值
|
public static final int SIZETYPE_KB = 2;//获取文件大小单位为KB的double值
|
||||||
public static final int SIZETYPE_MB = 3;//获取文件大小单位为MB的double值
|
public static final int SIZETYPE_MB = 3;//获取文件大小单位为MB的double值
|
||||||
@@ -610,24 +617,19 @@ public class FileUtil {
|
|||||||
if (!isSaveLog) {
|
if (!isSaveLog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new Thread() {
|
// 提交到单线程队列,串行执行,避免并发写入竞争
|
||||||
@Override
|
LOG_EXECUTOR.execute(() -> {
|
||||||
public void run() {
|
|
||||||
super.run();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (String log : logs) {
|
for (String log : logs) {
|
||||||
sb.append("===")
|
sb.append("===")
|
||||||
.append((AppUtil.formatDateGetCurrentTime()))
|
.append((AppUtil.formatDateGetCurrentTime()))
|
||||||
.append("===")
|
.append("===")
|
||||||
.append("\n")
|
|
||||||
.append(log)
|
.append(log)
|
||||||
.append("\n");
|
.append("\n");
|
||||||
}
|
}
|
||||||
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
|
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
|
||||||
FileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
|
FileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
|
||||||
|
});
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String[][] MIME_MapTable = {
|
private static final String[][] MIME_MapTable = {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
|
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||||
|
import com.sw.dualscreen.objbox.Food
|
||||||
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
object FoodVectorTool {
|
||||||
|
|
||||||
|
/** 每页拉取条数 */
|
||||||
|
private const val PAGE_SIZE = 100
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一入口:分页拉取所有食物向量数据并保存到本地 ObjectBox
|
||||||
|
*/
|
||||||
|
fun loadAndSaveFoodVector(
|
||||||
|
userViewModel: NetViewModelV2,
|
||||||
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
|
successBlock: () -> Unit,
|
||||||
|
failureBlock: (String) -> Unit
|
||||||
|
) {
|
||||||
|
fetchPage(
|
||||||
|
pageNum = 1,
|
||||||
|
userViewModel = userViewModel,
|
||||||
|
lifecycleScope = lifecycleScope,
|
||||||
|
successBlock = successBlock,
|
||||||
|
failureBlock = failureBlock
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchPage(
|
||||||
|
pageNum: Int,
|
||||||
|
userViewModel: NetViewModelV2,
|
||||||
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
|
successBlock: () -> Unit,
|
||||||
|
failureBlock: (String) -> Unit
|
||||||
|
) {
|
||||||
|
userViewModel.getCollectVectorPage(
|
||||||
|
pageNum = pageNum.toLong(),
|
||||||
|
pageSize = PAGE_SIZE.toLong(),
|
||||||
|
onSuccess = { items ->
|
||||||
|
if (pageNum == 1 && items.isEmpty()) {
|
||||||
|
successBlock()
|
||||||
|
return@getCollectVectorPage
|
||||||
|
}
|
||||||
|
if (items.isNotEmpty()) {
|
||||||
|
saveFoodVector(lifecycleScope, items)
|
||||||
|
}
|
||||||
|
if (items.size >= PAGE_SIZE) {
|
||||||
|
fetchPage(pageNum + 1, userViewModel, lifecycleScope, successBlock, failureBlock)
|
||||||
|
} else {
|
||||||
|
successBlock()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { failureBlock(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 CollectedFoodV2 列表保存到 ObjectBox 本地数据库
|
||||||
|
*/
|
||||||
|
private fun saveFoodVector(lifecycleScope: LifecycleCoroutineScope, list: List<CollectedFoodV2>) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val vectorList = list.map { item ->
|
||||||
|
val foodVector = item.foodVector?.removeSurrounding("[", "]")
|
||||||
|
?.split(",")?.map { it.toFloatOrNull() ?: 0.0f }?.toFloatArray()
|
||||||
|
Food(
|
||||||
|
collectId = item.foodId,
|
||||||
|
foodId = item.foodId,
|
||||||
|
foodName = item.foodName,
|
||||||
|
version = item.version,
|
||||||
|
foodVector = foodVector
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ObjectBox.putAll(vectorList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,17 +15,23 @@ object ImageUtil {
|
|||||||
|
|
||||||
fun uriToBitmap(context: Context, uri: Uri): Bitmap? {
|
fun uriToBitmap(context: Context, uri: Uri): Bitmap? {
|
||||||
return try {
|
return try {
|
||||||
val options = BitmapFactory.Options()
|
val options = BitmapFactory.Options().apply {
|
||||||
//options.inSampleSize = 2; // 这会将图片的尺寸缩小到原来的1/2
|
inJustDecodeBounds = true
|
||||||
options.inJustDecodeBounds = false
|
}
|
||||||
// options.inPreferredConfig = Bitmap.Config.ARGB_8888
|
// 先只读尺寸,计算合适的采样率
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565
|
|
||||||
context.contentResolver.openInputStream(uri)?.use { stream ->
|
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||||
BitmapFactory.decodeStream(stream, null, options)
|
BitmapFactory.decodeStream(stream, null, options)
|
||||||
// BitmapFactory.decodeStream(stream)
|
}
|
||||||
|
// 目标最长边不超过 800px,减少内存占用
|
||||||
|
val maxDim = maxOf(options.outWidth, options.outHeight)
|
||||||
|
val sampleSize = (maxDim / 800).coerceAtLeast(1)
|
||||||
|
options.apply {
|
||||||
|
inJustDecodeBounds = false
|
||||||
|
inSampleSize = sampleSize
|
||||||
|
inPreferredConfig = Bitmap.Config.RGB_565
|
||||||
}
|
}
|
||||||
context.contentResolver.openInputStream(uri)?.use { stream ->
|
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||||
BitmapFactory.decodeStream(stream)
|
BitmapFactory.decodeStream(stream, null, options)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,9 @@ package com.sw.dualscreen.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import androidx.camera.core.ImageCapture
|
import androidx.camera.core.ImageCapture
|
||||||
import androidx.camera.core.ImageCaptureException
|
import androidx.camera.core.ImageCaptureException
|
||||||
import androidx.camera.view.CameraController
|
import androidx.camera.view.CameraController
|
||||||
@@ -19,9 +22,9 @@ import java.io.File
|
|||||||
class PhotoCaptureHelper(
|
class PhotoCaptureHelper(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val cameraController: CameraController,
|
private val cameraController: CameraController,
|
||||||
private val onSuccess: (Uri) -> Unit = {},
|
|
||||||
private val onError: (String) -> Unit = {}
|
private val onError: (String) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf()
|
private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf()
|
||||||
fun addSuccessCallback(callback:(Uri) -> Unit) {
|
fun addSuccessCallback(callback:(Uri) -> Unit) {
|
||||||
if (callbackList.contains(callback).not()) {
|
if (callbackList.contains(callback).not()) {
|
||||||
@@ -41,7 +44,8 @@ class PhotoCaptureHelper(
|
|||||||
*/
|
*/
|
||||||
fun takePhoto(
|
fun takePhoto(
|
||||||
fileNamePrefix: String = "IMG_",
|
fileNamePrefix: String = "IMG_",
|
||||||
fileExtension: String = ".jpg"
|
fileExtension: String = ".jpg",
|
||||||
|
isRetry: Boolean = false
|
||||||
) {
|
) {
|
||||||
Timber.d("开始拍照采集")
|
Timber.d("开始拍照采集")
|
||||||
|
|
||||||
@@ -63,20 +67,22 @@ class PhotoCaptureHelper(
|
|||||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
|
val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
|
||||||
Timber.d("照片保存成功: $photoUri")
|
Timber.d("照片保存成功: $photoUri")
|
||||||
onSuccess(photoUri)
|
|
||||||
callbackList.forEach {
|
callbackList.forEach {
|
||||||
it(photoUri)
|
it(photoUri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(exception: ImageCaptureException) {
|
override fun onError(exception: ImageCaptureException) {
|
||||||
|
Timber.e(exception)
|
||||||
val errorMsg = "拍照失败: ${exception.message}"
|
val errorMsg = "拍照失败: ${exception.message}"
|
||||||
if (errorMsg.contains("Not bound to a valid Camera")) {
|
if (errorMsg.contains("Not bound to a valid Camera") && !isRetry) {
|
||||||
if (bindCamera != null) {
|
Timber.w(exception, "相机未绑定,尝试重新绑定后重试")
|
||||||
bindCamera?.invoke()
|
bindCamera?.invoke()
|
||||||
//takePhoto()
|
// 延迟重试,等待相机异步绑定完成
|
||||||
}
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
takePhoto(fileNamePrefix, fileExtension, isRetry = true)
|
||||||
|
}, 300)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
Timber.e(exception, errorMsg)
|
Timber.e(exception, errorMsg)
|
||||||
onError(errorMsg)
|
onError(errorMsg)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sw.dualscreen.utils
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
import com.sw.dualscreen.GlobalKey
|
||||||
import com.sw.dualscreen.MyApp
|
import com.sw.dualscreen.MyApp
|
||||||
import com.sw.dualscreen.utils.SPUtil.Companion.getInstance
|
import com.sw.dualscreen.utils.SPUtil.Companion.getInstance
|
||||||
|
|
||||||
@@ -16,15 +17,26 @@ object SpTool {
|
|||||||
|
|
||||||
|
|
||||||
var lastFaceTimestamp: Long
|
var lastFaceTimestamp: Long
|
||||||
get() = spUtil?.get(LAST_FACE_TIMESTAMP, 0L)?:0L
|
get() = spUtil?.get(LAST_FACE_TIMESTAMP, 0L) ?: 0L
|
||||||
set(timestamp) {
|
set(timestamp) {
|
||||||
spUtil?.put(LAST_FACE_TIMESTAMP, timestamp)
|
spUtil?.put(LAST_FACE_TIMESTAMP, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstGetFace: Boolean
|
var firstGetFace: Boolean
|
||||||
get() = spUtil?.get(IS_FIRST_GET_FACE, true)?:true
|
get() = spUtil?.get(IS_FIRST_GET_FACE, true) ?: true
|
||||||
set(value) {
|
set(value) {
|
||||||
spUtil?.boolean(IS_FIRST_GET_FACE, value)
|
spUtil?.boolean(IS_FIRST_GET_FACE, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var baseUrl: String?
|
||||||
|
get() = spUtil?.get(GlobalKey.KEY_BASE_URL, "")
|
||||||
|
set(value) {
|
||||||
|
spUtil?.put(GlobalKey.KEY_BASE_URL, value)
|
||||||
|
}
|
||||||
|
var settlementMode: Int
|
||||||
|
get() = spUtil?.get(GlobalKey.KEY_SETTLEMENT_MODE, 0) ?: 0
|
||||||
|
set(value) {
|
||||||
|
spUtil?.put(GlobalKey.KEY_SETTLEMENT_MODE, value)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import com.sw.dualscreen.databinding.BottomSheetDialogBinding
|
|||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.utils.Debouncer
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
import com.sw.dualscreen.utils.KeyboardUtils
|
import com.sw.dualscreen.utils.KeyboardUtils
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import androidx.core.graphics.drawable.toDrawable
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ import androidx.core.graphics.drawable.toDrawable
|
|||||||
*/
|
*/
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
class CustomBottomSheetDialog(
|
class CustomBottomSheetDialog(
|
||||||
val viewModel: UserViewModel,
|
val viewModel: NetViewModelV2,
|
||||||
val itemClickCallback: (FoodInfo) -> Unit
|
val itemClickCallback: (FoodInfo) -> Unit
|
||||||
) : BottomSheetDialogFragment() {
|
) : BottomSheetDialogFragment() {
|
||||||
private lateinit var binding: BottomSheetDialogBinding
|
private lateinit var binding: BottomSheetDialogBinding
|
||||||
@@ -87,7 +87,7 @@ class CustomBottomSheetDialog(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
viewModel: UserViewModel,
|
viewModel: NetViewModelV2,
|
||||||
itemClickCallback: (FoodInfo) -> Unit
|
itemClickCallback: (FoodInfo) -> Unit
|
||||||
): CustomBottomSheetDialog {
|
): CustomBottomSheetDialog {
|
||||||
return CustomBottomSheetDialog(viewModel, itemClickCallback)
|
return CustomBottomSheetDialog(viewModel, itemClickCallback)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class HollowOvalInRectView @JvmOverloads constructor(
|
|||||||
private val rectWidthDp = 400f // 矩形宽度
|
private val rectWidthDp = 400f // 矩形宽度
|
||||||
private val rectHeightDp = 510f // 矩形高度
|
private val rectHeightDp = 510f // 矩形高度
|
||||||
private val ovalWidthDp = 400f // 椭圆宽度
|
private val ovalWidthDp = 400f // 椭圆宽度
|
||||||
private val ovalHeightDp = 436f // 椭圆高度
|
private val ovalHeightDp = 416f // 椭圆高度
|
||||||
|
|
||||||
// 转换后的像素尺寸
|
// 转换后的像素尺寸
|
||||||
private val rectWidth: Float
|
private val rectWidth: Float
|
||||||
|
|||||||
@@ -0,0 +1,575 @@
|
|||||||
|
package com.sw.dualscreen.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.arcsoft.face.ErrorInfo
|
||||||
|
import com.sw.dualscreen.GlobalData
|
||||||
|
import com.sw.dualscreen.model.request.UserNutritionParam
|
||||||
|
import com.sw.dualscreen.model.request.v2.BindUserOrderRequest
|
||||||
|
import com.sw.dualscreen.model.request.v2.PlaceOrderRequest
|
||||||
|
import com.sw.dualscreen.model.response.ApiResponse
|
||||||
|
import com.sw.dualscreen.model.response.DinnerType
|
||||||
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
|
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||||
|
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||||
|
import com.sw.dualscreen.model.response.MemberInfo
|
||||||
|
import com.sw.dualscreen.model.response.UserNutrition
|
||||||
|
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||||
|
import com.sw.dualscreen.model.response.v2.FaceVO
|
||||||
|
import com.sw.dualscreen.model.response.v2.NewFoodInfo
|
||||||
|
import com.sw.dualscreen.model.response.v2.NewMemberInfo
|
||||||
|
import com.sw.dualscreen.model.response.v2.SettlementOrder
|
||||||
|
import com.sw.dualscreen.model.response.v2.toFoodInfo
|
||||||
|
import com.sw.dualscreen.model.response.v2.toFoodOrderModel
|
||||||
|
import com.sw.dualscreen.model.response.v2.toMemberInfo
|
||||||
|
import com.sw.dualscreen.network.ApiClient
|
||||||
|
import com.sw.dualscreen.utils.FileUtil
|
||||||
|
import com.sw.dualscreen.utils.SpTool
|
||||||
|
import com.sw.plate.App
|
||||||
|
import com.sw.plate.utils.Base64
|
||||||
|
import com.sw.plate.utils.ToastUtils
|
||||||
|
import com.sw.plate.utils.arcface.FaceApi
|
||||||
|
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class NetViewModelV2 : BaseViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "NetViewModelV2"
|
||||||
|
const val PAGE_SIZE = 100L
|
||||||
|
}
|
||||||
|
|
||||||
|
private val faceApi: FaceApi = FaceApi()
|
||||||
|
|
||||||
|
/** 人脸加载完成 */
|
||||||
|
private val _loadFaceResult = MutableStateFlow<Boolean>(false)
|
||||||
|
val loadFaceResult: StateFlow<Boolean> = _loadFaceResult
|
||||||
|
|
||||||
|
/** 饭点类型 早餐/午餐/晚餐 */
|
||||||
|
private val _dinnerTypeInfo = MutableStateFlow<DinnerType?>(null)
|
||||||
|
val dinnerTypeInfo: StateFlow<DinnerType?> = _dinnerTypeInfo
|
||||||
|
|
||||||
|
fun getDeviceConfig(
|
||||||
|
onSuccess: (config: com.sw.dualscreen.model.response.DeviceConfig?) -> Unit,
|
||||||
|
onFailure: (message: String) -> Unit
|
||||||
|
) {
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getDeviceConfig()
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
onSuccess(response.data)
|
||||||
|
} else {
|
||||||
|
onFailure(response.msg ?: "获取设备配置失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "获取设备配置异常")
|
||||||
|
onFailure("网络异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取人脸全量数据(递归分页 + FaceApi 集成) */
|
||||||
|
fun getFacePage(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = PAGE_SIZE,
|
||||||
|
onSuccess: () -> Unit = {},
|
||||||
|
onFailure: (String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
var currentPageNum = pageNum
|
||||||
|
Timber.tag(TAG).d("getFacePage index = $currentPageNum")
|
||||||
|
launch {
|
||||||
|
_loadFaceResult.value = false
|
||||||
|
// 重置持久化时间戳和内存累加器
|
||||||
|
SpTool.lastFaceTimestamp = 0
|
||||||
|
lastFaceTimestamp = 0L
|
||||||
|
FileUtil.saveLog("获取人脸全量数据开始,重置时间戳为0")
|
||||||
|
val response = ApiClient.repositoryV2.getFacePage(currentPageNum, pageSize)
|
||||||
|
if (!parseResponse(response)) {
|
||||||
|
onFailure("获取人脸数据失败,${response.msg}(${response.code})")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
val list = response.data ?: emptyList()
|
||||||
|
if (pageNum == 1L && list.isEmpty()) {
|
||||||
|
onFailure("查询人脸数据为空")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
val faceEntity = list.map { vo ->
|
||||||
|
FaceEntity(
|
||||||
|
vo.userId, // userName
|
||||||
|
Base64.decode(vo.faceFeature), // featureData
|
||||||
|
vo.personType ?: if (vo.member == true) "1" else "2", // userType
|
||||||
|
vo.cardNo ?: "", // cardNo
|
||||||
|
vo.userId ?: "", // userId
|
||||||
|
vo.userFaceId ?: "", // userFaceId
|
||||||
|
vo.member ?: false, // member
|
||||||
|
vo.faceUpdateTimestamp?.toLongOrNull() ?: 0L // faceUpdateTimestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
faceApi.updateFaceData(currentPageNum.toInt(), faceEntity)
|
||||||
|
// 取当前页最大时间戳(后端返回 String 类型,需转为 Long 比较),跨页累加防止中间页有更大值被覆盖
|
||||||
|
val pageMaxTimestamp = list.maxOf { it.faceUpdateTimestamp?.toLongOrNull() ?: 0L }
|
||||||
|
lastFaceTimestamp = maxOf(lastFaceTimestamp, pageMaxTimestamp)
|
||||||
|
if (list.size >= pageSize) {
|
||||||
|
currentPageNum++
|
||||||
|
getFacePage(currentPageNum, pageSize, onSuccess, onFailure)
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||||
|
FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||||
|
_loadFaceResult.value = true
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取人脸增量数据(FaceApi 集成) */
|
||||||
|
fun getFaceIncrement(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = PAGE_SIZE,
|
||||||
|
timestamp: Long,
|
||||||
|
onAllQueryFinished: () -> Unit,
|
||||||
|
onPageQueryFinished: (List<FaceVO>) -> Unit,
|
||||||
|
onFailure: (String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
Timber.tag(TAG).d("getFaceIncrement timestamp=$timestamp")
|
||||||
|
launch {
|
||||||
|
val response = ApiClient.repositoryV2.getFaceIncrement(pageNum, pageSize, timestamp)
|
||||||
|
if (!parseResponse(response)) {
|
||||||
|
onFailure(response.msg ?: "获取增量人脸数据失败")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
val list = response.data ?: emptyList()
|
||||||
|
if (pageNum == 1L && list.isEmpty()) {
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
onPageQueryFinished(list)
|
||||||
|
// 取列表中最大时间戳(后端返回 String 类型,需转为 Long),而非依赖列表最后一条
|
||||||
|
lastFaceTimestamp = list.maxOf { it.faceUpdateTimestamp?.toLongOrNull() ?: 0L }
|
||||||
|
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||||
|
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||||
|
onAllQueryFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFoodByNames(
|
||||||
|
names: String,
|
||||||
|
onSuccess: (list: List<NewFoodInfo>) -> Unit,
|
||||||
|
onFailure: (message: String) -> Unit
|
||||||
|
) {
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getFoodByNames(names.split(","))
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
onSuccess(response.data ?: emptyList())
|
||||||
|
} else {
|
||||||
|
onFailure(response.msg ?: "查询菜品失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "查询菜品异常")
|
||||||
|
onFailure("网络异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun placeOrder(
|
||||||
|
orderRequest: PlaceOrderRequest,
|
||||||
|
onSuccess: (orderNo: String?) -> Unit,
|
||||||
|
onFailure: (message: String) -> Unit
|
||||||
|
) {
|
||||||
|
launchWithLoading {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.placeOrder(orderRequest)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
onSuccess(response.data)
|
||||||
|
} else {
|
||||||
|
onFailure(response.msg ?: "下单失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "开餐下单异常")
|
||||||
|
onFailure("网络异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bindUserOrder(
|
||||||
|
userId: Long,
|
||||||
|
orderNo: String,
|
||||||
|
mode: Int? = null,
|
||||||
|
onSuccess: () -> Unit,
|
||||||
|
onFailure: (message: String) -> Unit
|
||||||
|
) {
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.bindUserOrder(userId, orderNo, mode)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
onSuccess()
|
||||||
|
} else {
|
||||||
|
onFailure(response.msg ?: "绑定订单失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "绑定订单异常")
|
||||||
|
onFailure("网络异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCollectPage(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = PAGE_SIZE,
|
||||||
|
foodName: String? = null,
|
||||||
|
onSuccess: (list: List<CollectedFoodV2>) -> Unit,
|
||||||
|
onFailure: (message: String) -> Unit
|
||||||
|
) {
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getCollectPage(pageNum, pageSize, foodName)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
onSuccess(response.data ?: emptyList())
|
||||||
|
} else {
|
||||||
|
onFailure(response.msg ?: "获取采集列表失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "获取采集列表异常")
|
||||||
|
onFailure("网络异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCollectVectorPage(
|
||||||
|
pageNum: Long = 1L,
|
||||||
|
pageSize: Long = PAGE_SIZE,
|
||||||
|
onSuccess: (list: List<CollectedFoodV2>) -> Unit,
|
||||||
|
onFailure: (message: String) -> Unit
|
||||||
|
) {
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getCollectVectorPage(pageNum, pageSize)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
onSuccess(response.data ?: emptyList())
|
||||||
|
} else {
|
||||||
|
onFailure(response.msg ?: "获取采集列表失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "获取采集列表异常")
|
||||||
|
onFailure("网络异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 上传采集图片(suspend,直接返回 picUrls 列表) */
|
||||||
|
suspend fun uploadCollect(
|
||||||
|
foodId: Long,
|
||||||
|
foodName: String,
|
||||||
|
version: String,
|
||||||
|
foodVector: String,
|
||||||
|
fileList: List<File>
|
||||||
|
): List<String>? {
|
||||||
|
Timber.tag(TAG).d("uploadCollect foodId=$foodId, foodName=$foodName")
|
||||||
|
val fileListNotNull = fileList.filter { it.exists() }
|
||||||
|
if (fileListNotNull.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val response = ApiClient.repositoryV2.uploadCollect(
|
||||||
|
foodId, foodName, version, foodVector, fileListNotNull
|
||||||
|
)
|
||||||
|
if (!parseResponse(response)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除采集菜品 */
|
||||||
|
fun deleteCollect(
|
||||||
|
foodId: String?,
|
||||||
|
version: String?,
|
||||||
|
block: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
Timber.tag(TAG).d("deleteCollect foodId=$foodId, version=$version")
|
||||||
|
val id = foodId?.toLongOrNull() ?: run {
|
||||||
|
block(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
launchWithLoading {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.deleteCollect(id, version ?: "")
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(true)
|
||||||
|
} else {
|
||||||
|
block(false)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "删除采集菜品异常")
|
||||||
|
block(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 以下方法暂用 V1 API 实现(待 V2 接口补充) ==========
|
||||||
|
|
||||||
|
/** 激活虹软人脸识别引擎 */
|
||||||
|
fun activeEngine() {
|
||||||
|
Timber.tag(TAG).d("activeEngine")
|
||||||
|
faceApi.activeEngine(
|
||||||
|
App.getContext(),
|
||||||
|
GlobalData.appId,
|
||||||
|
GlobalData.sdkKey,
|
||||||
|
GlobalData.activeKey,
|
||||||
|
object : FaceApi.ActiveCallback {
|
||||||
|
override fun onSuccess(activeCode: Int) {
|
||||||
|
Timber.tag(TAG).d("activeEngine activeCode = $activeCode")
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
when (activeCode) {
|
||||||
|
ErrorInfo.MOK -> {
|
||||||
|
ToastUtils.showToast("激活引擎成功")
|
||||||
|
}
|
||||||
|
ErrorInfo.MERR_ASF_ALREADY_ACTIVATED -> {
|
||||||
|
// 引擎已激活,无需再次激活
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
ToastUtils.showToast("激活引擎失败($activeCode)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFail(e: Exception?) {
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
ToastUtils.showToast("激活引擎异常,${e?.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取支付二维码 */
|
||||||
|
fun getQrCodeImg(
|
||||||
|
orderId: String,
|
||||||
|
userId: String? = null,
|
||||||
|
totalFee: String? = null,
|
||||||
|
block: (String?) -> Unit
|
||||||
|
) {
|
||||||
|
Timber.tag(TAG).d("getQrCodeImg orderId = $orderId, userId = $userId")
|
||||||
|
launch {
|
||||||
|
val response = repository.getQrCodeImg(
|
||||||
|
orderNo = orderId, memberId = userId, totalFee = totalFee
|
||||||
|
)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data)
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 扫码支付 */
|
||||||
|
fun qrCodePay(
|
||||||
|
authCode: String,
|
||||||
|
orderNo: String,
|
||||||
|
memberId: String?,
|
||||||
|
block: (Boolean, String?) -> Unit
|
||||||
|
) {
|
||||||
|
launch {
|
||||||
|
val response = repository.qrCodePay(authCode, orderNo, memberId)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(true, response.data)
|
||||||
|
} else {
|
||||||
|
block(false, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 现金支付 */
|
||||||
|
fun cashPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("cashPay")
|
||||||
|
launchWithLoading {
|
||||||
|
val response = repository.cashPay(param)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data ?: false)
|
||||||
|
} else {
|
||||||
|
block(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 会员支付 */
|
||||||
|
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("memberPay")
|
||||||
|
launchWithLoading {
|
||||||
|
val response = repository.memberPay(param)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(true)
|
||||||
|
} else {
|
||||||
|
block(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询订单支付状态 */
|
||||||
|
suspend fun queryOrderState(orderId: String, block: (Boolean) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("queryOrderState")
|
||||||
|
val response = repository.queryOrderState(orderNo = orderId)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data == "1")
|
||||||
|
} else {
|
||||||
|
block(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取饭点类型 */
|
||||||
|
fun getDinnerType(
|
||||||
|
onSuccess: (DinnerType?) -> Unit = {},
|
||||||
|
onFailure: (message: String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
_dinnerTypeInfo.value = null
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val response = repository.getDinnerType()
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
_dinnerTypeInfo.value = response.data
|
||||||
|
onSuccess(response.data)
|
||||||
|
} else {
|
||||||
|
onFailure(response.msg ?: "获取饭点类型失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "获取饭点类型异常")
|
||||||
|
onFailure("网络异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== V1 兼容包装方法(暂用 V1 API,后续逐步迁移至 V2) ==========
|
||||||
|
|
||||||
|
private var lastFaceTimestamp = 0L
|
||||||
|
|
||||||
|
/** 搜索食物 */
|
||||||
|
fun searchByFoodName(foodName: String, action: (List<FoodInfo>) -> Unit = {}) {
|
||||||
|
Timber.tag(TAG).d("searchFood foodName = $foodName")
|
||||||
|
launchWithLoading {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.searchFood(name = foodName)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
val list = (response.data ?: emptyList()).map { it.toFoodInfo() }
|
||||||
|
action(list)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "搜索菜品异常")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取用户就餐营养数据 */
|
||||||
|
fun getUserNutritionData(
|
||||||
|
userId: String,
|
||||||
|
block: (UserNutrition?) -> Unit
|
||||||
|
) {
|
||||||
|
Timber.tag(TAG).d("getUserNutritionData userId = $userId")
|
||||||
|
val uid = userId.toLongOrNull() ?: run {
|
||||||
|
block(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getUserCurrentFood(uid)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data)
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "获取就餐营养数据异常")
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取订单列表 */
|
||||||
|
fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("getFoodOrderList userId = $userId")
|
||||||
|
val uid = userId.toLongOrNull() ?: run {
|
||||||
|
block(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
launchWithLoading {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getSettlementOrders(uid)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data?.toFoodOrderModel())
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "查询订单列表异常")
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 ID 查询会员信息 */
|
||||||
|
fun getMemberInfoById(memberId: String, block: (MemberInfo?) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("getMemberInfo memberId=$memberId")
|
||||||
|
val uid = memberId.toLongOrNull() ?: run {
|
||||||
|
block(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
launchWithLoading {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getMemberInfo(uid)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data?.toMemberInfo())
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "查询会员信息异常")
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据手机号查询会员信息 */
|
||||||
|
fun getMemberInfoByPhone(phone: String, key: String, block: (MemberInfo?) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("getMemberInfoByPhone phone=$phone")
|
||||||
|
launchWithLoading {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getMemberInfoByPhone(phone, key)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data?.toMemberInfo())
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "查询会员异常")
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取会员折扣 */
|
||||||
|
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("getMemberDiscount userId = $userId")
|
||||||
|
val uid = userId.toLongOrNull() ?: run {
|
||||||
|
block(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
launchWithLoading {
|
||||||
|
try {
|
||||||
|
val response = ApiClient.repositoryV2.getMemberDiscount(uid)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data?.toDoubleOrNull())
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "查询会员折扣异常")
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import com.sw.dualscreen.model.response.DeviceConfig
|
|||||||
import com.sw.dualscreen.model.response.DinnerType
|
import com.sw.dualscreen.model.response.DinnerType
|
||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.model.response.FoodOrder
|
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.FoodVector
|
||||||
import com.sw.dualscreen.model.response.MemberInfo
|
import com.sw.dualscreen.model.response.MemberInfo
|
||||||
import com.sw.dualscreen.model.response.UserFaceModel
|
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.model.response.UserNutrition
|
||||||
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
||||||
import com.sw.dualscreen.utils.Debouncer
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
|
import com.sw.dualscreen.utils.FileUtil
|
||||||
import com.sw.dualscreen.utils.SPUtil
|
import com.sw.dualscreen.utils.SPUtil
|
||||||
import com.sw.dualscreen.utils.SpTool
|
import com.sw.dualscreen.utils.SpTool
|
||||||
import com.sw.plate.App
|
import com.sw.plate.App
|
||||||
@@ -90,27 +92,43 @@ 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
|
var currentPageNo = pageNo
|
||||||
|
// Debug log to track current page
|
||||||
Timber.tag(TAG).d("getUserFaceCache index = $currentPageNo")
|
Timber.tag(TAG).d("getUserFaceCache index = $currentPageNo")
|
||||||
|
// Launch a coroutine to perform the face data retrieval
|
||||||
launch {
|
launch {
|
||||||
|
// Set loading state to false
|
||||||
_loadFaceResult.value = false
|
_loadFaceResult.value = false
|
||||||
//获取全量数据时,时间戳改为0
|
//获取全量数据时,时间戳改为0
|
||||||
SpTool.lastFaceTimestamp = 0
|
SpTool.lastFaceTimestamp = 0
|
||||||
|
FileUtil.saveLog("获取人脸全量数据开始,重置时间戳为0")
|
||||||
val response = repository.getUserFaceCache(currentPageNo)
|
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)
|
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
val list: List<UserFaceModel> = response.data ?: emptyList()
|
val list: List<UserFaceModel> = response.data ?: emptyList()
|
||||||
if (pageNo == 1 && list.isEmpty()) {
|
if (pageNo == 1 && list.isEmpty()) {
|
||||||
ToastUtils.showToast("查询人脸数据为空")
|
//ToastUtils.showToast("查询人脸数据为空")
|
||||||
|
onFailure("查询人脸数据为空")
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
//val item = list.map { it.member }
|
//val item = list.map { it.member }
|
||||||
//Timber.tag(TAG).d("getUserFaceCache item = $item")
|
//Timber.tag(TAG).d("getUserFaceCache item = $item")
|
||||||
val faceEntity = list.map {
|
val faceEntity = list.map {
|
||||||
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr)).also { item->
|
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr)).also { item ->
|
||||||
item.userType = if (it.member) "1" else "2"
|
item.userType = if (it.member) "1" else "2"
|
||||||
item.cardNo = it.cardNo
|
item.cardNo = it.cardNo
|
||||||
}
|
}
|
||||||
@@ -128,9 +146,10 @@ class UserViewModel : BaseViewModel() {
|
|||||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||||
}
|
}
|
||||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||||
|
FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||||
_loadFaceResult.value = true
|
_loadFaceResult.value = true
|
||||||
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
// val nextPageIndex = response.result?.nextPageIndex ?: -1
|
// val nextPageIndex = response.result?.nextPageIndex ?: -1
|
||||||
// if (nextPageIndex > 0) {
|
// if (nextPageIndex > 0) {
|
||||||
// getUserFaceCache(nextPageIndex)
|
// getUserFaceCache(nextPageIndex)
|
||||||
@@ -139,7 +158,6 @@ class UserViewModel : BaseViewModel() {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// fun getUserFaceCache2(index: Int = 0) {
|
// fun getUserFaceCache2(index: Int = 0) {
|
||||||
// Timber.d("getUserFaceCache index = $index")
|
// Timber.d("getUserFaceCache index = $index")
|
||||||
@@ -206,6 +224,7 @@ class UserViewModel : BaseViewModel() {
|
|||||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||||
}
|
}
|
||||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||||
|
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||||
onAllQueryFinished()
|
onAllQueryFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,9 +382,14 @@ class UserViewModel : BaseViewModel() {
|
|||||||
/**
|
/**
|
||||||
* 二维码支付
|
* 二维码支付
|
||||||
*/
|
*/
|
||||||
fun qrCodePay(authCode: String,orderNo: String, block:(Boolean, String?)-> Unit){
|
fun qrCodePay(
|
||||||
|
authCode: String,
|
||||||
|
orderNo: String,
|
||||||
|
memberId: String?,
|
||||||
|
block: (Boolean, String?) -> Unit
|
||||||
|
) {
|
||||||
launch {
|
launch {
|
||||||
val response = repository.qrCodePay(authCode, orderNo)
|
val response = repository.qrCodePay(authCode, orderNo, memberId)
|
||||||
if (parseResponse(response)) {
|
if (parseResponse(response)) {
|
||||||
block(true, response.data)
|
block(true, response.data)
|
||||||
} else {
|
} else {
|
||||||
@@ -373,6 +397,7 @@ class UserViewModel : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDinnerType() {
|
fun getDinnerType() {
|
||||||
Timber.tag(TAG).d("getDinnerType")
|
Timber.tag(TAG).d("getDinnerType")
|
||||||
_dinnerTypeInfo.value = null
|
_dinnerTypeInfo.value = null
|
||||||
@@ -491,7 +516,7 @@ class UserViewModel : BaseViewModel() {
|
|||||||
/**
|
/**
|
||||||
* 会员支付
|
* 会员支付
|
||||||
*/
|
*/
|
||||||
fun memberPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
|
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
|
||||||
Timber.tag(TAG).d("memberPay")
|
Timber.tag(TAG).d("memberPay")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
val response = repository.memberPay(param)
|
val response = repository.memberPay(param)
|
||||||
@@ -588,19 +613,22 @@ class UserViewModel : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCollectedFoodVector(param: MutableMap<String, String>, block: (List<FoodVector>?) -> Unit) {
|
fun getCollectedFoodVector(
|
||||||
|
param: MutableMap<String, String>,
|
||||||
|
block: (List<FoodVector>?) -> Unit
|
||||||
|
) {
|
||||||
Timber.tag(TAG).d("getCollectedFoodVector")
|
Timber.tag(TAG).d("getCollectedFoodVector")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
val response = repository.getCollectedFoodVector(param)
|
val response = repository.getCollectedFoodVector(param)
|
||||||
if (parseResponse(response)) {
|
if (parseResponse(response)) {
|
||||||
block(response.data?:emptyList())
|
block(response.data ?: emptyList())
|
||||||
} else {
|
} else {
|
||||||
block(null)
|
block(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCollectFood(foodId:String?, version: String?, block: (Boolean) -> Unit) {
|
fun deleteCollectFood(foodId: String?, version: String?, block: (Boolean) -> Unit) {
|
||||||
Timber.tag(TAG).d("deleteCollectFood")
|
Timber.tag(TAG).d("deleteCollectFood")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
val response = repository.deleteCollectFood(foodId, version)
|
val response = repository.deleteCollectFood(foodId, version)
|
||||||
@@ -611,4 +639,28 @@ class UserViewModel : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("getFoodOrderList")
|
||||||
|
launchWithLoading {
|
||||||
|
val response = repository.getFoodOrderList(userId)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data)
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
|
||||||
|
Timber.tag(TAG).d("getMemberDiscount")
|
||||||
|
launchWithLoading {
|
||||||
|
val response = repository.getMemberDiscount(userId)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
block(response.data)
|
||||||
|
} else {
|
||||||
|
block(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#F2F2F2"/>
|
||||||
|
<!-- <stroke android:color="#cdcdcd" android:width="1dp"/>-->
|
||||||
|
<corners android:radius="5dp"/>
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<!-- 眼睛轮廓 -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:strokeColor="@android:color/black"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
|
||||||
|
<!-- 瞳孔(path绘制圆形) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/black"
|
||||||
|
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
|
||||||
|
<!-- 斜线表示隐藏 -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:strokeColor="@android:color/black"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M 4 4 L 20 20" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<!-- 眼睛轮廓 -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:strokeColor="@android:color/black"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
|
||||||
|
<!-- 瞳孔(path绘制圆形) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/black"
|
||||||
|
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
|
||||||
|
</vector>
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginStart="28dp"
|
android:layout_marginStart="28dp"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
android:textColor="#ff0a1428"
|
android:textColor="#ff0a1428"
|
||||||
android:textSize="26sp"
|
android:textSize="26sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
@@ -48,19 +49,23 @@
|
|||||||
android:textColor="#FF0A1428"
|
android:textColor="#FF0A1428"
|
||||||
android:textSize="52sp"
|
android:textSize="52sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
<!-- <TextView-->
|
|
||||||
<!-- android:id="@+id/tvShowWeight"-->
|
<TextView
|
||||||
<!-- android:layout_width="wrap_content"-->
|
android:id="@+id/tvShowWeight"
|
||||||
<!-- android:layout_height="80dp"-->
|
android:layout_width="match_parent"
|
||||||
<!-- android:textSize="30sp"-->
|
android:layout_height="80dp"
|
||||||
<!-- android:gravity="center"-->
|
android:textSize="20sp"
|
||||||
<!-- tools:text="100克"/>-->
|
android:gravity="center"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
tools:text="100克"/>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/foodDisplayLayout"
|
android:id="@+id/foodDisplayLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="560dp"
|
android:layout_height="560dp"
|
||||||
android:layout_marginHorizontal="28dp"
|
android:layout_marginHorizontal="28dp"
|
||||||
android:layout_marginTop="80dp"
|
android:layout_marginTop="0dp"
|
||||||
android:visibility="visible">
|
android:visibility="visible">
|
||||||
|
|
||||||
<androidx.camera.view.PreviewView
|
<androidx.camera.view.PreviewView
|
||||||
@@ -84,53 +89,21 @@
|
|||||||
android:layout_margin="27dp"
|
android:layout_margin="27dp"
|
||||||
android:background="@drawable/ic_camera_rect" />
|
android:background="@drawable/ic_camera_rect" />
|
||||||
<!-- preview_border-->
|
<!-- preview_border-->
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<FrameLayout
|
||||||
android:id="@+id/recyclerview"
|
android:id="@+id/flFoodList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="14dp"
|
android:layout_weight="1"/>
|
||||||
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>
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/flPay"
|
android:id="@+id/flPay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/white"
|
android:background="@color/white"
|
||||||
|
android:layout_marginTop="45dp"
|
||||||
android:visibility="gone">
|
android:visibility="gone">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<RadioGroup
|
<RadioGroup
|
||||||
android:id="@+id/rgSetting"
|
android:id="@+id/rgSetting"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="90dp"
|
android:layout_height="75dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:paddingHorizontal="50dp"
|
android:paddingHorizontal="50dp"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingBottom="35dp"
|
android:paddingBottom="15dp"
|
||||||
android:text="经营设置"
|
android:text="经营设置"
|
||||||
android:textColor="@color/setting_radio"
|
android:textColor="@color/setting_radio"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:paddingHorizontal="50dp"
|
android:paddingHorizontal="50dp"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingBottom="35dp"
|
android:paddingBottom="15dp"
|
||||||
android:text="餐品采集"
|
android:text="餐品采集"
|
||||||
android:textColor="@color/setting_radio"
|
android:textColor="@color/setting_radio"
|
||||||
android:textSize="30sp"
|
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="测试环境"
|
||||||
|
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:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 结算设置模块 -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -16,6 +17,85 @@
|
|||||||
android:paddingTop="35dp"
|
android:paddingTop="35dp"
|
||||||
android:paddingBottom="42dp">
|
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
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -134,6 +214,7 @@
|
|||||||
android:background="@drawable/setting_border_gray">
|
android:background="@drawable/setting_border_gray">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tvDishWeightLabel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="120dp"
|
android:layout_height="120dp"
|
||||||
android:layout_marginStart="42dp"
|
android:layout_marginStart="42dp"
|
||||||
|
|||||||
@@ -11,16 +11,14 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="32dp"
|
android:layout_marginHorizontal="32dp"
|
||||||
android:background="@drawable/setting_border"
|
android:background="@drawable/setting_border"
|
||||||
android:orientation="vertical"
|
android:paddingBottom="10dp">
|
||||||
android:paddingBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvChargeSetting"
|
android:id="@+id/tvChargeSetting"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center_vertical"
|
|
||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:layout_marginTop="35dp"
|
android:layout_marginTop="20dp"
|
||||||
android:drawablePadding="17dp"
|
android:drawablePadding="17dp"
|
||||||
android:text="餐品采集"
|
android:text="餐品采集"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
@@ -34,7 +32,6 @@
|
|||||||
android:id="@+id/btnCollectedFood"
|
android:id="@+id/btnCollectedFood"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end|center_vertical"
|
|
||||||
android:layout_marginEnd="32dp"
|
android:layout_marginEnd="32dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
@@ -51,7 +48,7 @@
|
|||||||
android:id="@+id/flPreview"
|
android:id="@+id/flPreview"
|
||||||
android:layout_width="456dp"
|
android:layout_width="456dp"
|
||||||
android:layout_height="342dp"
|
android:layout_height="342dp"
|
||||||
android:layout_marginTop="41dp"
|
android:layout_marginTop="20dp"
|
||||||
app:layout_constraintStart_toStartOf="@id/tvChargeSetting"
|
app:layout_constraintStart_toStartOf="@id/tvChargeSetting"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvChargeSetting">
|
app:layout_constraintTop_toBottomOf="@id/tvChargeSetting">
|
||||||
|
|
||||||
@@ -114,9 +111,9 @@
|
|||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rvFoodList"
|
android:id="@+id/rvFoodList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="382dp"
|
android:layout_height="358dp"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginVertical="10dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -132,7 +129,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
android:layout_marginHorizontal="32dp"
|
android:layout_marginHorizontal="32dp"
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="20dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
@@ -167,22 +164,22 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginTop="18dp"
|
android:layout_marginTop="12dp"
|
||||||
android:minHeight="348dp"
|
android:minHeight="348dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:spanCount="2"
|
app:spanCount="2"
|
||||||
tools:itemCount="10"
|
tools:itemCount="11"
|
||||||
android:overScrollMode="never"
|
android:overScrollMode="never"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
tools:listitem="@layout/list_item_search_food"/>
|
tools:listitem="@layout/list_item_search_food3"/>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
android:id="@+id/btnSave"
|
android:id="@+id/btnSave"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
android:layout_marginHorizontal="32dp"
|
android:layout_marginHorizontal="32dp"
|
||||||
android:layout_marginBottom="32dp"
|
android:layout_marginBottom="10dp"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="10dp"
|
||||||
android:text="保存"
|
android:text="保存"
|
||||||
android:background="@drawable/bg_btn_save"
|
android:background="@drawable/bg_btn_save"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
|
|||||||
@@ -34,17 +34,21 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||||
|
|
||||||
<EditText
|
<FrameLayout
|
||||||
android:id="@+id/etInputNumber"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
android:layout_marginHorizontal="64dp"
|
android:layout_marginHorizontal="64dp"
|
||||||
android:inputType="textVisiblePassword"
|
android:layout_marginTop="32dp">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etInputNumber"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:inputType="textPassword"
|
||||||
android:background="@drawable/setting_border_gray3"
|
android:background="@drawable/setting_border_gray3"
|
||||||
android:hint="输入消费码"
|
android:hint="输入消费码"
|
||||||
android:textColorHint="#ffc4cfda"
|
android:textColorHint="#ffc4cfda"
|
||||||
android:textColor="#ff0a1428"
|
android:textColor="#ff0a1428"
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:textSize="36sp"
|
android:textSize="36sp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
@@ -53,6 +57,19 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivTogglePassword"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
|
android:src="@drawable/ic_eye_close"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
android:id="@+id/btnConfirm"
|
android:id="@+id/btnConfirm"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -285,7 +285,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/meatTv"
|
android:id="@+id/meatTv"
|
||||||
style="@style/takeFoodNameTv"
|
style="@style/takeFoodNameTv"
|
||||||
android:text="蛋肉"
|
android:text="肉蛋豆"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="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>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="150dp">
|
android:layout_height="130dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ivPageBack"
|
android:id="@+id/ivPageBack"
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="212dp"
|
android:layout_width="212dp"
|
||||||
android:layout_height="159dp"
|
android:layout_height="159dp"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
android:background="@drawable/bg_gray2">
|
android:background="@drawable/bg_gray2">
|
||||||
|
|
||||||
<ImageView
|
<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:ellipsize="end"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:layout_marginHorizontal="14dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginVertical="14dp"
|
android:layout_marginVertical="16dp"
|
||||||
android:paddingHorizontal="15dp"
|
android:paddingHorizontal="15dp"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
tools:textColor="@color/black"
|
tools:textColor="@color/black"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:layout_marginHorizontal="14dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginVertical="14dp"
|
android:layout_marginVertical="16dp"
|
||||||
android:paddingHorizontal="15dp"
|
android:paddingHorizontal="15dp"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textColor="@color/color_text_dialog"
|
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">
|
android:padding="28dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/llTopInfo"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/setting_border"
|
android:background="@drawable/setting_border"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="26dp">
|
android:paddingBottom="26dp"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvFoodName"
|
android:id="@+id/tvFoodName"
|
||||||
@@ -30,10 +32,10 @@
|
|||||||
android:textSize="40sp"
|
android:textSize="40sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<!-- android:layout_width="300dp"-->
|
<!--android:layout_width="300dp"-->
|
||||||
<!-- android:layout_height="450dp"-->
|
<!--android:layout_height="450dp"-->
|
||||||
<!-- android:layout_width="330dp"-->
|
<!--android:layout_width="330dp"-->
|
||||||
<!-- android:layout_height="495dp"-->
|
<!--android:layout_height="495dp"-->
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/flCameraView"
|
android:id="@+id/flCameraView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -187,6 +189,20 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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
|
<FrameLayout
|
||||||
android:id="@+id/flBottomView"
|
android:id="@+id/flBottomView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
agp = "8.10.1"
|
agp = "8.10.1"
|
||||||
core = "3.4.1"
|
core = "3.4.1"
|
||||||
androidCore = "3.3.0"
|
androidCore = "3.3.0"
|
||||||
|
eventbus = "3.2.0"
|
||||||
glide = "4.16.0"
|
glide = "4.16.0"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.0.21"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
@@ -13,7 +14,10 @@ material = "1.10.0"
|
|||||||
activity = "1.8.0"
|
activity = "1.8.0"
|
||||||
constraintlayout = "2.1.4"
|
constraintlayout = "2.1.4"
|
||||||
accompanistPermissions = "0.37.3"
|
accompanistPermissions = "0.37.3"
|
||||||
cameraCore = "1.3.0"
|
cameraCore = "1.4.1"
|
||||||
|
objectboxVersion = "5.0.1"
|
||||||
|
refreshLayoutKernel = "3.0.0-alpha"
|
||||||
|
refreshHeaderClassics = "3.0.0-alpha"
|
||||||
retrofit = "3.0.0"
|
retrofit = "3.0.0"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
@@ -30,11 +34,13 @@ pytorch = "1.13.0"
|
|||||||
|
|
||||||
recyclerview = "1.3.2"
|
recyclerview = "1.3.2"
|
||||||
|
|
||||||
#baseQuickAdapter = "4.1.2"
|
baseQuickAdapter = "3.0.4"
|
||||||
|
baseQuickAdapter4 = "4.1.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
|
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
|
||||||
core = { module = "com.google.zxing:core", version.ref = "core" }
|
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" }
|
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
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" }
|
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
||||||
|
|
||||||
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
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" }
|
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" }
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
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-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
|
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 = { group = "org.pytorch", name = "pytorch_android", version.ref = "pytorch" }
|
||||||
pytorch-android-torchvision = { group = "org.pytorch", name = "pytorch_android_torchvision", 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" }
|
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
#Mon Jul 14 11:28:37 CST 2025
|
#Mon Jul 14 11:28:37 CST 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip
|
||||||
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
|
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
|
||||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -68,5 +68,5 @@ dependencies {
|
|||||||
|
|
||||||
val objectboxVersion = "5.0.1"
|
val objectboxVersion = "5.0.1"
|
||||||
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
||||||
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user