Compare commits
76
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 | ||
|
|
dfde8c0fad | ||
|
|
aca84f1e94 | ||
|
|
7bcb74d9e0 | ||
|
|
6c857dd1f7 | ||
|
|
fd0f9977b1 | ||
|
|
ec4a5801c2 |
+3
-6
@@ -1,15 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
/.idea/
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
app/objectbox-models/*.json
|
||||
app/objectbox-models/*.json.bak
|
||||
|
||||
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 {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
viewBinding {
|
||||
enable = true
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
|
||||
//implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
@@ -108,17 +110,19 @@ dependencies {
|
||||
implementation(libs.pytorch.android)
|
||||
implementation(libs.pytorch.android.torchvision)
|
||||
|
||||
implementation("io.github.scwang90:refresh-layout-kernel:3.0.0-alpha")
|
||||
implementation("io.github.scwang90:refresh-header-classics:3.0.0-alpha")
|
||||
implementation(libs.baseRecyclerViewAdapterHelper4)
|
||||
|
||||
implementation(libs.refresh.layout.kernel)
|
||||
implementation(libs.refresh.header.classics)
|
||||
|
||||
val objectboxVersion = "5.0.1"
|
||||
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
||||
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||
debugImplementation(libs.objectbox.android.objectbrowser)
|
||||
releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||
// implementation("io.objectbox:objectbox-fulltext:5.0.1")
|
||||
|
||||
implementation(libs.androidx.recyclerview)
|
||||
|
||||
implementation("org.greenrobot:eventbus:3.2.0")
|
||||
implementation(libs.eventbus)
|
||||
}
|
||||
|
||||
apply(plugin = "io.objectbox")
|
||||
@@ -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,11 +61,13 @@
|
||||
<!-- </activity>-->
|
||||
<!-- <activity android:name="com.sw.dualscreen.activity.CollectedDataActivity" />-->
|
||||
<activity android:name="com.sw.dualscreen.activity.SettingActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:launchMode="singleTask" />
|
||||
<activity android:name="com.sw.dualscreen.activity.CollectedFoodActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
<activity android:name="com.sw.dualscreen.activity.PayActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:launchMode="singleTask"/>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
||||
@@ -17,11 +17,16 @@ object GlobalData {
|
||||
*/
|
||||
var appVersion: String = "1"
|
||||
|
||||
var appBaseUrl: String = ""
|
||||
|
||||
/**
|
||||
* 具体业务baseurl
|
||||
* 具体业务 BaseUrl
|
||||
*/
|
||||
var appBaseUrl2: String = "http://192.168.1.201:14801"
|
||||
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
|
||||
// const val LOCAL_BASE_URL = "http://192.168.1.201:14801"
|
||||
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_PICKUP_MODE = "pickupMode"
|
||||
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.utils.ActivityManager
|
||||
import com.sw.dualscreen.utils.CrashHandler
|
||||
import com.sw.dualscreen.utils.SpTool
|
||||
import com.sw.plate.App
|
||||
import com.sw.plate.utils.AppUtil
|
||||
import timber.log.Timber
|
||||
@@ -13,7 +14,7 @@ import timber.log.Timber
|
||||
class MyApp : App() {
|
||||
companion object {
|
||||
const val DEBUG: Boolean = true
|
||||
var instance: MyApp?=null
|
||||
var instance: MyApp? = null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -24,13 +25,23 @@ class MyApp : App() {
|
||||
Timber.d("deviceId = $deviceId")
|
||||
// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
|
||||
GlobalData.deviceId = deviceId
|
||||
GlobalData.appBaseUrl = if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) GlobalData.appBaseUrl2 else GlobalData.appBaseUrl
|
||||
ObjectBox.init(this)
|
||||
// 初始化崩溃处理器
|
||||
CrashHandler.init(this)
|
||||
|
||||
SensorScaleUtils.startScale()
|
||||
addActivityLifecycleListener()
|
||||
|
||||
// 优先读取用户手动切换后持久化的 url
|
||||
val savedUrl = SpTool.baseUrl
|
||||
if (!savedUrl.isNullOrEmpty()) {
|
||||
GlobalData.appBaseUrl = savedUrl
|
||||
} else {
|
||||
// 未保存过则使用默认逻辑:特定设备走测试环境,其余走 UAT 测试档口机: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() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sw.dualscreen.activity
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Bundle
|
||||
@@ -59,7 +60,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
keyEventHelper?.onDestroy()
|
||||
keyEventHelper?.release()
|
||||
EventBus.getDefault().unregister(this)
|
||||
super.onDestroy()
|
||||
}
|
||||
@@ -153,11 +154,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
*/
|
||||
fun registerKeyEvent() {
|
||||
keyEventHelper =
|
||||
ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener {
|
||||
ScanGunKeyEventHelper( object : ScanGunKeyEventHelper.OnScanSuccessListener {
|
||||
override fun onScanSuccess(barcode: String?) {
|
||||
Timber.d("onScanSuccess barcode = $barcode")
|
||||
if (barcode == null) return
|
||||
handleScanKeyInfo(barcode)
|
||||
scanGunKeyEventCallback(barcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -166,14 +167,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
/**
|
||||
* 处理扫描枪数据
|
||||
*/
|
||||
protected open fun handleScanKeyInfo(scanInfo: String) {
|
||||
var payCode = scanInfo
|
||||
//if (scanInfo.length > 6) {
|
||||
// payCode = scanInfo.take(6)
|
||||
//}
|
||||
Log.d("TAG", "handleScanKeyInfo: " + payCode)
|
||||
fun scanGunKeyEventCallback(scanInfo: String){
|
||||
if (this is PayActivity) {
|
||||
qrCodePay(scanInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (keyEventHelper != null) {
|
||||
if (keyEventHelper!!.isScanGunEvent(event)) {
|
||||
@@ -183,4 +183,52 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
|
||||
private var activityCallback: ((intent: Intent?) -> Unit)? = null
|
||||
|
||||
fun requestMultiplePermissions(
|
||||
permissions: Array<String>,
|
||||
callback: (isGranted: Boolean) -> Unit
|
||||
) {
|
||||
this.permissionCallback = callback
|
||||
requestMultiplePermissionsLauncher.launch(permissions)
|
||||
}
|
||||
|
||||
fun requestPermission(permission: String, callback: (isGranted: Boolean) -> Unit) {
|
||||
this.permissionCallback = callback
|
||||
requestPermissionLauncher.launch(permission)
|
||||
}
|
||||
|
||||
val requestMultiplePermissionsLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
var isGranted = true
|
||||
permissions.entries.forEach {
|
||||
if (!it.value) {
|
||||
isGranted = false
|
||||
}
|
||||
}
|
||||
permissionCallback?.invoke(isGranted)
|
||||
}
|
||||
|
||||
// 权限请求回调
|
||||
val requestPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
permissionCallback?.invoke(isGranted)
|
||||
}
|
||||
|
||||
fun startActivity(intent: Intent, callback: (Intent?) -> Unit) {
|
||||
this.activityCallback = callback
|
||||
startActivityLauncher.launch(intent)
|
||||
}
|
||||
|
||||
// activity页面返回的回调
|
||||
private val startActivityLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
activityCallback?.invoke(it.data)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import androidx.activity.viewModels
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.sw.dualscreen.GlobalData
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.adapter.CollectedFoodNewAdapter
|
||||
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.hideKeyboard
|
||||
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.viewmodel.BaseViewModel
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -27,7 +26,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
||||
private const val PAGE_SIZE = 100
|
||||
}
|
||||
|
||||
private val viewModel by viewModels<UserViewModel>()
|
||||
private val viewModel by viewModels<NetViewModelV2>()
|
||||
override fun getViewModel(): BaseViewModel {
|
||||
return viewModel
|
||||
}
|
||||
@@ -36,7 +35,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
||||
return ActivityCollectedFoodBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
private val list: MutableList<CollectedFoodInfo> = mutableListOf()
|
||||
private val list: MutableList<CollectedFoodV2> = mutableListOf()
|
||||
private val adapter by lazy {
|
||||
CollectedFoodNewAdapter(list).apply {
|
||||
// isStateViewEnable = true
|
||||
@@ -48,6 +47,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
||||
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
registerKeyEvent()
|
||||
binding.root.setOnClickListener { it.hideKeyboard() }
|
||||
binding.rvFoodList.let {
|
||||
it.layoutManager = LinearLayoutManager(this)
|
||||
@@ -126,19 +126,26 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
||||
// loadEmptyView()
|
||||
// }
|
||||
|
||||
viewModel.getCollectedFoodList(
|
||||
pageNo = pageNo,
|
||||
pageSize = PAGE_SIZE,
|
||||
foodName = searchName ?: ""
|
||||
) { items ->
|
||||
runOnUiThread {
|
||||
loadFoodList(items)
|
||||
viewModel.getCollectPage(
|
||||
pageNum = pageNo.toLong(),
|
||||
pageSize = PAGE_SIZE.toLong(),
|
||||
foodName = searchName,
|
||||
onSuccess = { items ->
|
||||
runOnUiThread {
|
||||
loadFoodList(items)
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
runOnUiThread {
|
||||
finishRefresh()
|
||||
ToastUtils.showToast(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun loadFoodList(items: List<CollectedFoodInfo>) {
|
||||
private fun loadFoodList(items: List<CollectedFoodV2>) {
|
||||
finishRefresh()
|
||||
if (pageNo == 1 && items.isEmpty()) {
|
||||
list.clear()
|
||||
@@ -190,7 +197,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
||||
private fun deleteGoods(position: Int) {
|
||||
val food = list[position]
|
||||
showWaitingDialog("加载中……")
|
||||
viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess ->
|
||||
viewModel.deleteCollect(food.foodId, food.version) { deleteSuccess ->
|
||||
if (deleteSuccess) {
|
||||
// Thread {}.start()
|
||||
lifecycleScope.launch {
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
package com.sw.dualscreen.activity
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.os.CountDownTimer
|
||||
import android.provider.Settings
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.sw.dualscreen.GlobalData
|
||||
import com.sw.dualscreen.databinding.ActivityInitBinding
|
||||
import com.sw.dualscreen.dialog.RemindDialog
|
||||
import com.sw.dualscreen.ext.clickWithDebounce
|
||||
import com.sw.dualscreen.ext.invisible
|
||||
import com.sw.dualscreen.ext.visible
|
||||
import com.sw.dualscreen.model.response.FoodVector
|
||||
import com.sw.dualscreen.objbox.Food
|
||||
import com.sw.dualscreen.objbox.FoodModule
|
||||
import com.sw.dualscreen.objbox.ObjectBox
|
||||
import com.sw.dualscreen.utils.FoodVectorTool
|
||||
import com.sw.dualscreen.utils.L
|
||||
import com.sw.dualscreen.utils.NetworkUtils
|
||||
import com.sw.dualscreen.utils.SpTool
|
||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||
import com.sw.plate.utils.AppUtil
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
companion object {
|
||||
private const val PAGE_SIZE = 100
|
||||
const val PAGE_SIZE = 100
|
||||
}
|
||||
|
||||
private val userViewModel by viewModels<UserViewModel>()
|
||||
private val userViewModel by viewModels<NetViewModelV2>()
|
||||
private var pageNum = 1
|
||||
|
||||
override fun getViewModel(): BaseViewModel {
|
||||
@@ -36,55 +41,23 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
}
|
||||
|
||||
private fun getCollectedFoodVector(block: () -> Unit) {
|
||||
userViewModel.getCollectedFoodVector(
|
||||
param = mutableMapOf(
|
||||
"pageNum" to "$pageNum",
|
||||
"pageSize" to "$PAGE_SIZE",
|
||||
"version" to GlobalData.foodModelVersion
|
||||
)
|
||||
) { 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()
|
||||
}
|
||||
}
|
||||
FoodVectorTool.loadAndSaveFoodVector(
|
||||
userViewModel = userViewModel,
|
||||
lifecycleScope = lifecycleScope,
|
||||
successBlock = { block() },
|
||||
failureBlock = { getVectorErrorDialog() }
|
||||
)
|
||||
}
|
||||
|
||||
// private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
|
||||
private fun saveFoodVector(list: List<FoodVector>) {
|
||||
lifecycleScope.launch {
|
||||
val vectorList = list.map { vt ->
|
||||
val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")
|
||||
?.map { it.toFloatOrNull() ?: 0.0f }?.toFloatArray()
|
||||
Food(
|
||||
collectId = vt.id,
|
||||
foodId = vt.foodId,
|
||||
foodName = vt.foodName,
|
||||
version = vt.version,
|
||||
foodVector = foodVector
|
||||
)
|
||||
}
|
||||
ObjectBox.putAll(vectorList)
|
||||
}
|
||||
}
|
||||
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
//写死,后续改为从接口获取结算类型 ------------------------------------------
|
||||
//val settlementMode = 1
|
||||
//SPUtil.getInstance().put(GlobalKey.KEY_SETTLEMENT_MODE, settlementMode)
|
||||
|
||||
registerKeyEvent()
|
||||
initNetworkTimer()
|
||||
binding.okButton.clickWithDebounce(500) {
|
||||
checkNetworkTimer?.cancel()
|
||||
@@ -92,21 +65,6 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
}
|
||||
showWaitingDialog("网络连接中...")
|
||||
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() {
|
||||
@@ -120,18 +78,19 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
}
|
||||
|
||||
private fun getCollectedFoodVector() {
|
||||
binding.okButton.invisible()
|
||||
showWaitingDialog2("加载中……")
|
||||
lifecycleScope.launch {
|
||||
FoodModule.init(this@InitActivity) {
|
||||
getCollectedFoodVector {
|
||||
runOnUiThread {
|
||||
hideWaitingDialog()
|
||||
binding.root.postDelayed({
|
||||
goMainActivity()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.root.postDelayed({
|
||||
checkCameraPermissionAndGo()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +100,57 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun hasCameraPermission(): Boolean {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.CAMERA
|
||||
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
// 检查相机权限
|
||||
private fun checkCameraPermissionAndGo() {
|
||||
val isGrantedCameraPermission = hasCameraPermission()
|
||||
if (isGrantedCameraPermission) {
|
||||
// 有权限,直接打开 MainActivity
|
||||
goMainActivity()
|
||||
} else {
|
||||
// 无权限,请求相机权限
|
||||
requestPermission(Manifest.permission.CAMERA) { isGranted ->
|
||||
checkCameraPermission(isGranted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从设置页面返回后检查权限
|
||||
private fun checkCameraPermission(isGranted: Boolean) {
|
||||
if (isGranted) {
|
||||
// 权限已授予,打开 MainActivity
|
||||
goMainActivity()
|
||||
} else {
|
||||
// 权限仍未授予,再次显示弹窗
|
||||
showPermissionDeniedDialog()
|
||||
}
|
||||
}
|
||||
|
||||
// 显示权限被拒绝的弹窗
|
||||
private fun showPermissionDeniedDialog() {
|
||||
RemindDialog(
|
||||
context = context,
|
||||
content = "暂无相机权限,请授权后重试",
|
||||
confirmBlock = {
|
||||
// 打开 App 设置页面
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = android.net.Uri.fromParts("package", packageName, null)
|
||||
}
|
||||
startActivity(intent) {
|
||||
// 从设置页面返回,重新检查权限
|
||||
val isGrantedCameraPermission = hasCameraPermission()
|
||||
checkCameraPermission(isGrantedCameraPermission)
|
||||
}
|
||||
}
|
||||
).show()
|
||||
}
|
||||
|
||||
private var checkNetworkTimer: CountDownTimer? = null
|
||||
|
||||
private fun initNetworkTimer() {
|
||||
@@ -157,17 +167,57 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
loadNetworkErrorDialog()
|
||||
return
|
||||
}
|
||||
getDeviceConfig()
|
||||
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() {
|
||||
RemindDialog(
|
||||
context = context,
|
||||
content = "网络连接异常,请检查WiFi连接状态后重试",
|
||||
confirmBlock = {
|
||||
startActivity(Intent("android.settings.WIFI_SETTINGS"))
|
||||
val intent = Intent("android.settings.WIFI_SETTINGS")
|
||||
startActivity(intent) {
|
||||
// 从设置页面返回,重新检查网络连接状态
|
||||
val isConnected = NetworkUtils.isNetworkConnected(this@InitActivity)
|
||||
if (isConnected.not()) {
|
||||
loadNetworkErrorDialog()
|
||||
return@startActivity
|
||||
}
|
||||
getCollectedFoodVector()
|
||||
}
|
||||
}).show()
|
||||
}
|
||||
|
||||
@@ -184,4 +234,27 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
checkNetworkTimer?.cancel()
|
||||
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
|
||||
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.sw.dualscreen.GlobalKey
|
||||
@@ -14,13 +15,15 @@ import com.sw.dualscreen.ext.gone
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.PaySuccessEvent
|
||||
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
|
||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||
import com.sw.dualscreen.utils.Debouncer
|
||||
import com.sw.dualscreen.utils.SPUtil
|
||||
import com.sw.dualscreen.utils.SoundPoolUtil
|
||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
|
||||
class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
|
||||
@@ -37,9 +40,10 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
const val TAG_PAY_NUMBER = "tagPayNumber"
|
||||
const val TAG_PAY_FACE = "tagPayFace"
|
||||
const val TAG_PAY_RESULT = "tagPayResult"
|
||||
const val PAY_SUCCESS = "pay_success"
|
||||
}
|
||||
|
||||
val userViewModel by viewModels<UserViewModel>()
|
||||
val userViewModel by viewModels<NetViewModelV2>()
|
||||
val recognizeViewModel by viewModels<RecognizeViewModel>()
|
||||
|
||||
override fun getViewModel(): BaseViewModel {
|
||||
@@ -61,10 +65,31 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
|
||||
var foodOrderId: String = ""
|
||||
var eatNum: Int = 1
|
||||
var isMember: Boolean = false
|
||||
|
||||
val presentation by lazy {
|
||||
PayPresentation(
|
||||
activity = this@PayActivity,
|
||||
userViewModel = userViewModel,
|
||||
recognizeViewModel = recognizeViewModel,
|
||||
display = displays[1]
|
||||
)
|
||||
}
|
||||
|
||||
fun playSoundOnPaySuccess() {
|
||||
SoundPoolUtil.getInstance().play(PAY_SUCCESS, 0)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
presentation.show()
|
||||
registerKeyEvent()
|
||||
addBackEventListener()
|
||||
val soundMap = hashMapOf<String, Int>()
|
||||
soundMap[PAY_SUCCESS] = R.raw.pay_success
|
||||
SoundPoolUtil.getInstance().loadR(this, soundMap)
|
||||
|
||||
foodInfo = intent.getParcelableExtra(FOOD_INFO)
|
||||
foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: ""
|
||||
eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1)
|
||||
@@ -74,12 +99,14 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
binding.include.ivPageBack.setOnClickListener { finish() }
|
||||
binding.include.tvPageTitle.text = "下单结算"
|
||||
binding.btnPayQrCode.setOnClickListener {
|
||||
isMember = false
|
||||
if (binding.btnPayQrCode.isChecked) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
showScanQrCodePay()
|
||||
}
|
||||
binding.btnPayCash.setOnClickListener {
|
||||
isMember = false
|
||||
if (binding.btnPayCash.isChecked) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
@@ -101,7 +128,6 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
showFacePay()
|
||||
}
|
||||
|
||||
showScanQrCodePay()
|
||||
}
|
||||
|
||||
@@ -136,45 +162,67 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
qrCodePayFragment?.presentation?.dismiss()
|
||||
cashPayFragment?.presentation?.dismiss()
|
||||
numberPayFragment?.presentation?.dismiss()
|
||||
facePayFragment?.presentation?.dismiss()
|
||||
scanQrCodePayPresentation?.dismiss()
|
||||
presentation.dismiss()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private var scanQrCodePayPresentation: ScanQrCodePayPresentation? = null
|
||||
var memberDiscount = 1.0
|
||||
|
||||
var memberInfo: MemberInfo? = null
|
||||
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
|
||||
this.memberInfo = memberInfo
|
||||
payResultFragment = PayResultFragment.instance(type, memberInfo)
|
||||
showFragment(payResultFragment!!, TAG_PAY_RESULT)
|
||||
if (displays.size > 1) {
|
||||
scanQrCodePayPresentation = ScanQrCodePayPresentation(
|
||||
activity = this,
|
||||
display = displays[1],
|
||||
type = type
|
||||
) {
|
||||
scanQrCodePayPresentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodInfo?.foodName
|
||||
//确认使用vipPrice 还是 specPrice
|
||||
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||
it.totalPrice = (onePrice ?: 0.0) * eatNum
|
||||
// if (displays.size > 1) {
|
||||
// scanQrCodePayPresentation = ScanQrCodePayPresentation(
|
||||
// activity = this,
|
||||
// display = displays[1],
|
||||
// type = type
|
||||
// ) {
|
||||
// scanQrCodePaypresentation.dismiss()
|
||||
// }.also {
|
||||
// it.foodName = foodInfo?.foodName
|
||||
// //确认使用vipPrice 还是 specPrice
|
||||
// val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||
// it.totalPrice = (onePrice ?: 0.0) * eatNum
|
||||
// }
|
||||
// scanQrCodePaypresentation.show()
|
||||
// }
|
||||
presentation.let {
|
||||
it.pageType = PayPresentation.QR_CODE_PAY
|
||||
it.type = type
|
||||
it.foodName = foodInfo?.foodName
|
||||
//确认使用vipPrice 还是 specPrice
|
||||
//val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||
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()
|
||||
}
|
||||
} else {
|
||||
it.totalPrice = (foodInfo?.specPrice ?: 0.0) * eatNum
|
||||
presentation.initView()
|
||||
}
|
||||
scanQrCodePayPresentation?.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateQrCodeImage(qrCodeUrl: String?) {
|
||||
scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl)
|
||||
// scanQrCodePaypresentation.loadQrCodeImage(qrCodeUrl)
|
||||
presentation.loadQrCodeImage(qrCodeUrl)
|
||||
}
|
||||
|
||||
private var debouncer = Debouncer(5000)
|
||||
|
||||
fun showPaySuccess(isVip: Boolean) {
|
||||
EventBus.getDefault().post(PaySuccessEvent())
|
||||
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
|
||||
hidePayTab()
|
||||
debouncer.debounce {
|
||||
playSoundOnPaySuccess()
|
||||
EventBus.getDefault().post(PaySuccessEvent())
|
||||
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
|
||||
hidePayTab()
|
||||
}
|
||||
}
|
||||
|
||||
fun getQrCodeImg(
|
||||
@@ -206,7 +254,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun memberPay(param: HashMap<String, String>, block: () -> Unit) {
|
||||
fun memberPay(param: HashMap<String, String?>, block: () -> Unit) {
|
||||
userViewModel.memberPay(param) {
|
||||
runOnUiThread {
|
||||
if (it.not()) {
|
||||
@@ -251,13 +299,32 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
|
||||
fun bindOrder(userId: String, block: (Boolean) -> Unit) {
|
||||
//0-即放即取,1-余量计量
|
||||
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
||||
val mode = if (pickupMode == 1) 1 else 2
|
||||
userViewModel.bindOrder(userId, foodOrderId, mode = mode) { bindResult ->
|
||||
runOnUiThread {
|
||||
block(bindResult)
|
||||
val uid = userId.toLongOrNull() ?: run {
|
||||
runOnUiThread { block(false) }
|
||||
return
|
||||
}
|
||||
userViewModel.bindUserOrder(
|
||||
userId = uid,
|
||||
orderNo = foodOrderId,
|
||||
mode = mode,
|
||||
onSuccess = { runOnUiThread { block(true) } },
|
||||
onFailure = { runOnUiThread { block(false) } }
|
||||
)
|
||||
}
|
||||
|
||||
fun qrCodePay(scanInfo: String) {
|
||||
showWaitingDialog("支付中……")
|
||||
userViewModel.qrCodePay(scanInfo, foodOrderId, memberInfo?.id) { payState, backData ->
|
||||
Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData")
|
||||
hideWaitingDialog()
|
||||
if (payState) {
|
||||
showPaySuccess(isMember)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// fun bindOrder(userId: String, block: () -> Unit) {
|
||||
@@ -303,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.UpdateRefreshEvent
|
||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
||||
|
||||
val viewModel by viewModels<UserViewModel>()
|
||||
val viewModel by viewModels<NetViewModelV2>()
|
||||
override fun getViewModel(): BaseViewModel {
|
||||
return viewModel
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
||||
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
registerKeyEvent()
|
||||
initBackDispatcher()
|
||||
binding.include.let {
|
||||
it.ivPageBack.setOnClickListener {
|
||||
|
||||
@@ -6,32 +6,43 @@ import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.activity.SettingActivity
|
||||
import com.sw.dualscreen.databinding.FragmentBusinessBinding
|
||||
import com.sw.dualscreen.model.response.ChargeModeEvent
|
||||
import com.sw.dualscreen.model.response.ResetRecognizeEvent
|
||||
import com.sw.dualscreen.sdk.SensorScaleUtils
|
||||
import com.sw.dualscreen.utils.SPUtil
|
||||
import com.sw.dualscreen.utils.SpTool
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
||||
|
||||
// 当前秤的实时读数(克),作为待保存的餐具重量
|
||||
private var dishWeight: Int = 0
|
||||
|
||||
override fun inflateViewBinding(): FragmentBusinessBinding {
|
||||
return FragmentBusinessBinding.inflate(LayoutInflater.from(context))
|
||||
}
|
||||
|
||||
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 {
|
||||
// it.setOnCheckedChangeListener { group, checkedId ->
|
||||
// val mode = if (checkedId == R.id.rbChargeYes) 0 else 1
|
||||
// SPUtil.getInstance().put(GlobalKey.KEY_CHARGE_MODE, mode)
|
||||
// }
|
||||
//0-计费,1-不计费
|
||||
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||
it.check(if (mode == 0) R.id.rbChargeYes else R.id.rbChargeNot)
|
||||
}
|
||||
binding.rgTakeFood.let {
|
||||
// it.setOnCheckedChangeListener { group, checkedId ->
|
||||
// val mode = if (checkedId == R.id.rbPickAndPlace) 0 else 1
|
||||
// SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode)
|
||||
// }
|
||||
//0-即放即取,1-余量计量
|
||||
val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
||||
it.check(if (mode == 0) R.id.rbPickAndPlace else R.id.rbSurplusCalculate)
|
||||
}
|
||||
@@ -39,6 +50,10 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
||||
SensorScaleUtils.tare()
|
||||
}
|
||||
binding.btnConfirm.setOnClickListener { v ->
|
||||
// if (binding.rgSettlement.checkedRadioButtonId == -1) {
|
||||
// ToastUtils.showToast("请设置结算模式")
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
if (binding.rgCharge.checkedRadioButtonId == -1) {
|
||||
ToastUtils.showToast("请设置计费模式")
|
||||
return@setOnClickListener
|
||||
@@ -50,6 +65,7 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
||||
|
||||
val chargeMode = if (binding.rgCharge.checkedRadioButtonId == R.id.rbChargeYes) 0 else 1
|
||||
//原来的支付模式
|
||||
//0-计费,1-不计费
|
||||
val oldChargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||
if (chargeMode != oldChargeMode) {
|
||||
//支付模式有变化通知首页页面刷新
|
||||
@@ -59,7 +75,11 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
||||
|
||||
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_DISH_WEIGHT, dishWeight)
|
||||
// 刷新标签文字,显示当前已保存的餐具重量
|
||||
refreshDishWeightLabel()
|
||||
EventBus.getDefault().post(ResetRecognizeEvent())
|
||||
//ToastUtils.showToast("设置已保存")
|
||||
(activity as SettingActivity).let {
|
||||
it.showWaitingDialog("设置保存中")
|
||||
@@ -70,8 +90,18 @@ class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
|
||||
}
|
||||
}
|
||||
SensorScaleUtils.addWeightListener { value ->
|
||||
val realWeight = (value * 1000).roundToInt()
|
||||
binding.tvFoodWeight.text = "$realWeight"
|
||||
//val realWeight = (value * 1000).roundToInt()
|
||||
// 追踪秤的实时读数(克),作为待保存的餐具重量
|
||||
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.ImageUtil
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import timber.log.Timber
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@@ -101,13 +100,14 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
|
||||
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||
try {
|
||||
val index = foodCollectionList.indexOfFirst { it.imageFile == null }
|
||||
if (index == -1) {
|
||||
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||
settingActivity?.hideWaitingDialog()
|
||||
rerurn@ cameraCallback
|
||||
}
|
||||
activity?.runOnUiThread {
|
||||
activity?.lifecycleScope?.launch(Dispatchers.Main) {
|
||||
// 主线程原子操作:查找空闲槽位并立即标记,避免竞态
|
||||
val index = foodCollectionList.indexOfFirst { it.isShowCamera }
|
||||
if (index == -1) {
|
||||
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||
settingActivity?.hideWaitingDialog()
|
||||
return@launch
|
||||
}
|
||||
foodCollectionList[index].let {
|
||||
it.imageVector = null
|
||||
it.bitmap = null
|
||||
@@ -116,13 +116,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
it.imageUri = uri
|
||||
}
|
||||
collectionAdapter.notifyItemChanged(index)
|
||||
}
|
||||
// settingActivity?.hideWaitingDialog()
|
||||
Thread {
|
||||
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
|
||||
getImageVector(index, bitmap)
|
||||
// IO线程处理bitmap
|
||||
launch(Dispatchers.IO) {
|
||||
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
|
||||
getImageVector(index, bitmap)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
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) {
|
||||
// val bitmap = BitmapCropper.cropCenter(
|
||||
// original = srcBmp,
|
||||
@@ -141,7 +145,9 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
FoodModule.bitmap2FloatArray(bitmap, false)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
ToastUtils.showToast("操作失败")
|
||||
activity?.lifecycleScope?.launch(Dispatchers.Main) {
|
||||
ToastUtils.showToast("操作失败")
|
||||
}
|
||||
settingActivity?.log("操作失败:${e.message}")
|
||||
settingActivity?.hideWaitingDialog()
|
||||
return
|
||||
@@ -151,19 +157,16 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
)
|
||||
settingActivity?.log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||
|
||||
activity?.runOnUiThread {
|
||||
foodCollectionList[index].let {
|
||||
it.imageVector = imageVector
|
||||
it.bitmap = null
|
||||
it.isShowCamera = false
|
||||
it.imageFile = file
|
||||
}
|
||||
collectionAdapter.notifyItemChanged(index)
|
||||
foodCollectionList[index].let {
|
||||
it.imageVector = imageVector
|
||||
it.bitmap = null
|
||||
it.isShowCamera = false
|
||||
it.imageFile = file
|
||||
}
|
||||
settingActivity?.hideWaitingDialog()
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
settingActivity?.hideWaitingDialog()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@@ -173,19 +176,9 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||
return
|
||||
}
|
||||
|
||||
settingActivity?.showWaitingDialog("采集中……")
|
||||
cameraUtils.takePhoto(cameraCallback) { errMsg ->
|
||||
settingActivity?.hideWaitingDialog()
|
||||
// ToastUtils.showToast("拍照异常,请重新操作")
|
||||
// if (cameraErrorCount >= 10) {
|
||||
// ToastUtils.showToast("拍照异常,请重新操作")
|
||||
// settingActivity?.log("拍照异常,请重新操作:$errMsg")
|
||||
// cameraErrorCount = 0
|
||||
// return@takePhoto
|
||||
// }
|
||||
// cameraErrorCount++
|
||||
// takePhoto()
|
||||
}
|
||||
cameraUtils.takePhoto(succCallback = cameraCallback, failCallback = photoFailCallback)
|
||||
}
|
||||
|
||||
private var cameraErrorCount = 0
|
||||
@@ -263,14 +256,15 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
lifecycleScope.launch {
|
||||
val totalFileCount = foodCollectionList.count { it.imageFile != null }
|
||||
settingActivity?.showWaitingDialog2("图片上传中0/$totalFileCount")
|
||||
val params = HashMap<String, RequestBody>()
|
||||
//params["placeId"] = restId.toRequestBody()
|
||||
params["foodId"] = checkedItem!!.foodId.toRequestBody()
|
||||
params["foodName"] = checkedItem!!.foodName!!.toRequestBody()
|
||||
params["version"] = GlobalData.foodModelVersion.toRequestBody()
|
||||
val foodId = checkedItem!!.foodId.toLongOrNull() ?: run {
|
||||
ToastUtils.showToast("foodId 格式错误")
|
||||
return@launch
|
||||
}
|
||||
val foodName = checkedItem!!.foodName ?: ""
|
||||
val version = GlobalData.foodModelVersion
|
||||
|
||||
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 {
|
||||
it.imageVector!!.joinToString(
|
||||
separator = ",", prefix = "[", postfix = "]"
|
||||
@@ -279,8 +273,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
val foodVectorJson =
|
||||
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||
Timber.tag(TAG).d("json=$foodVectorJson")
|
||||
params["foodVector"] = foodVectorJson.toRequestBody()
|
||||
settingActivity?.viewModel?.uploadCollectFoodPics(files, params)
|
||||
settingActivity?.viewModel?.uploadCollect(
|
||||
foodId = foodId,
|
||||
foodName = foodName,
|
||||
version = version,
|
||||
foodVector = foodVectorJson,
|
||||
fileList = files
|
||||
)
|
||||
}, onProgress = { count, batch, idList ->
|
||||
//batch.forEach {
|
||||
// it.uploadSuccess = true
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.databinding.FragmentCashPayBinding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.presentation.pay.CashPayPresentation
|
||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
|
||||
class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
|
||||
@@ -17,8 +17,6 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
|
||||
var presentation: CashPayPresentation? = null
|
||||
|
||||
override fun inflateViewBinding(): FragmentCashPayBinding {
|
||||
return FragmentCashPayBinding.inflate(layoutInflater)
|
||||
}
|
||||
@@ -27,63 +25,73 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
|
||||
payActivity = requireActivity() as PayActivity
|
||||
payActivity.foodInfo?.let {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.specPrice?:0.0) * payActivity.eatNum
|
||||
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
||||
}
|
||||
binding.tvRealAmount.text = getAmountText(payAmount.format2String(2))
|
||||
|
||||
binding.btnConfirmPayFinish.setOnClickListener {
|
||||
payActivity.cashPay(hashMapOf(
|
||||
//标价金额(单位为元)
|
||||
"totalFee" to payAmount.format2String(2),
|
||||
//订单类型(0付款1会员充值)
|
||||
"orderType" to "0",
|
||||
//商户订单号
|
||||
"orderNo" to payActivity.foodOrderId,
|
||||
//支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动
|
||||
"paySource" to "0"
|
||||
)) { paySuccess ->
|
||||
payActivity.cashPay(
|
||||
hashMapOf(
|
||||
//标价金额(单位为元)
|
||||
"totalFee" to payAmount.format2String(2),
|
||||
//订单类型(0付款1会员充值)
|
||||
"orderType" to "0",
|
||||
//商户订单号
|
||||
"orderNo" to payActivity.foodOrderId,
|
||||
//支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动
|
||||
"paySource" to "0"
|
||||
)
|
||||
) { paySuccess ->
|
||||
if (paySuccess.not()) {
|
||||
ToastUtils.showToast("接口调用失败")
|
||||
return@cashPay
|
||||
}
|
||||
payActivity.showPaySuccess( false)
|
||||
payActivity.showPaySuccess(false)
|
||||
}
|
||||
}
|
||||
showSubScreen()
|
||||
}
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
//showSubScreen()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
// override fun onHiddenChanged(hidden: Boolean) {
|
||||
// super.onHiddenChanged(hidden)
|
||||
// if (hidden) {
|
||||
// dismissSubScreen()
|
||||
// return
|
||||
// }
|
||||
// //showSubScreen()
|
||||
// }
|
||||
//
|
||||
// override fun onDestroy() {
|
||||
// dismissSubScreen()
|
||||
// super.onDestroy()
|
||||
// }
|
||||
//
|
||||
// private fun dismissSubScreen() {
|
||||
// }
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
}
|
||||
presentation?.show()
|
||||
// // 查找副屏(通常索引为1)
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation = PayPresentation(
|
||||
// activity = payActivity,
|
||||
// pageType = PayPresentation.CASH_PAY,
|
||||
// userViewModel = payActivity.userViewModel,
|
||||
// recognizeViewModel = payActivity.recognizeViewModel,
|
||||
// display = displays[1]
|
||||
// ) {
|
||||
// presentation?.dismiss()
|
||||
// }.also {
|
||||
// it.foodName = foodName
|
||||
// it.payAmount = payAmount.format2String(2)
|
||||
// }
|
||||
// presentation?.show()
|
||||
// }
|
||||
payActivity.presentation.let {
|
||||
it.pageType = PayPresentation.CASH_PAY
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
it.initView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,13 @@ import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.databinding.FragmentFacePayBinding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.presentation.pay.FacePayPresentation
|
||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||
|
||||
class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
||||
|
||||
private lateinit var payActivity: PayActivity
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
var presentation: FacePayPresentation? = null
|
||||
override fun inflateViewBinding(): FragmentFacePayBinding {
|
||||
return FragmentFacePayBinding.inflate(layoutInflater)
|
||||
}
|
||||
@@ -24,32 +23,36 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
||||
// }
|
||||
payActivity.foodInfo?.let {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
// payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
val price = it.specPrice ?: 0.0
|
||||
payAmount = price * payActivity.memberDiscount * payActivity.eatNum
|
||||
}
|
||||
showSubScreen()
|
||||
}
|
||||
//
|
||||
// override fun onHiddenChanged(hidden: Boolean) {
|
||||
// super.onHiddenChanged(hidden)
|
||||
// if (hidden) {
|
||||
//// dismissSubScreen()
|
||||
// payActivity.presentation.pauseCamera()
|
||||
// return
|
||||
// }
|
||||
// payActivity.presentation.resumeCamera()
|
||||
// //showSubScreen()
|
||||
// }
|
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
//showSubScreen()
|
||||
}
|
||||
// override fun onDestroy() {
|
||||
// dismissSubScreen()
|
||||
// super.onDestroy()
|
||||
// }
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
// private fun dismissSubScreen() {
|
||||
// presentation?.let {
|
||||
// if (it.isShowing) {
|
||||
// it.dismiss()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun loadBitmap(frame: Bitmap) {
|
||||
binding.ivFaceImage.setImageBitmap(frame)
|
||||
@@ -57,21 +60,44 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation = FacePayPresentation(
|
||||
activity = payActivity,
|
||||
display = displays[1],
|
||||
userViewModel = payActivity.userViewModel,
|
||||
recognizeViewModel = payActivity.recognizeViewModel,
|
||||
// parentTextureView = binding.parentTextureView,
|
||||
) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
}
|
||||
presentation?.show()
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation = PayPresentation(
|
||||
// activity = payActivity,
|
||||
// pageType = PayPresentation.FACE_PAY,
|
||||
// userViewModel = payActivity.userViewModel,
|
||||
// recognizeViewModel = payActivity.recognizeViewModel,
|
||||
// display = displays[1]
|
||||
// ) {
|
||||
// presentation?.dismiss()
|
||||
// }.also {
|
||||
// it.foodName = foodName
|
||||
// it.payAmount = payAmount.format2String(2)
|
||||
// }
|
||||
// presentation?.show()
|
||||
// }
|
||||
payActivity.presentation.let {
|
||||
it.pageType = PayPresentation.FACE_PAY
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
isResumeCamera = true
|
||||
it.initView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
payActivity.presentation.pauseCamera()
|
||||
}
|
||||
|
||||
private var isResumeCamera = false
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isResumeCamera) {
|
||||
payActivity.presentation.resumeCamera()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.sw.dualscreen.activity.fragment.pay
|
||||
|
||||
import android.text.InputType
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.ext.hideKeyboard
|
||||
import com.sw.dualscreen.presentation.pay.CashPayPresentation
|
||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
|
||||
class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
@@ -13,7 +15,8 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
private lateinit var payActivity: PayActivity
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
var presentation: CashPayPresentation? = null
|
||||
// 密码是否显示的标志
|
||||
private var isPasswordVisible = false
|
||||
override fun inflateViewBinding(): FragmentNumberPayBinding {
|
||||
return FragmentNumberPayBinding.inflate(layoutInflater)
|
||||
}
|
||||
@@ -28,6 +31,12 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
||||
}
|
||||
|
||||
// 密码显示/隐藏切换
|
||||
binding.ivTogglePassword.setOnClickListener {
|
||||
togglePasswordVisibility()
|
||||
}
|
||||
|
||||
binding.btnConfirm.setOnClickListener { v ->
|
||||
val phone = binding.etInputPhone.text.toString().trim()
|
||||
val key = binding.etInputNumber.text.toString().trim()
|
||||
@@ -42,7 +51,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
// ToastUtils.showToast("会员验证成功")
|
||||
// }
|
||||
// }
|
||||
|
||||
payActivity.isMember = false
|
||||
payActivity.getMemberInfoByPhone(phone, key) { memberInfo ->
|
||||
if (memberInfo == null) {
|
||||
binding.root.postDelayed({
|
||||
@@ -51,6 +60,9 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
}, 1000)
|
||||
return@getMemberInfoByPhone
|
||||
}
|
||||
//肯定是会员
|
||||
memberInfo.member = true
|
||||
payActivity.isMember = true
|
||||
//绑定订单使用faceUserId
|
||||
payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
|
||||
if (bindResult.not()) {
|
||||
@@ -60,7 +72,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
}
|
||||
binding.root.postDelayed({
|
||||
payActivity.hideWaitingDialog()
|
||||
payActivity.showPayInfo(type = 1, isVip = true, memberInfo = memberInfo)
|
||||
payActivity.showPayInfo(type = 1, isVip = memberInfo.member, memberInfo = memberInfo)
|
||||
payActivity.hidePayTab()
|
||||
}, 1000)
|
||||
}
|
||||
@@ -79,42 +91,76 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
showSubScreen()
|
||||
}
|
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
//showSubScreen()
|
||||
}
|
||||
// override fun onHiddenChanged(hidden: Boolean) {
|
||||
// super.onHiddenChanged(hidden)
|
||||
// if (hidden) {
|
||||
// dismissSubScreen()
|
||||
// return
|
||||
// }
|
||||
// //showSubScreen()
|
||||
// }
|
||||
//
|
||||
// override fun onDestroy() {
|
||||
// dismissSubScreen()
|
||||
// super.onDestroy()
|
||||
// }
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
// private fun dismissSubScreen() {
|
||||
// presentation?.let {
|
||||
// if (it.isShowing) {
|
||||
// it.dismiss()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
}
|
||||
presentation?.show()
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation = PayPresentation(
|
||||
// activity = payActivity,
|
||||
// pageType = PayPresentation.CASH_PAY,
|
||||
// userViewModel = payActivity.userViewModel,
|
||||
// recognizeViewModel = payActivity.recognizeViewModel,
|
||||
// display = displays[1]
|
||||
// ).also {
|
||||
// it.foodName = foodName
|
||||
// it.payAmount = payAmount.format2String(2)
|
||||
// }
|
||||
// presentation?.show()
|
||||
// }
|
||||
payActivity.presentation.let {
|
||||
it.pageType = PayPresentation.CASH_PAY
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
it.initView()
|
||||
}
|
||||
}
|
||||
|
||||
// 切换密码显示/隐藏
|
||||
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) {
|
||||
// payActivity.bindOrder(userId) { bindResult ->
|
||||
// if (bindResult.not()) {
|
||||
|
||||
@@ -74,8 +74,10 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
memberInfo = it.getParcelable(MEMBER_INFO)
|
||||
}
|
||||
|
||||
if (memberInfo != null) {
|
||||
totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
if (memberInfo != null && memberInfo!!.member) {
|
||||
// totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
val price = payActivity.foodInfo?.specPrice ?: 0.0
|
||||
totalPrice = price * payActivity.memberDiscount * payActivity.eatNum
|
||||
binding.layoutVip.visible()
|
||||
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
|
||||
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
@@ -112,9 +114,15 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
|
||||
getAmountList()
|
||||
)
|
||||
if (memberInfo != null && memberInfo!!.member) {
|
||||
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
|
||||
getAmountList()
|
||||
)
|
||||
} else {
|
||||
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
|
||||
getAmountListNotMember()
|
||||
)
|
||||
}
|
||||
if (balance >= totalPrice) {
|
||||
binding.ivPayQrCode.invisible()
|
||||
binding.btnConfirmPay.run {
|
||||
@@ -201,6 +209,21 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
//扣除余额
|
||||
private var expensesBalance = 0.0
|
||||
|
||||
private fun getAmountListNotMember(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
list.add(TextBean(text = "应付金额 ", textSize = 30, textColor = "#FF889AC2"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = "-${totalPrice.format2String(2)}",
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF889AC2"))
|
||||
return list
|
||||
}
|
||||
private fun getAmountList(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
@@ -268,7 +291,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
//订单号
|
||||
"orderNo" to payActivity.foodOrderId,
|
||||
//用户id
|
||||
"memberId" to memberInfo!!.id!!
|
||||
"memberId" to memberInfo?.id
|
||||
),
|
||||
block = callback
|
||||
)
|
||||
|
||||
+31
-25
@@ -8,11 +8,11 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.ext.load
|
||||
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
|
||||
import com.sw.dualscreen.utils.countDownByFlow
|
||||
import kotlinx.coroutines.Job
|
||||
import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||
import com.sw.dualscreen.utils.IntervalExecutor
|
||||
|
||||
class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
@@ -25,7 +25,6 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
private var payQrCodePic: String? = null
|
||||
var presentation: ScanQrCodePayPresentation? = null
|
||||
private var countDownJob: Job? = null
|
||||
|
||||
override fun inflateViewBinding(): FragmentScanQrcodePayBinding {
|
||||
@@ -37,7 +36,7 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
|
||||
payActivity.foodInfo?.let {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.specPrice?:0.0)*payActivity.eatNum
|
||||
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
||||
}
|
||||
binding.tvRealAmount.text = getAmountText(payAmount.format2String(2))
|
||||
//不需要绑定会员id
|
||||
@@ -60,41 +59,48 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
// if (hidden) {
|
||||
//// dismissSubScreen()
|
||||
// return
|
||||
// }
|
||||
//showSubScreen()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
// dismissSubScreen()
|
||||
countDownJob?.cancel() // 自动取消订阅,防止内存泄漏
|
||||
payTaskJob?.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
// presentation?.let {
|
||||
// if (it.isShowing) {
|
||||
// it.dismiss()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation =
|
||||
ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.totalPrice = payAmount ?: 0.0
|
||||
it.payQrCodePic = payQrCodePic
|
||||
}
|
||||
presentation?.show()
|
||||
// // 查找副屏(通常索引为1)
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation =
|
||||
// ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
|
||||
// presentation?.dismiss()
|
||||
// }.also {
|
||||
// it.foodName = foodName
|
||||
// it.totalPrice = payAmount ?: 0.0
|
||||
// it.payQrCodePic = payQrCodePic
|
||||
// }
|
||||
// presentation?.show()
|
||||
// }
|
||||
payActivity.presentation.let {
|
||||
it.pageType = PayPresentation.QR_CODE_PAY
|
||||
it.foodName = foodName
|
||||
it.totalPrice = payAmount ?: 0.0
|
||||
it.payQrCodePic = payQrCodePic
|
||||
it.initView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,17 @@ import androidx.core.graphics.toColorInt
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.databinding.ListItemSearchFoodBinding
|
||||
import com.sw.dualscreen.databinding.ListItemSearchFood3Binding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
|
||||
class CollectFoodListAdapter(var list: MutableList<FoodInfo>) :
|
||||
BaseQuickAdapter<FoodInfo, CollectFoodListAdapter.VH>(list) {
|
||||
inner class VH(var binding: ListItemSearchFoodBinding) : QuickViewHolder(binding.root)
|
||||
inner class VH(var binding: ListItemSearchFood3Binding) : QuickViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val binding = ListItemSearchFoodBinding.inflate(inflater, parent, false)
|
||||
val binding = ListItemSearchFood3Binding.inflate(inflater, parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.sw.dualscreen.databinding.ListItemCollectedFoodBinding
|
||||
import com.sw.dualscreen.ext.gone
|
||||
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>) :
|
||||
BaseQuickAdapter<CollectedFoodInfo, CollectedFoodNewAdapter.VH>(list) {
|
||||
class CollectedFoodNewAdapter(var list: MutableList<CollectedFoodV2>) :
|
||||
BaseQuickAdapter<CollectedFoodV2, CollectedFoodNewAdapter.VH>(list) {
|
||||
|
||||
inner class VH(var binding: ListItemCollectedFoodBinding) : QuickViewHolder(binding.root)
|
||||
|
||||
@@ -21,7 +21,7 @@ class CollectedFoodNewAdapter(var list: MutableList<CollectedFoodInfo>) :
|
||||
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.tvCollectedNum.text = "已采集${item?.foodCount}"
|
||||
holder.binding.divider.run {
|
||||
|
||||
@@ -40,7 +40,7 @@ class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
|
||||
//setImageURI(it.imageUri)
|
||||
//setImageBitmap(it.bitmap)
|
||||
if (it.imageUri!=null) {
|
||||
setImageURI(it.imageUri)
|
||||
load(it.imageUri)
|
||||
} else {
|
||||
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.Rect
|
||||
import android.graphics.YuvImage
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.TouchDelegate
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
@@ -159,3 +162,35 @@ fun View.invisible() {
|
||||
fun View.gone() {
|
||||
visibility = View.GONE
|
||||
}
|
||||
|
||||
val Context.screenSize: IntArray
|
||||
get() {
|
||||
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
val displayMetrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
return intArrayOf(
|
||||
displayMetrics.widthPixels,
|
||||
displayMetrics.heightPixels
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩大 View 的点击区域
|
||||
* @param horizontal 水平方向扩大距离(dp)→ 左右各扩大一半
|
||||
* @param vertical 垂直方向扩大距离(dp)→ 上下各扩大一半
|
||||
*/
|
||||
fun View.expandClickArea(horizontal: Int, vertical: Int) {
|
||||
val density = resources.displayMetrics.density
|
||||
val hPx = (horizontal * density).toInt()
|
||||
val vPx = (vertical * density).toInt()
|
||||
|
||||
post {
|
||||
val rect = android.graphics.Rect()
|
||||
getHitRect(rect)
|
||||
|
||||
// 扩大区域:负值 = 向外扩大
|
||||
rect.inset(-hPx, -vPx)
|
||||
//rect.bottom += downPx // 只往下扩大
|
||||
(parent as View).touchDelegate = TouchDelegate(rect, this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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? = "",
|
||||
|
||||
//即放即取-1,称重-2
|
||||
var mode: Int
|
||||
var mode: Int,
|
||||
//设备模式,1 结算终端,2档口机
|
||||
var paymentFrom: Int,
|
||||
//是否会员
|
||||
var member: Boolean
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
@@ -54,7 +58,8 @@ data class MemberInfo(
|
||||
//赠送余额
|
||||
val rewardBalance: Double?,
|
||||
//积分余额
|
||||
val integralBalance: Int?
|
||||
val integralBalance: Int?,
|
||||
var member: Boolean = false
|
||||
) : Parcelable
|
||||
|
||||
data class TextBean(
|
||||
@@ -95,12 +100,14 @@ data class UserNutrition(
|
||||
val fruitsVegetablesNearExcess: String? = null,
|
||||
//果蔬超量
|
||||
val fruitsVegetablesExcess: String? = null,
|
||||
//肉蛋推荐值
|
||||
val meatEggsRecommend: String? = null,
|
||||
//肉蛋即将超量
|
||||
val meatEggsNearExcess: String? = null,
|
||||
//肉蛋超量
|
||||
val meatEggsExcess: String? = null,
|
||||
//肉蛋豆推荐值
|
||||
val meatEggsBeansRecommend: String? = null,
|
||||
//肉蛋豆即将超量
|
||||
val meatEggsBeansNearExcess: String? = null,
|
||||
//肉蛋豆超量
|
||||
val meatEggsBeansExcess: String? = null,
|
||||
//总重量
|
||||
val eatWeightSum:Double? = null,
|
||||
)
|
||||
|
||||
//{"code":"00000","data":"{\"orderType\":0,\"orderPayFrom\":2,\"payType\":\"1\",\"paySuc\":0,\"totalFee\":0.01,\"orderCode\":\"1998193614899372032\",\"message\":\"支付成功\",\"payCode\":\"1998193614899372032508227\"}","msg":"成功","total":0}
|
||||
@@ -122,7 +129,7 @@ data class RespCodeMsg(
|
||||
|
||||
|
||||
data class PostEvent(var name: String = "")
|
||||
data class UpdateRefreshEvent(var name:String = "")
|
||||
data class UpdateRefreshEvent(var name: String = "")
|
||||
data class PaySuccessEvent(
|
||||
var name: String = ""
|
||||
)
|
||||
@@ -130,6 +137,9 @@ data class PaySuccessEvent(
|
||||
data class ChargeModeEvent(
|
||||
var chargeMode: Int = 0
|
||||
)
|
||||
data class ResetRecognizeEvent(
|
||||
var state: Int = 0
|
||||
)
|
||||
|
||||
data class ClickBackEvent(
|
||||
var name: String = ""
|
||||
@@ -138,7 +148,9 @@ data class ClickBackEvent(
|
||||
data class DeviceConfig(
|
||||
var arcsoftAppId: String? = null,
|
||||
var arcsoftSdkKey: String? = null,
|
||||
var arcsoftActiveKey: String? = null
|
||||
var arcsoftActiveKey: String? = null,
|
||||
//结算模式:1-独立支付模式,2-联合结算模式
|
||||
var payType: Int = 1
|
||||
)
|
||||
|
||||
data class FoodVector(
|
||||
@@ -157,4 +169,33 @@ data class WeightRecord(
|
||||
var foodInfo: FoodInfo? = null,
|
||||
var lastWeight: Int = 0,
|
||||
var currentWeight: Int = 0
|
||||
)
|
||||
|
||||
data class FoodItem(
|
||||
var id: String? = null,
|
||||
var foodId: String? = null,
|
||||
var foodName: String? = null,
|
||||
var foodMaterialId: String? = null,
|
||||
var specId: String? = null,
|
||||
var num: Int = 0,
|
||||
var eatNum: Int = 0,
|
||||
var price: Double = 0.0,
|
||||
var foodPrice: Double = 0.0,
|
||||
var discount: Double = 0.0,
|
||||
var income: Double = 0.0,
|
||||
var foodDifferenceId: Long = 0,
|
||||
var createTime: String? = null,
|
||||
var specName: String? = null,
|
||||
var specWeight: Int = 0,
|
||||
var orderFrom: Int = 0,
|
||||
var isLocalData: Boolean = false
|
||||
)
|
||||
|
||||
data class FoodOrderModel(
|
||||
val calorie: Double = 0.0,
|
||||
val incomeSum: Double = 0.0,
|
||||
val discountSum: Double = 0.0,
|
||||
val eatWeightSum: Int = 0,
|
||||
val orderNo: String? = null,
|
||||
val list: List<FoodItem>? = null
|
||||
)
|
||||
@@ -43,6 +43,8 @@ data class FoodInfo(
|
||||
val recommendCalorie: Double? = null,
|
||||
//图片
|
||||
var foodImg: String? = "",
|
||||
//按单烹制 ID(菜品列表返回,就餐时原样回传,用于溯源码追踪)
|
||||
var cookOrderId: String? = "",
|
||||
|
||||
//-----------------------------------
|
||||
var score: Int = 0,
|
||||
|
||||
@@ -32,7 +32,7 @@ data class UserFaceModel(
|
||||
/**
|
||||
* 是否会员
|
||||
*/
|
||||
val isMember: Boolean
|
||||
val member: Boolean
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -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
|
||||
|
||||
import com.sw.dualscreen.BuildConfig
|
||||
import com.sw.dualscreen.GlobalData
|
||||
import com.sw.dualscreen.MyApp
|
||||
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.repository.RemoteRepository
|
||||
import com.sw.dualscreen.repository.v2.RemoteRepositoryV2
|
||||
import com.sw.dualscreen.utils.FileUtil
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
@@ -11,22 +18,36 @@ import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
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()
|
||||
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
|
||||
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
|
||||
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
|
||||
.addNetworkInterceptor(HttpLoggingInterceptor(logger = {
|
||||
Timber.d("okhttp logger ==>${it}")
|
||||
}).apply {
|
||||
level = if (MyApp.DEBUG) {
|
||||
HttpLoggingInterceptor.Level.BODY
|
||||
} else {
|
||||
HttpLoggingInterceptor.Level.NONE
|
||||
.addNetworkInterceptor(Interceptor { chain ->
|
||||
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 {
|
||||
level = if (BuildConfig.DEBUG) {
|
||||
HttpLoggingInterceptor.Level.BODY
|
||||
} else {
|
||||
HttpLoggingInterceptor.Level.NONE
|
||||
}
|
||||
}
|
||||
loggingInterceptor.intercept(chain)
|
||||
})
|
||||
.addInterceptor(RequestInterceptor())
|
||||
.build()
|
||||
@@ -35,10 +56,21 @@ object ApiClient {
|
||||
.baseUrl(BASE_URL)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
// .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 协程适配器
|
||||
.build()
|
||||
|
||||
val apiService: ApiService by lazy {
|
||||
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.FoodInfo
|
||||
import com.sw.dualscreen.model.response.FoodOrder
|
||||
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||
import com.sw.dualscreen.model.response.FoodVector
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
@@ -202,7 +203,7 @@ interface ApiService {
|
||||
@POST
|
||||
suspend fun memberPay(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/memberPay",
|
||||
@Body param: HashMap<String, String>
|
||||
@Body param: HashMap<String, String?>
|
||||
): ApiResponse<Any?>
|
||||
|
||||
/**
|
||||
@@ -216,6 +217,16 @@ interface ApiService {
|
||||
@Query("totalFee") totalFee: String? = null
|
||||
): ApiResponse<String?>
|
||||
|
||||
/**
|
||||
* 二维码支付
|
||||
* @param orderType 0付款 1会员充值
|
||||
*/
|
||||
@POST
|
||||
suspend fun qrCodePay(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay",
|
||||
@Body param: HashMap<String, String?>
|
||||
): ApiResponse<String?>
|
||||
|
||||
/**
|
||||
* 开餐-生成订单
|
||||
*/
|
||||
@@ -301,4 +312,22 @@ interface ApiService {
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/deleteBoothMachineCollection",
|
||||
@QueryMap param: MutableMap<String, String?>
|
||||
): ApiResponse<Any?>
|
||||
|
||||
/**
|
||||
* 查询就餐数据
|
||||
*/
|
||||
@GET
|
||||
suspend fun getFoodOrderList(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/settlement/terminal/app/getFoodInfoBySettlement",
|
||||
@Query("userId") userId: String
|
||||
): ApiResponse<FoodOrderModel?>
|
||||
|
||||
/**
|
||||
* 查询会员折扣
|
||||
*/
|
||||
@GET
|
||||
suspend fun getMemberDiscount(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getMemberDiscount",
|
||||
@Query("userId") userId: String
|
||||
): ApiResponse<Double?>
|
||||
}
|
||||
@@ -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 org.pytorch.IValue
|
||||
import org.pytorch.Module
|
||||
import org.pytorch.Tensor
|
||||
import org.pytorch.torchvision.TensorImageUtils
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
@@ -29,6 +30,9 @@ object FoodModule {
|
||||
// private const val THRESHOLD = 0.0
|
||||
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 labelsList: IntArray
|
||||
// private lateinit var classInfo: FoodClassInfo
|
||||
@@ -46,6 +50,23 @@ object FoodModule {
|
||||
withContext(Dispatchers.IO) {
|
||||
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
||||
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()
|
||||
if (list.isNotEmpty()) {
|
||||
@@ -86,8 +107,28 @@ object FoodModule {
|
||||
if (module == null) {
|
||||
return null
|
||||
}
|
||||
val outputTensor = module?.forward(IValue.from(inputTensor))?.toTensor()
|
||||
return outputTensor?.dataAsFloatArray
|
||||
Timber.tag(TAG).d("module:${module}")
|
||||
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) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
@@ -133,18 +174,10 @@ object FoodModule {
|
||||
//查询比较分数
|
||||
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
||||
// val idScoreList = query.findIdsWithScores()
|
||||
val objScoreList = ObjectBox.query(floatArray, queryCount)
|
||||
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}")
|
||||
val nameScoreList = mutableListOf<IdNameScore>()
|
||||
objScoreList.forEach {
|
||||
val food = it.get()
|
||||
nameScoreList.add(
|
||||
IdNameScore(
|
||||
id = food.id,
|
||||
name = food.foodName ?: "",
|
||||
score = it.score
|
||||
)
|
||||
)
|
||||
val tripleList = ObjectBox.query(floatArray, queryCount)
|
||||
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(tripleList)}")
|
||||
val nameScoreList = tripleList.map { (id, name, score) ->
|
||||
IdNameScore(id = id, name = name ?: "", score = score)
|
||||
}
|
||||
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
||||
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> {
|
||||
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}")
|
||||
|
||||
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||
@@ -161,28 +196,26 @@ object FoodModule {
|
||||
return emptyList()
|
||||
}
|
||||
val maxScoreList = nameScoreList
|
||||
.filter { it.score < 1 - THRESHOLD }
|
||||
// .filter { it.score < 1 - THRESHOLD }
|
||||
.groupBy { it.name }
|
||||
.map { (_, value) -> value.minByOrNull { it.score }!! }
|
||||
.toMutableList()
|
||||
val map = mutableMapOf<String, Int>()
|
||||
nameScoreList.forEach {
|
||||
val key = it.name
|
||||
val count = map[key] ?: 0
|
||||
map[key] = count + 1
|
||||
}
|
||||
val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
|
||||
val firstFood = nameScoreList[0].name
|
||||
orderList.remove(firstFood)
|
||||
orderList.add(0, firstFood)
|
||||
|
||||
val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
||||
orderList.indexOf(it.name)
|
||||
})
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
//Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
||||
// val map = mutableMapOf<String, Int>()
|
||||
// nameScoreList.forEach {
|
||||
// val key = it.name
|
||||
// val count = map[key] ?: 0
|
||||
// map[key] = count + 1
|
||||
// }
|
||||
// val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
|
||||
// val firstFood = nameScoreList[0].name
|
||||
// orderList.remove(firstFood)
|
||||
// orderList.add(0, firstFood)
|
||||
//
|
||||
// val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
||||
// orderList.indexOf(it.name)
|
||||
// })
|
||||
val sortedScoreList = maxScoreList.sortedBy { it.score }
|
||||
Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toString()}")
|
||||
return sortedScoreList
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,13 @@ package com.sw.dualscreen.objbox
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import com.sw.dualscreen.BuildConfig
|
||||
import com.sw.plate.App
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.BoxStore
|
||||
import io.objectbox.BoxStoreBuilder
|
||||
import io.objectbox.android.Admin
|
||||
import io.objectbox.android.ObjectBoxLiveData
|
||||
|
||||
import io.objectbox.config.DebugFlags
|
||||
import io.objectbox.exception.DbException
|
||||
import io.objectbox.exception.FileCorruptException
|
||||
@@ -91,14 +92,14 @@ object ObjectBox {
|
||||
}
|
||||
}
|
||||
|
||||
// if (BuildConfig.DEBUG) {
|
||||
val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
|
||||
Timber.tag(TAG)
|
||||
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
|
||||
// Enable ObjectBox Admin on debug builds.
|
||||
// https://docs.objectbox.io/data-browser
|
||||
Admin(boxStore).start(context.applicationContext)
|
||||
// }
|
||||
if (BuildConfig.DEBUG) {
|
||||
val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
|
||||
Timber.tag(TAG)
|
||||
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
|
||||
// Enable ObjectBox Admin on debug builds.
|
||||
// https://docs.objectbox.io/data-browser
|
||||
Admin(boxStore).start(context.applicationContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,24 +144,126 @@ object ObjectBox {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
} finally {
|
||||
// 线程资源清理
|
||||
boxStore?.closeThreadResources()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
||||
val query: Query<Food>? = getBox<Food>()?.query()
|
||||
?.equal(Food_.isDel, false)
|
||||
?.and()
|
||||
?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||
?.build()
|
||||
val box = getBox<Food>() ?: return@safeDbOp emptyList<Triple<Long, String?, Double>>()
|
||||
|
||||
val query: Query<Food> = box.query()
|
||||
.equal(Food_.isDel, false)
|
||||
.and()
|
||||
.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||
.build()
|
||||
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 {
|
||||
// 先关闭Query,释放Cursor
|
||||
query?.close()
|
||||
query.close()
|
||||
}
|
||||
} ?: 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 {
|
||||
getBox<Food>()?.get(id)
|
||||
}
|
||||
@@ -177,16 +280,13 @@ object ObjectBox {
|
||||
getBox<Food>()?.all
|
||||
} ?: emptyList()
|
||||
|
||||
suspend fun filter(name: String?, isDistinct: Boolean = false) = safeDbOp {
|
||||
val list = if (isDistinct) {
|
||||
getBox<Food>()?.all?.distinctBy { it.foodName }
|
||||
suspend fun filter(name: String?) = safeDbOp {
|
||||
if (!name.isNullOrBlank()) {
|
||||
getBox<Food>()?.query(Food_.foodName.contains(name))?.build()?.find()
|
||||
?.distinctBy { it.foodName }
|
||||
} else {
|
||||
getBox<Food>()?.all
|
||||
}
|
||||
if (name.isNullOrBlank().not()) {
|
||||
list?.filter { it.foodName?.contains(name) == true }
|
||||
}
|
||||
list
|
||||
getBox<Food>()?.all?.distinctBy { it.foodName }
|
||||
} ?: emptyList()
|
||||
} ?: emptyList()
|
||||
|
||||
suspend fun removeAll() = safeDbOp {
|
||||
@@ -194,10 +294,7 @@ object ObjectBox {
|
||||
}
|
||||
|
||||
suspend fun remove(name: String) = safeDbOp {
|
||||
getBox<Food>()?.run {
|
||||
val filterIdList = all.filter { it.foodName == name }.map { it.id }
|
||||
removeByIds(filterIdList)
|
||||
}
|
||||
getBox<Food>()?.query(Food_.foodName.equal(name))?.build()?.remove()
|
||||
}
|
||||
|
||||
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
|
||||
@@ -207,9 +304,7 @@ object ObjectBox {
|
||||
// 检查存储是否可读写(操作数据库前调用)
|
||||
fun isStorageAvailable(context: Context): Boolean {
|
||||
return try {
|
||||
val state = Environment.getExternalStorageState()
|
||||
val innerDir = context.filesDir
|
||||
Environment.MEDIA_MOUNTED == state && innerDir.canRead() && innerDir.canWrite()
|
||||
context.filesDir.canRead() && context.filesDir.canWrite()
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,49 +1,51 @@
|
||||
package com.sw.dualscreen.presentation
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Presentation
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Point
|
||||
import android.hardware.Camera
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.arcsoft.face.ErrorInfo
|
||||
import com.sw.dualscreen.BuildConfig
|
||||
import com.sw.dualscreen.GlobalKey
|
||||
import com.sw.dualscreen.MyApp
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.activity.MainActivity
|
||||
import com.sw.dualscreen.adapter.FoodOrderAdapter
|
||||
import com.sw.dualscreen.databinding.PresentationMainScreenBinding
|
||||
import com.sw.dualscreen.ext.dp
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.ext.gone
|
||||
import com.sw.dualscreen.ext.invisible
|
||||
import com.sw.dualscreen.ext.load
|
||||
import com.sw.dualscreen.ext.maskName
|
||||
import com.sw.dualscreen.ext.roundedDecimalPlace
|
||||
import com.sw.dualscreen.ext.screenSize
|
||||
import com.sw.dualscreen.ext.visible
|
||||
import com.sw.dualscreen.model.response.DinnerType
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.FoodItem
|
||||
import com.sw.dualscreen.model.response.UserEnergy
|
||||
import com.sw.dualscreen.model.response.UserNutrition
|
||||
import com.sw.dualscreen.utils.Debouncer
|
||||
import com.sw.dualscreen.utils.SPUtil
|
||||
import com.sw.dualscreen.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.ToastUtils
|
||||
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.RecognizeConfiguration
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
|
||||
class MainScreenPresentation(
|
||||
val activity: MainActivity,
|
||||
display: Display,
|
||||
val userViewModel: UserViewModel,
|
||||
val userViewModel: NetViewModelV2,
|
||||
val recognizeViewModel: RecognizeViewModel,
|
||||
private val onDismissListener: () -> Unit = {}
|
||||
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
companion object {
|
||||
const val TAG = "SubScreenPresentation"
|
||||
const val TAG = "MainScreenPresentation"
|
||||
}
|
||||
|
||||
private lateinit var binding: PresentationMainScreenBinding
|
||||
@@ -98,14 +101,14 @@ class MainScreenPresentation(
|
||||
private var irFaceRectTransformer: FaceRectTransformer? = null
|
||||
|
||||
private var detectWeight = 0.0 //识别菜品时的重量
|
||||
var lastWeight = 0.0 // 上一次的计算热量结果
|
||||
var lastWeight = 0 // 上一次的计算热量结果
|
||||
|
||||
// private var userNutritionData: UserNutritionData? = null
|
||||
private var userNutrition: UserNutrition? = null
|
||||
private var dinnerTypeInfo: DinnerType? = null
|
||||
private val debouncer = Debouncer(500)
|
||||
private val debouncer = Debouncer(1500)
|
||||
private var recognitionTime: Long = 0L // 人脸识别时的时间
|
||||
var recognitionWeight: Double = 0.0 // 人脸识别时的重量
|
||||
var recognitionWeight: Int = 0 // 人脸识别时的重量
|
||||
|
||||
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
|
||||
var mealPickupMode: Int = 0 // 取餐模式 0 即放即取 1 余量取餐
|
||||
@@ -114,6 +117,7 @@ class MainScreenPresentation(
|
||||
var isKeepFaceState = false
|
||||
|
||||
var currentUserId: String? = null
|
||||
var isMember: Boolean = false
|
||||
|
||||
var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
|
||||
|
||||
@@ -121,11 +125,13 @@ class MainScreenPresentation(
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = PresentationMainScreenBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
//0-即放即取,1-余量计量
|
||||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||
window?.setBackgroundDrawableResource(android.R.color.white)
|
||||
initView()
|
||||
//0-计费,1-不计费
|
||||
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||
if (chargeMode == 1) {
|
||||
if (activity.settlementMode == 0 || chargeMode == 1) {
|
||||
setupArcCamera()
|
||||
}
|
||||
registerDataChange()
|
||||
@@ -134,71 +140,83 @@ class MainScreenPresentation(
|
||||
|
||||
private var isFirstGetWeight = true
|
||||
|
||||
fun updateWeight(weight: Double) {
|
||||
lastWeight = weight * 1000 // 将千克转成克
|
||||
fun updateWeight(weight: Int) {
|
||||
Timber.tag(TAG)
|
||||
.d("updateWeight lastWeight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1")
|
||||
.d("updateWeight weight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1")
|
||||
//0-即放即取,1-余量计量
|
||||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||
//0-计费,1-不计费
|
||||
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||
// if (mealPickupMode == 1 && chargeMode == 0) {
|
||||
// //余量计量+计费模式
|
||||
// if (isFirstGetWeight && lastWeight > 5) {
|
||||
// recognitionWeight = lastWeight
|
||||
// isFirstGetWeight = false
|
||||
// }
|
||||
// if (lastWeight > 5) {
|
||||
// //余量计量,有重量的情况下不回到重新识别状态
|
||||
// return
|
||||
// }
|
||||
// isFirstGetWeight = true
|
||||
// }
|
||||
if (currentStep == 2 && lastWeight <= 5 && isGoStep1) {
|
||||
if (currentStep == 2 && weight <= MainActivity.WEIGHT_RESET_RECOGNIZE && isGoStep1) {
|
||||
//人脸识别过程中,秤上物品拿走
|
||||
isGoStep1 = false
|
||||
activity.runOnUiThread {
|
||||
Timber.tag(TAG).e("切回step1")
|
||||
Timber.tag(TAG).e("切回step1,------2000000000000")
|
||||
step1FoodRecognizing()
|
||||
}
|
||||
} else if (lastWeight <= 5) {
|
||||
} else if (weight <= MainActivity.WEIGHT_RESET_RECOGNIZE) {
|
||||
isGoStep1 = false
|
||||
activity.runOnUiThread {
|
||||
Timber.tag(TAG).e("切回step1")
|
||||
Timber.tag(TAG).e("切回step1,------2000000000001")
|
||||
if (mealPickupMode == 0) {
|
||||
//即放即取
|
||||
step1FoodRecognizing()
|
||||
} else {
|
||||
//秤上菜品不足,请补充
|
||||
if (currentStep == 1 && currentFood == null) {
|
||||
return@runOnUiThread
|
||||
}
|
||||
//余量计量
|
||||
initCountDownResetJob(weight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (currentStep != 3) {
|
||||
// return
|
||||
// }
|
||||
if (currentFood == null) {
|
||||
Timber.tag(TAG).e("updateWeight currentFood is null")
|
||||
return
|
||||
}
|
||||
|
||||
//if (userNutritionData == null) {
|
||||
if (userNutrition == null) {
|
||||
Timber.tag(TAG).e("updateWeight userNutritionData is null")
|
||||
return
|
||||
}
|
||||
// if (dinnerTypeInfo == null) {
|
||||
// Timber.tag(TAG).e("updateWeight dinnerType is null")
|
||||
// return
|
||||
// }
|
||||
if (chargeMode == 1) {
|
||||
//不计费
|
||||
if (chargeMode == 1 || activity.settlementMode == 0) {
|
||||
//不计费或者联合支付模式更新营养热量数据
|
||||
if (mealPickupMode == 0) {
|
||||
//即放即取
|
||||
calculateNutrition(lastWeight)
|
||||
calculateNutrition(weight)
|
||||
} else {
|
||||
//余量计量
|
||||
calculateNutrition(recognitionWeight - lastWeight)
|
||||
val value = recognitionWeight - weight
|
||||
Timber.tag(TAG).e("updateWeight 余量计量:${recognitionWeight} - ${weight} = ${value}")
|
||||
calculateNutrition(value)
|
||||
}
|
||||
}
|
||||
lastWeight = weight
|
||||
}
|
||||
|
||||
private var countDownResetJob: Job? = null
|
||||
|
||||
private fun initCountDownResetJob(weight: Int) {
|
||||
if (countDownResetJob == null || countDownResetJob?.isActive == false) {
|
||||
countDownResetJob = countDownByFlow(
|
||||
total = 30,
|
||||
scope = activity.lifecycleScope,
|
||||
onStart = {},
|
||||
onTick = { seconds ->
|
||||
Timber.tag(TAG).e("余量计量模型秤重量已清空,等待${seconds}秒后继续重置识别状态")
|
||||
if (weight > MainActivity.WEIGHT_RESET_RECOGNIZE) {
|
||||
countDownResetJob?.cancel()
|
||||
countDownResetJob = null
|
||||
}
|
||||
},
|
||||
onFinish = {
|
||||
step1FoodRecognizing()
|
||||
countDownResetJob?.cancel()
|
||||
countDownResetJob = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setStepChangeCallback(callback: (Int) -> Unit) {
|
||||
@@ -275,16 +293,58 @@ class MainScreenPresentation(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTopInfoVisible() {
|
||||
binding.llTopInfo.visible()
|
||||
binding.flFoodDetail.gone()
|
||||
if (activity.settlementMode == 0) {
|
||||
try {
|
||||
foodOrderList.clear()
|
||||
foodOrderAdapter.submitList(foodOrderList)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
binding.llTopInfo.updateLayoutParams {
|
||||
height = activity.screenSize[1] / 2 + 120.dp
|
||||
}
|
||||
}
|
||||
|
||||
private var isGoRecognize = true
|
||||
|
||||
/**
|
||||
* 显示识别出的菜品信息
|
||||
*/
|
||||
fun step3ShowRecognizeResult(userId: String) {
|
||||
Timber.tag(TAG).d("main,step3,耗时:${System.currentTimeMillis() - startTime}")
|
||||
LightManager.closeRedLight()
|
||||
LightManager.openGreenLight()
|
||||
currentStep = 3
|
||||
stepChangeCallback(currentStep)
|
||||
//根据接口数据更新热量数据--------------------------
|
||||
step3Debouncer.debounce {
|
||||
Timber.tag(TAG).d("main,step3,耗时:${System.currentTimeMillis() - startTime}")
|
||||
LightManager.closeRedLight()
|
||||
LightManager.openGreenLight()
|
||||
currentStep = 3
|
||||
stepChangeCallback(currentStep)
|
||||
|
||||
setTopInfoVisible()
|
||||
if (activity.settlementMode == 0) {
|
||||
//判断是联合支付调用接口查询就餐信息后,再查询热量营养数据
|
||||
loadOrderList(userId) {
|
||||
loadUserNutritionData(userId)
|
||||
}
|
||||
activity.layoutEatDetailBinding.root.visible()
|
||||
} else {
|
||||
//独立支付
|
||||
loadUserNutritionData(userId)
|
||||
}
|
||||
step3Debouncer.reset()
|
||||
activity.isSwitchFood = false
|
||||
}
|
||||
}
|
||||
|
||||
private val step3Debouncer = Debouncer(10*1000)
|
||||
private var memberDiscount:Double = 1.0
|
||||
|
||||
/**
|
||||
* 根据接口数据更新热量、营养数据
|
||||
*/
|
||||
private fun loadUserNutritionData(userId: String) {
|
||||
userViewModel.getUserNutritionData(userId = userId) { nutrition ->
|
||||
activity.runOnUiThread {
|
||||
if (nutrition == null) {
|
||||
@@ -401,7 +461,7 @@ class MainScreenPresentation(
|
||||
}
|
||||
|
||||
private var isLoadUnbilledMode = false
|
||||
private val df by lazy { DecimalFormat("#.##") }
|
||||
// private val df by lazy { DecimalFormat("#.##") }
|
||||
|
||||
/**
|
||||
* 餐品识别成功,不计费模式
|
||||
@@ -410,19 +470,6 @@ class MainScreenPresentation(
|
||||
if (currentUserId.isNullOrBlank()) {
|
||||
return
|
||||
}
|
||||
//todo 清除人脸数据
|
||||
// binding.dualCameraFaceRectView.clearFaceInfo()
|
||||
// clearLeftFaceData()
|
||||
// facePreviewInfoList?.clear()
|
||||
// recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
|
||||
|
||||
// pauseCamera()
|
||||
|
||||
// resumeCamera()
|
||||
|
||||
// TODO: 如何清除人脸数据重新识别
|
||||
// stopCamera()
|
||||
// setupArcCamera()
|
||||
|
||||
isLoadUnbilledMode = true
|
||||
Timber.tag(TAG).d("loadUnbilledMode")
|
||||
@@ -440,15 +487,16 @@ class MainScreenPresentation(
|
||||
|
||||
//updateFoodInfo(foodInfo)
|
||||
binding.nutritionInclude.tvUserName.text = userNutrition?.name.maskName()
|
||||
val recommendCalorie = userNutrition?.recommendCalorie ?: 0.0
|
||||
val recommendCalorie = (userNutrition?.recommendCalorie ?: 0.0).roundedDecimalPlace(1)
|
||||
|
||||
binding.nutritionInclude.tvRecommendHeat.text =
|
||||
"推荐热量:${df.format(recommendCalorie)}kcal"
|
||||
// binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${recommendCalorie.removeTrailingZeros()}kcal"
|
||||
//updateWeight(lastWeight / 1000)
|
||||
// -------------------------------------------------
|
||||
// 删除userNutritionData,改为userNutrition
|
||||
calculateNutrition(recognitionWeight - lastWeight)
|
||||
binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${recommendCalorie}kcal"
|
||||
|
||||
//0-即放即取,1-余量计量
|
||||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||
calculateNutrition(
|
||||
if (mealPickupMode == 1) recognitionWeight - lastWeight
|
||||
else recognitionWeight
|
||||
)
|
||||
binding.root.postDelayed({
|
||||
isLoadUnbilledMode = false
|
||||
}, 10000)
|
||||
@@ -465,24 +513,24 @@ class MainScreenPresentation(
|
||||
private var isFirstOpen = true
|
||||
var startTime = 0L
|
||||
|
||||
var jointPaymentQueryFinish = false
|
||||
|
||||
/**
|
||||
* 餐品识别中
|
||||
*/
|
||||
fun step1FoodRecognizing() {
|
||||
if (currentStep == 1 && currentFood == null) {
|
||||
activity.clearRecognizeFood()
|
||||
return
|
||||
}
|
||||
Timber.tag(TAG).d("main,step1,耗时:${System.currentTimeMillis() - startTime}")
|
||||
startTime = System.currentTimeMillis()
|
||||
jointPaymentQueryFinish = false
|
||||
hideStandbyScreen()
|
||||
activity.resetTouchTime()
|
||||
if (isFirstOpen.not()) {
|
||||
activity.resumeAnalysis()
|
||||
}
|
||||
//if (abs(lastWeight.toInt()) <= 5) {
|
||||
// activity.runOnUiThread {
|
||||
// ToastUtils.showToast("秤上重量几乎为0,请确认")
|
||||
// }
|
||||
// return
|
||||
//}
|
||||
currentStep = 1
|
||||
LightManager.closeGreenLight()
|
||||
LightManager.closeRedLight()
|
||||
|
||||
@@ -490,9 +538,9 @@ class MainScreenPresentation(
|
||||
currentFood = null
|
||||
// userNutritionData = null
|
||||
userNutrition = null
|
||||
recognitionWeight = 0.0
|
||||
recognitionWeight = 0
|
||||
|
||||
stepChangeCallback(currentStep)
|
||||
setTopInfoVisible()
|
||||
|
||||
binding.calorieInclude.root.gone()
|
||||
binding.nutritionInclude.root.gone()
|
||||
@@ -511,31 +559,23 @@ class MainScreenPresentation(
|
||||
binding.flRecognizeIr.gone()
|
||||
|
||||
pauseCamera()
|
||||
activity.clearFoodList()
|
||||
}
|
||||
activity.clearRecognizeFood()
|
||||
|
||||
// fun foodRecSuccess(foodInfo: FoodInfo) {
|
||||
// //识别逻辑完成根据是否计费显示不同页面
|
||||
// val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||
// if (mode == 0) {
|
||||
// //计费
|
||||
// loadBilledMode()
|
||||
// } else {
|
||||
// //不计费
|
||||
// //userViewModel.getUserNutritionData(
|
||||
// // userId = userId,
|
||||
// // foodId = currentFood!!.foodId!!
|
||||
// //)
|
||||
// loadUnbilledMode()
|
||||
// }
|
||||
// }
|
||||
currentStep = 1
|
||||
stepChangeCallback(currentStep)
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户人脸识别中
|
||||
*/
|
||||
fun step2FaceRecognizing(foodInfo: FoodInfo) {
|
||||
fun step2FaceRecognizing(foodInfo: FoodInfo, hideFoodDetail: Boolean = false) {
|
||||
isGoStep1 = true
|
||||
Timber.tag(TAG).d("main,step2,耗时:${System.currentTimeMillis() - startTime}")
|
||||
|
||||
if (hideFoodDetail) {
|
||||
setTopInfoVisible()
|
||||
}
|
||||
|
||||
startTime = System.currentTimeMillis()
|
||||
currentStep = 2
|
||||
LightManager.closeRedLight()
|
||||
@@ -559,7 +599,7 @@ class MainScreenPresentation(
|
||||
dinnerTypeInfo = null
|
||||
// userNutritionData = null
|
||||
userNutrition = null
|
||||
recognitionWeight = 0.0
|
||||
recognitionWeight = 0
|
||||
|
||||
stepChangeCallback(currentStep)
|
||||
currentFood = foodInfo
|
||||
@@ -567,6 +607,10 @@ class MainScreenPresentation(
|
||||
// userViewModel.getDinnerType()
|
||||
|
||||
resumeCamera()
|
||||
|
||||
if (activity.settlementMode == 0) {
|
||||
activity.layoutEatDetailBinding.root.invisible()
|
||||
}
|
||||
}
|
||||
|
||||
fun hideRecImage() {
|
||||
@@ -574,7 +618,7 @@ class MainScreenPresentation(
|
||||
}
|
||||
|
||||
// 当前食物信息
|
||||
private var currentFood: FoodInfo? = null
|
||||
var currentFood: FoodInfo? = null
|
||||
fun updateFood(foodInfo: FoodInfo?) {
|
||||
//if (isLoadUnbilledMode) return
|
||||
Timber.tag(TAG).d("main,updateFood,耗时:${System.currentTimeMillis() - startTime}")
|
||||
@@ -586,6 +630,7 @@ class MainScreenPresentation(
|
||||
binding.tvFoodName.text = foodInfo.foodName
|
||||
binding.ivPreviewImage.gone()
|
||||
binding.ivFrame.gone()
|
||||
//0-计费,1-不计费
|
||||
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||
if (chargeMode == 0) {
|
||||
binding.ivRecImage.let {
|
||||
@@ -597,22 +642,25 @@ class MainScreenPresentation(
|
||||
} else {
|
||||
binding.ivRecImage.gone()
|
||||
}
|
||||
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||
if (mode == 0) {
|
||||
//独立支付+计费模式,使用人脸识别
|
||||
if (activity.settlementMode == 1 && chargeMode == 0) {
|
||||
//计费,查询营养信息、价格信息------------------------
|
||||
loadBilledMode()
|
||||
} else {
|
||||
//不计费
|
||||
//开启人脸识别
|
||||
if (currentStep == 1 || binding.tvFaceTip.isVisible.not()) {
|
||||
step2FaceRecognizing(foodInfo)
|
||||
//联合支付,进行人脸识别
|
||||
if (activity.settlementMode == 0) {
|
||||
if (activity.isSwitchFood.not()) {
|
||||
step2FaceRecognizing(foodInfo)
|
||||
}
|
||||
} else {
|
||||
//不计费,开启人脸识别
|
||||
if (currentStep == 1 || binding.tvFaceTip.isVisible.not()) {
|
||||
if (activity.isSwitchFood.not()) {
|
||||
step2FaceRecognizing(foodInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//if (currentStep == 1) {
|
||||
// step2FaceRecognizing(foodInfo)
|
||||
//} else {
|
||||
// updateFoodInfo(foodInfo)
|
||||
//}
|
||||
}
|
||||
|
||||
private fun updateFoodInfo(foodInfo: FoodInfo) {
|
||||
@@ -636,12 +684,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) {
|
||||
return
|
||||
}
|
||||
var weight = weight1
|
||||
if (weight < 0) weight = 0.0
|
||||
var weight = value
|
||||
if (weight < 0) weight = 0
|
||||
// 扣减餐具重量,后续营养计算、柱图绘制、取餐明细均以净重为准
|
||||
weight = (weight - activity.getDishWeight()).coerceAtLeast(0)
|
||||
if (activity.settlementMode == 0) {
|
||||
activity.runOnUiThread {
|
||||
addFoodToOrder(weight, currentFood!!)
|
||||
}
|
||||
}
|
||||
debouncer.debounce {
|
||||
Timber.tag(TAG)
|
||||
.d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight")
|
||||
@@ -652,7 +777,7 @@ class MainScreenPresentation(
|
||||
currentFood!!,
|
||||
// userNutritionData!!,
|
||||
userNutrition!!,
|
||||
weight,
|
||||
weight.toDouble(),
|
||||
dinnerType = dinnerType ?: ""
|
||||
)
|
||||
// Timber.tag(TAG).d("calculateNutrition calcResultInfo = $calcResultInfo")
|
||||
@@ -666,6 +791,15 @@ class MainScreenPresentation(
|
||||
val fruitsVegetables = energy.fruitsVegetables
|
||||
val meatEggs = energy.meatEggs
|
||||
|
||||
val showTotalCalorie = "总热量:${totalKcal.roundedDecimalPlace(1)}千卡"
|
||||
binding.detailInclude.tvTotalCalorie.text = showTotalCalorie
|
||||
activity.layoutEatDetailBinding.tvTotalCalorie.text = showTotalCalorie
|
||||
|
||||
val totalWeight = (userNutrition?.eatWeightSum ?: 0.0) + weight
|
||||
val showTotalWeight = "总重量:${totalWeight.roundedDecimalPlace(1)}克"
|
||||
binding.detailInclude.tvTotalWeight.text = showTotalWeight
|
||||
activity.layoutEatDetailBinding.tvTotalWeight.text = showTotalWeight
|
||||
|
||||
val include = binding.nutritionInclude
|
||||
|
||||
// 设置热量---------------------------------------------------------------------
|
||||
@@ -684,7 +818,7 @@ class MainScreenPresentation(
|
||||
// 避免0作为被除数
|
||||
maxKcal = if (maxKcal <= 0.0) 1.0 else maxKcal
|
||||
Timber.tag(TAG).d("maxKcal 1 = $maxKcal")
|
||||
include.totalKcalTv.text = totalKcal.format2String(2)
|
||||
include.totalKcalTv.text = totalKcal.roundedDecimalPlace(1).toString()
|
||||
var totalKcalHeight = (totalKcal / maxKcal).toFloat()
|
||||
if (totalKcalHeight == 0F) {
|
||||
include.divKcalView.visible()
|
||||
@@ -702,7 +836,7 @@ class MainScreenPresentation(
|
||||
)
|
||||
val grainIndex = UserNutritionUtils.findCalorieIndex(energy.grain, grainArray)
|
||||
include.customFoodColumn.setImageDrawable(false, grainIndex)
|
||||
include.totalFoodTv.text = grain.format2String(2)
|
||||
include.totalFoodTv.text = grain.roundedDecimalPlace(1).toString()
|
||||
columnMax = if (columnMax <= 0.0) 1.0 else columnMax
|
||||
var grainHeight = (grain / columnMax).toFloat()
|
||||
if (grainHeight == 0F) {
|
||||
@@ -722,7 +856,7 @@ class MainScreenPresentation(
|
||||
val fruitsIndex =
|
||||
UserNutritionUtils.findCalorieIndex(energy.fruitsVegetables, fruitsArray)
|
||||
include.customVegetableColumn.setImageDrawable(false, fruitsIndex)
|
||||
include.totalVegetableTv.text = fruitsVegetables.format2String(2)
|
||||
include.totalVegetableTv.text = fruitsVegetables.roundedDecimalPlace(1).toString()
|
||||
var fruitsHeight = (fruitsVegetables / columnMax).toFloat()
|
||||
if (fruitsHeight == 0F) {
|
||||
include.divVegetableView.visible()
|
||||
@@ -734,14 +868,14 @@ class MainScreenPresentation(
|
||||
|
||||
// 设置肉蛋-----------------------------------------------------------
|
||||
val meatEggsArray = UserNutritionUtils.getCalorieArray(
|
||||
left = userNutrition?.meatEggsRecommend,
|
||||
mid = userNutrition?.meatEggsNearExcess,
|
||||
right = userNutrition?.meatEggsExcess
|
||||
left = userNutrition?.meatEggsBeansRecommend,
|
||||
mid = userNutrition?.meatEggsBeansNearExcess,
|
||||
right = userNutrition?.meatEggsBeansExcess
|
||||
)
|
||||
val meatEggsIndex =
|
||||
UserNutritionUtils.findCalorieIndex(energy.meatEggs, meatEggsArray)
|
||||
include.customMeatColumn.setImageDrawable(false, meatEggsIndex)
|
||||
include.totalMeatTv.text = meatEggs.format2String(2)
|
||||
include.totalMeatTv.text = meatEggs.roundedDecimalPlace(1).toString()
|
||||
var meatHeight = (meatEggs / columnMax).toFloat()
|
||||
if (meatHeight == 0F) {
|
||||
include.divMeatView.visible()
|
||||
@@ -765,6 +899,7 @@ class MainScreenPresentation(
|
||||
*/
|
||||
fun updateMealPickupMode(mode: Int) {
|
||||
Timber.tag(TAG).d("updateMealPickupMode mode = $mode")
|
||||
Timber.tag(TAG).e("切回step1,------2000000000003")
|
||||
mealPickupMode = mode
|
||||
step1FoodRecognizing()
|
||||
}
|
||||
@@ -785,7 +920,16 @@ class MainScreenPresentation(
|
||||
fun resumeCamera() {
|
||||
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
|
||||
isRecognition = true
|
||||
if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) {
|
||||
if (rgbCameraHelper == null) {
|
||||
Timber.tag(TAG).d("resumeCamera isRecognition ---1")
|
||||
setupArcCamera()
|
||||
binding.root.postDelayed({
|
||||
if (rgbCameraHelper != null) {
|
||||
rgbCameraHelper!!.start()
|
||||
}
|
||||
}, 2000)
|
||||
} else if (rgbCameraHelper!!.isStopped) {
|
||||
Timber.tag(TAG).d("resumeCamera isRecognition ---2")
|
||||
rgbCameraHelper!!.start()
|
||||
}
|
||||
}
|
||||
@@ -795,6 +939,23 @@ class MainScreenPresentation(
|
||||
isRecognition = false
|
||||
|
||||
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
|
||||
//triggerReidentify()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置人脸跟踪状态,让已在画面中的人脸重新触发识别
|
||||
* 适用场景:切换按钮后需要重新识别当前画面中的人脸
|
||||
*/
|
||||
fun triggerReidentify() {
|
||||
// 清除UI上的人脸框
|
||||
binding.dualCameraFaceRectView.clearFaceInfo()
|
||||
// 清除本地跟踪数据
|
||||
clearLeftFaceData()
|
||||
// 重置上一次的 trackId,避免 drawPreviewInfo 中的去重逻辑拦截
|
||||
lastFaceTrackId = -1
|
||||
// 插入空帧,让引擎重置内部跟踪状态
|
||||
// isRecognition 保持 true,相机继续送帧,下一帧真实人脸会产生新 trackId
|
||||
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
|
||||
}
|
||||
|
||||
private var recognizeTime = 0L
|
||||
@@ -842,23 +1003,30 @@ class MainScreenPresentation(
|
||||
|
||||
recognizeViewModel.recognizeUserId.observe(
|
||||
activity,
|
||||
Observer { compareResult: CompareResult ->
|
||||
Observer { result: CompareResult? ->
|
||||
if (result == null) return@Observer
|
||||
if (currentStep != 2 || System.currentTimeMillis() - recognizeTime <= 2000) {
|
||||
//忽略非识别中及两秒内的再次识别
|
||||
//忽略非识别中及5秒内的再次识别
|
||||
return@Observer
|
||||
}
|
||||
// if (isGoRecognize.not()) {
|
||||
// return@Observer
|
||||
// }
|
||||
recognizeTime = System.currentTimeMillis()
|
||||
activity.runOnUiThread {
|
||||
activity.showWaitingDialog("加载中,请稍后……")
|
||||
}
|
||||
Timber.tag(TAG)
|
||||
.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}, step = $currentStep")
|
||||
.i("recognizeUserId observe compareResult = ${result.trackId}, userId = ${result.faceEntity.userName}, step = $currentStep")
|
||||
recognitionTime = System.currentTimeMillis()
|
||||
//识别成功时更新lastWeight,解决使用过程中给秤上放东西,导致重量错误问题
|
||||
lastWeight = activity.currentWeight
|
||||
recognitionWeight = lastWeight
|
||||
lastFaceTrackId = compareResult.trackId
|
||||
val faceEntity = compareResult.faceEntity
|
||||
lastFaceTrackId = result.trackId
|
||||
val faceEntity = result.faceEntity
|
||||
val userId = faceEntity.userName
|
||||
currentUserId = userId
|
||||
isMember = faceEntity.userType == "1"
|
||||
isAgainLoadData = false
|
||||
if (userId == null) return@Observer
|
||||
//ToastUtils.showToast("人脸识别成功,userId = $userId")
|
||||
@@ -872,7 +1040,12 @@ class MainScreenPresentation(
|
||||
return@runOnUiThread
|
||||
}
|
||||
}
|
||||
step3ShowRecognizeResult(userId)
|
||||
// pauseCamera()
|
||||
//先查询会员折扣
|
||||
userViewModel.getMemberDiscount(userId) { discount ->
|
||||
memberDiscount = discount ?: 1.0
|
||||
step3ShowRecognizeResult(userId)
|
||||
}
|
||||
})
|
||||
|
||||
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
|
||||
@@ -978,6 +1151,7 @@ class MainScreenPresentation(
|
||||
}
|
||||
if (facePreviewInfoList.isNullOrEmpty()) {
|
||||
currentUserId = null
|
||||
isMember = false
|
||||
}
|
||||
//recognizeViewModel.clearLeftFace(facePreviewInfoList)
|
||||
clearLeftFaceData()
|
||||
@@ -1135,52 +1309,101 @@ class MainScreenPresentation(
|
||||
|
||||
val listIsEmpty = facePreviewInfoList.isEmpty()
|
||||
val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0].trackId else null
|
||||
Timber.tag(TAG)
|
||||
.d("listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId")
|
||||
// Timber.tag(TAG)
|
||||
// .d("drawPreviewInfo,listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId")
|
||||
if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) {
|
||||
if (lastFaceTrackId != -1 && currentUserId != null) {
|
||||
//0-即放即取,1-余量计量
|
||||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||
Timber.tag(TAG).i("$lastFaceTrackId 用户离开")
|
||||
Timber.tag(TAG).i("drawPreviewInfo,$lastFaceTrackId 用户离开")
|
||||
lastFaceTrackId = -1
|
||||
Timber.tag(TAG)
|
||||
.d("postUserData userNutritionData是否null: ${userNutrition == null}, currentFood是否null: ${currentFood == null},currentUserId是否null:${currentUserId == null}")
|
||||
.d("drawPreviewInfo,postUserData userNutritionData是否null: ${userNutrition == null}, currentFood是否null: ${currentFood == null},currentUserId是否null:${currentUserId == null}")
|
||||
if (userNutrition == null || currentFood == null || currentUserId == null) {
|
||||
return
|
||||
}
|
||||
//重置人脸
|
||||
recognizeViewModel.resetFaceState()
|
||||
Timber.tag(TAG).d("drawPreviewInfo,开始提交订单")
|
||||
createOrder(notEnoughOneBlock = {
|
||||
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock")
|
||||
if (activity.settlementMode == 0) {
|
||||
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock,联合模式")
|
||||
setTopInfoVisible()
|
||||
}
|
||||
// if (mealPickupMode == 0) {
|
||||
Timber.tag(TAG).d("drawPreviewInfo,notEnoughOneBlock,step2FaceRecognizing")
|
||||
currentStep = 2
|
||||
step2FaceRecognizing(currentFood!!)
|
||||
// }
|
||||
}) {
|
||||
}, successBlock = {
|
||||
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成")
|
||||
// TODO: 测试人脸识别问题--------------------
|
||||
//recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
|
||||
if (MyApp.DEBUG) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
ToastUtils.showToast("订单已生成")
|
||||
}
|
||||
currentUserId = null
|
||||
isMember = false
|
||||
//ToastUtils.showToast("isKeepFaceState=$isKeepFaceState,mealPickupMode=$mealPickupMode")
|
||||
//|| isKeepFaceState
|
||||
// if (activity.settlementMode == 0) {
|
||||
// activity.isAnalyzing = false
|
||||
// step1FoodRecognizing()
|
||||
// } else {
|
||||
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,mealPickupMode=$mealPickupMode")
|
||||
if (mealPickupMode == 1) {
|
||||
step2FaceRecognizing(currentFood!!)
|
||||
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step2FaceRecognizing")
|
||||
if (activity.currentWeight > MainActivity.WEIGHT_RESET_RECOGNIZE) {
|
||||
//余量计量拿走秤上最后一点餐品时,不用再进行人脸识别了
|
||||
step2FaceRecognizing(currentFood!!)
|
||||
}
|
||||
isKeepFaceState = false
|
||||
if (activity.settlementMode == 0) {
|
||||
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step2FaceRecognizing,联合支付")
|
||||
setTopInfoVisible()
|
||||
}
|
||||
} else {
|
||||
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,称重为:${activity.currentWeight}")
|
||||
//即放即取未拿走餐品,不用重置为默认识别中状态
|
||||
if (abs(activity.currentWeight) > 10) {
|
||||
//提交订单后秤重量绝对值大于10克,都认为餐品还没拿走
|
||||
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,未拿走餐品")
|
||||
return@createOrder
|
||||
}
|
||||
Timber.tag(TAG).d("drawPreviewInfo,successBlock,订单已生成,step1FoodRecognizing")
|
||||
Timber.tag(TAG).e("切回step1,------2000000000002")
|
||||
activity.isAnalyzing = false
|
||||
step1FoodRecognizing()
|
||||
}
|
||||
}
|
||||
// }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createOrder(notEnoughOneBlock: () -> Unit = {}, block: () -> Unit) {
|
||||
/**
|
||||
* 人脸离开后调用创建订单接口
|
||||
*/
|
||||
fun createOrder(notEnoughOneBlock: () -> Unit = {}, successBlock: () -> Unit) {
|
||||
//0-即放即取,1-余量计量
|
||||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||
val eatWeight = if (mealPickupMode == 0) {
|
||||
// 餐具重量(克),从就餐重量中扣减
|
||||
val dishWeight = activity.getDishWeight()
|
||||
//if (settlementMode == 0) {
|
||||
// //使用联合支付方式
|
||||
// ToastUtils.showToast("使用联合支付方式")
|
||||
// return
|
||||
//}
|
||||
val rawEatWeight = if (mealPickupMode == 0) {
|
||||
//即放即取
|
||||
recognitionWeight
|
||||
} else {
|
||||
recognitionWeight - lastWeight
|
||||
}
|
||||
// 扣减餐具重量(不足则归零),foodWeight 保持不变
|
||||
val eatWeight = (rawEatWeight - dishWeight).coerceAtLeast(0)
|
||||
activity.log("createOrder,rawEatWeight=$rawEatWeight,dishWeight=$dishWeight,eatWeight=$eatWeight")
|
||||
//val userNutritionParam = UserNutritionParam(
|
||||
// userId = userNutritionData?.userId!!,
|
||||
// foodId = currentFood?.foodId!!,
|
||||
@@ -1190,16 +1413,21 @@ class MainScreenPresentation(
|
||||
// foodWeight = lastWeight
|
||||
//)
|
||||
//userViewModel.postUserNutritionData(listOf(userNutritionParam))
|
||||
val realWeight = eatWeight.toDouble()
|
||||
val specWeight = currentFood!!.specWeight
|
||||
val eatNum = activity.getEatNum(realWeight, specWeight)
|
||||
activity.log("createOrder,realWeight=$realWeight,specWeight=$specWeight")
|
||||
activity.createOrder(
|
||||
foodInfo = currentFood!!,
|
||||
foodWeight = lastWeight.roundToInt(),
|
||||
eatWeight = eatWeight.roundToInt(),
|
||||
eatNum = activity.getEatNum(eatWeight, currentFood!!.specWeight),
|
||||
foodWeight = lastWeight,
|
||||
eatWeight = eatWeight,
|
||||
eatNum = eatNum,
|
||||
userId = currentUserId,
|
||||
isMember = isMember,
|
||||
notEnoughOneBlock = notEnoughOneBlock,
|
||||
block = {
|
||||
successBlock = {
|
||||
activity.runOnUiThread {
|
||||
block()
|
||||
successBlock()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1244,4 +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)
|
||||
|
||||
}
|
||||
@@ -1,49 +1,49 @@
|
||||
package com.sw.dualscreen.presentation.pay
|
||||
|
||||
import android.R
|
||||
import android.app.Presentation
|
||||
import android.os.Bundle
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.view.Display
|
||||
import androidx.core.text.buildSpannedString
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.databinding.PresentationCashPayBinding
|
||||
import com.sw.dualscreen.ext.load
|
||||
|
||||
class CashPayPresentation(
|
||||
val activity: PayActivity,
|
||||
display: Display,
|
||||
private val onDismissListener: () -> Unit = {}
|
||||
) : Presentation(activity, display) {
|
||||
|
||||
private lateinit var binding: PresentationCashPayBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = PresentationCashPayBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
window?.setBackgroundDrawableResource(R.color.transparent)
|
||||
initView()
|
||||
}
|
||||
|
||||
var foodName: String? = null
|
||||
var payAmount: String? = null
|
||||
private fun initView() {
|
||||
binding.tvFoodName.text = foodName
|
||||
binding.tvRealAmount.text = getAmountText(payAmount ?: "")
|
||||
}
|
||||
|
||||
private fun getAmountText(amount: String): SpannedString {
|
||||
return buildSpannedString {
|
||||
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayRemoved() {
|
||||
super.onDisplayRemoved()
|
||||
onDismissListener()
|
||||
}
|
||||
}
|
||||
//package com.sw.dualscreen.presentation.pay
|
||||
//
|
||||
//import android.R
|
||||
//import android.app.Presentation
|
||||
//import android.os.Bundle
|
||||
//import android.text.Spanned
|
||||
//import android.text.SpannedString
|
||||
//import android.text.style.AbsoluteSizeSpan
|
||||
//import android.view.Display
|
||||
//import androidx.core.text.buildSpannedString
|
||||
//import com.sw.dualscreen.activity.PayActivity
|
||||
//import com.sw.dualscreen.databinding.PresentationCashPayBinding
|
||||
//import com.sw.dualscreen.ext.load
|
||||
//
|
||||
//class CashPayPresentation(
|
||||
// val activity: PayActivity,
|
||||
// display: Display,
|
||||
// private val onDismissListener: () -> Unit = {}
|
||||
//) : Presentation(activity, display) {
|
||||
//
|
||||
// private lateinit var binding: PresentationCashPayBinding
|
||||
//
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// binding = PresentationCashPayBinding.inflate(layoutInflater)
|
||||
// setContentView(binding.root)
|
||||
// window?.setBackgroundDrawableResource(R.color.transparent)
|
||||
// initView()
|
||||
// }
|
||||
//
|
||||
// var foodName: String? = null
|
||||
// var payAmount: String? = null
|
||||
// private fun initView() {
|
||||
// binding.tvFoodName.text = foodName
|
||||
// binding.tvRealAmount.text = getAmountText(payAmount ?: "")
|
||||
// }
|
||||
//
|
||||
// private fun getAmountText(amount: String): SpannedString {
|
||||
// return buildSpannedString {
|
||||
// append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onDisplayRemoved() {
|
||||
// super.onDisplayRemoved()
|
||||
// onDismissListener()
|
||||
// }
|
||||
//}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,826 @@
|
||||
package com.sw.dualscreen.presentation.pay
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Presentation
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Point
|
||||
import android.hardware.Camera
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.arcsoft.face.ErrorInfo
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.databinding.PresentationPayBinding
|
||||
import com.sw.dualscreen.ext.dp
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.ext.gone
|
||||
import com.sw.dualscreen.ext.invisible
|
||||
import com.sw.dualscreen.ext.load
|
||||
import com.sw.dualscreen.ext.visible
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.TextBean
|
||||
import com.sw.dualscreen.utils.SpannedUtils
|
||||
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import com.sw.plate.utils.arcface.ConfigUtil
|
||||
import com.sw.plate.utils.arcface.ErrorCodeUtil
|
||||
import com.sw.plate.utils.arcface.FaceRectTransformer
|
||||
import com.sw.plate.utils.arcface.FaceRectView
|
||||
import com.sw.plate.utils.arcface.FaceRectView.DrawInfo
|
||||
import com.sw.plate.utils.arcface.PreviewConfig
|
||||
import com.sw.plate.utils.arcface.camera.CameraListener
|
||||
import com.sw.plate.utils.arcface.camera.DualCameraHelper
|
||||
import com.sw.plate.utils.arcface.face.constants.LivenessType
|
||||
import com.sw.plate.utils.arcface.face.model.CompareResult
|
||||
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
|
||||
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class PayPresentation(
|
||||
val activity: PayActivity,
|
||||
display: Display,
|
||||
val userViewModel: NetViewModelV2,
|
||||
val recognizeViewModel: RecognizeViewModel,
|
||||
private val onDismissListener: () -> Unit = {}
|
||||
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PayPresentation"
|
||||
const val CASH_PAY = 100
|
||||
const val FACE_PAY = 200
|
||||
const val QR_CODE_PAY = 300
|
||||
}
|
||||
|
||||
private lateinit var binding: PresentationPayBinding
|
||||
|
||||
// 虹软人脸配置 ⬇
|
||||
private var isRecognition = false
|
||||
private var rgbCameraHelper: DualCameraHelper? = null
|
||||
private var rgbFaceRectTransformer: FaceRectTransformer? = null
|
||||
private val livenessType = LivenessType.IR
|
||||
private var openRectInfoDraw = false
|
||||
|
||||
private var irCameraHelper: DualCameraHelper? = null
|
||||
private var irFaceRectTransformer: FaceRectTransformer? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = PresentationPayBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
window?.setBackgroundDrawableResource(android.R.color.white)
|
||||
|
||||
setupArcCamera()
|
||||
registerDataChange()
|
||||
initView()
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
pauseCamera()
|
||||
}
|
||||
|
||||
var pageType: Int = 0
|
||||
var type: Int = 0
|
||||
|
||||
// fun updatePage(pageType:Int, type:Int = -1) {
|
||||
// this.pageType == pageType
|
||||
// this.type = type
|
||||
// if (pageType == PayPresentation.CASH_PAY) {
|
||||
//
|
||||
// return
|
||||
// }
|
||||
// if (pageType == PayPresentation.FACE_PAY) {
|
||||
//
|
||||
// return
|
||||
// }
|
||||
// if (pageType == PayPresentation.QR_CODE_PAY) {
|
||||
//
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
var foodName: String? = null
|
||||
var payAmount: String? = null
|
||||
fun initView() {
|
||||
when (pageType) {
|
||||
FACE_PAY -> {
|
||||
//人脸识别
|
||||
binding.flCashPay.gone()
|
||||
binding.flQrCodePay.gone()
|
||||
binding.flFacePay.visible()
|
||||
|
||||
binding.tvFoodName.text = foodName
|
||||
binding.tvRealAmount.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 32),
|
||||
TextBean(text = payAmount ?: "", textSize = 48),
|
||||
)
|
||||
)
|
||||
|
||||
//pauseCamera()
|
||||
resumeCamera()
|
||||
// binding.root.postDelayed({
|
||||
// resumeCamera()
|
||||
// }, 1000)
|
||||
}
|
||||
|
||||
QR_CODE_PAY -> {
|
||||
//二维码
|
||||
binding.flCashPay.gone()
|
||||
binding.flQrCodePay.visible()
|
||||
binding.flFacePay.gone()
|
||||
initQrCodeView()
|
||||
}
|
||||
|
||||
CASH_PAY -> {
|
||||
binding.flCashPay.visible()
|
||||
binding.flQrCodePay.gone()
|
||||
binding.flFacePay.gone()
|
||||
binding.tvFoodName2.text = foodName
|
||||
binding.tvRealAmount2.text = getAmountText(payAmount ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAmountText(amount: String): SpannedString {
|
||||
return buildSpannedString {
|
||||
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGlobalLayout() {
|
||||
Timber.tag(TAG).d("onGlobalLayout")
|
||||
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this)
|
||||
//parentTextureView.getViewTreeObserver().removeOnGlobalLayoutListener(this)
|
||||
openCamera()
|
||||
}
|
||||
|
||||
private fun openCamera() {
|
||||
Timber.tag(TAG).d("openCamera")
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ToastUtils.showToast("无摄像头权限")
|
||||
return
|
||||
}
|
||||
try {
|
||||
val cameraCount = Camera.getNumberOfCameras()
|
||||
if (cameraCount < 3) {
|
||||
ToastUtils.showToast("摄像头数量异常")
|
||||
return
|
||||
}
|
||||
recognizeViewModel.init(
|
||||
PreviewConfig(
|
||||
2, 1, 90, 90
|
||||
)
|
||||
)
|
||||
initRgbCamera()
|
||||
if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) {
|
||||
initIrCamera()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).e(e, "打开摄像头失败")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用虹软人脸失败
|
||||
*/
|
||||
private fun setupArcCamera() {
|
||||
initArcViewModel()
|
||||
initArcView()
|
||||
openRectInfoDraw = true
|
||||
}
|
||||
|
||||
private fun registerDataChange() {
|
||||
activity.lifecycleScope.launch {
|
||||
userViewModel.loadFaceResult.collect { needUpdate ->
|
||||
if (needUpdate) {
|
||||
recognizeViewModel.refreshFaceList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
rgbCameraHelper?.release()
|
||||
rgbCameraHelper = null
|
||||
irCameraHelper?.release()
|
||||
irCameraHelper = null
|
||||
recognizeViewModel.destroy()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
fun resumeCamera() {
|
||||
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
|
||||
isRecognition = true
|
||||
if (rgbCameraHelper?.isStopped == true) {
|
||||
rgbCameraHelper?.start()
|
||||
}
|
||||
}
|
||||
|
||||
fun pauseCamera() {
|
||||
Timber.tag(TAG).d("pauseCamera isRecognition = $isRecognition")
|
||||
isRecognition = false
|
||||
|
||||
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
|
||||
}
|
||||
|
||||
private fun initArcViewModel() {
|
||||
recognizeViewModel.setLiveType(livenessType)
|
||||
recognizeViewModel.ftInitCode.observe(activity, Observer { ftInitCode: Int? ->
|
||||
if (ftInitCode != ErrorInfo.MOK) {
|
||||
val error: String? = context.getString(
|
||||
R.string.specific_engine_init_failed, "ftEngine",
|
||||
ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!)
|
||||
)
|
||||
Timber.tag(TAG).e("ftInitCode observe = $error")
|
||||
ToastUtils.showToast(error)
|
||||
}
|
||||
})
|
||||
recognizeViewModel.frInitCode.observe(activity, Observer { frInitCode: Int? ->
|
||||
if (frInitCode != ErrorInfo.MOK) {
|
||||
val error: String? = context.getString(
|
||||
R.string.specific_engine_init_failed, "frEngine",
|
||||
frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!)
|
||||
)
|
||||
Timber.tag(TAG).e("frInitCode observe = $error")
|
||||
ToastUtils.showToast(error)
|
||||
}
|
||||
})
|
||||
recognizeViewModel.flInitCode.observe(activity, Observer { flInitCode: Int? ->
|
||||
if (flInitCode != ErrorInfo.MOK) {
|
||||
val error: String? = context.getString(
|
||||
R.string.specific_engine_init_failed, "flEngine",
|
||||
flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!)
|
||||
)
|
||||
Timber.tag(TAG).e("flInitCode observe = $error")
|
||||
ToastUtils.showToast(error)
|
||||
}
|
||||
})
|
||||
|
||||
recognizeViewModel.recognizeConfiguration
|
||||
.observe(activity, Observer { recognizeConfiguration: RecognizeConfiguration? ->
|
||||
Timber.tag(TAG)
|
||||
.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}")
|
||||
})
|
||||
recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? ->
|
||||
Timber.tag(TAG).i("recognizeNotice observe notice = $notice")
|
||||
})
|
||||
|
||||
recognizeViewModel.recognizeUserId.observe(
|
||||
activity,
|
||||
Observer { compareResult: CompareResult ->
|
||||
Timber.tag(TAG)
|
||||
.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}")
|
||||
// recognitionTime = System.currentTimeMillis()
|
||||
// recognitionWeight = lastWeight
|
||||
if (isExistNotPayOrder.get()) return@Observer
|
||||
lastFaceTrackId = compareResult.trackId
|
||||
val faceEntity = compareResult.faceEntity
|
||||
val userId = faceEntity.userName
|
||||
val isMember = faceEntity.userType == "1"
|
||||
activity.isMember = isMember
|
||||
if (userId.isNullOrBlank()) return@Observer
|
||||
// TODO: 测试支付用户id
|
||||
// userId = "1987710988425662466"
|
||||
isExistNotPayOrder.set(true)
|
||||
faceRecSuccess(userId, isMember)
|
||||
})
|
||||
|
||||
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
|
||||
Timber.tag(TAG).i("drawRectInfoText observe info = $info")
|
||||
})
|
||||
}
|
||||
|
||||
private var isExistNotPayOrder = AtomicBoolean(false)
|
||||
|
||||
private fun initArcView() {
|
||||
//在布局结束后才做初始化操作
|
||||
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
|
||||
//parentTextureView.getViewTreeObserver().addOnGlobalLayoutListener(this)
|
||||
recognizeViewModel.getCompareResultList().getValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整View的宽高,使预览显示正常且采集框固定为
|
||||
*
|
||||
* @param rgbPreview RGB预览View
|
||||
* @param previewView 显示预览数据的view
|
||||
* @param faceRectView 画框的view
|
||||
* @param previewSize 预览大小
|
||||
* @param displayOrientation 相机旋转角度
|
||||
* @param scale 缩放比例
|
||||
* @return 调整后的LayoutParams
|
||||
*/
|
||||
private fun adjustPreviewViewSize(
|
||||
rgbPreview: View,
|
||||
previewView: View,
|
||||
faceRectView: FaceRectView,
|
||||
previewSize: Camera.Size,
|
||||
displayOrientation: Int,
|
||||
scale: Float
|
||||
): ViewGroup.LayoutParams {
|
||||
val w = (600.dp * 1.5).toInt()
|
||||
val h = 1068.dp
|
||||
val layoutParams = FrameLayout.LayoutParams(w, h)
|
||||
|
||||
previewView.setLayoutParams(layoutParams)
|
||||
faceRectView.setLayoutParams(layoutParams)
|
||||
return layoutParams
|
||||
}
|
||||
|
||||
private fun initRgbCamera() {
|
||||
val cameraListener: CameraListener = object : CameraListener {
|
||||
override fun onCameraOpened(
|
||||
camera: Camera,
|
||||
cameraId: Int,
|
||||
displayOrientation: Int,
|
||||
isMirror: Boolean
|
||||
) {
|
||||
Timber.tag(TAG)
|
||||
.d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
|
||||
activity.runOnUiThread({
|
||||
val previewSizeRgb = camera.getParameters().getPreviewSize()
|
||||
val layoutParams = adjustPreviewViewSize(
|
||||
binding.dualCameraTexturePreviewRgb,
|
||||
binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView,
|
||||
previewSizeRgb, displayOrientation, 0.6F
|
||||
)
|
||||
|
||||
Timber.tag(TAG)
|
||||
.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
|
||||
Timber.tag(TAG)
|
||||
.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
|
||||
Timber.tag(TAG).d(
|
||||
"initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${
|
||||
ConfigUtil.isDrawRgbRectHorizontalMirror(
|
||||
context
|
||||
)
|
||||
}, isDrawRgbRectVerticalMirror = ${
|
||||
ConfigUtil.isDrawRgbRectVerticalMirror(
|
||||
context
|
||||
)
|
||||
}"
|
||||
)
|
||||
// 调整识别窗口位置
|
||||
rgbFaceRectTransformer = FaceRectTransformer(
|
||||
previewSizeRgb.width,
|
||||
previewSizeRgb.height,
|
||||
// layoutParams.width,
|
||||
// layoutParams.height,
|
||||
layoutParams.width,
|
||||
layoutParams.height,
|
||||
90,
|
||||
cameraId,
|
||||
isMirror,
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
recognizeViewModel.onRgbCameraOpened(camera)
|
||||
recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer)
|
||||
})
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
|
||||
if (!isRecognition) {
|
||||
return
|
||||
}
|
||||
binding.dualCameraFaceRectView.clearFaceInfo()
|
||||
val facePreviewInfoList: MutableList<FacePreviewInfo?>? =
|
||||
recognizeViewModel.onPreviewFrame(nv21, true)
|
||||
if (facePreviewInfoList != null && rgbFaceRectTransformer != null) {
|
||||
drawPreviewInfo(facePreviewInfoList)
|
||||
}
|
||||
recognizeViewModel.clearLeftFace(facePreviewInfoList)
|
||||
}
|
||||
|
||||
override fun onCameraClosed() {
|
||||
Timber.tag(TAG).i("initRgbCamera onCameraClosed: ")
|
||||
}
|
||||
|
||||
override fun onCameraError(e: java.lang.Exception) {
|
||||
Timber.tag(TAG).i("initRgbCamera onCameraError: %s", e.message)
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
|
||||
Timber.tag(TAG)
|
||||
.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}")
|
||||
if (rgbFaceRectTransformer != null) {
|
||||
rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
|
||||
}
|
||||
Timber.tag(TAG)
|
||||
.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation")
|
||||
}
|
||||
}
|
||||
val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth
|
||||
val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight
|
||||
Timber.tag(TAG)
|
||||
.i("initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight")
|
||||
|
||||
val previewConfig: PreviewConfig = recognizeViewModel.previewConfig
|
||||
rgbCameraHelper = DualCameraHelper.Builder()
|
||||
.previewViewSize(Point(measuredWidth, measuredHeight))
|
||||
.rotation(activity.windowManager.defaultDisplay.rotation)
|
||||
.additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度
|
||||
.previewSize(recognizeViewModel.loadPreviewSize())
|
||||
.specificCameraId(previewConfig.rgbCameraId)
|
||||
.isMirror(true)
|
||||
.previewOn(binding.dualCameraTexturePreviewRgb)
|
||||
.cameraListener(cameraListener)
|
||||
.build()
|
||||
rgbCameraHelper!!.setSurfaceFrameCallback { frame ->
|
||||
activity.facePayFragment?.loadBitmap(frame)
|
||||
}
|
||||
rgbCameraHelper!!.init()
|
||||
// rgbCameraHelper!!.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用
|
||||
*/
|
||||
private fun initIrCamera() {
|
||||
Timber.tag(TAG).d("initIrCamera: livenessType = $livenessType")
|
||||
if (livenessType === LivenessType.RGB) {
|
||||
return
|
||||
}
|
||||
val irCameraListener: CameraListener = object : CameraListener {
|
||||
override fun onCameraOpened(
|
||||
camera: Camera,
|
||||
cameraId: Int,
|
||||
displayOrientation: Int,
|
||||
isMirror: Boolean
|
||||
) {
|
||||
Timber.tag(TAG)
|
||||
.d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
|
||||
val previewSizeIr = camera.getParameters().getPreviewSize()
|
||||
val layoutParams = adjustPreviewViewSize(
|
||||
binding.dualCameraTexturePreviewRgb,
|
||||
binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr,
|
||||
previewSizeIr, displayOrientation, 0.25f
|
||||
)
|
||||
|
||||
irFaceRectTransformer = FaceRectTransformer(
|
||||
previewSizeIr.width, previewSizeIr.height,
|
||||
// layoutParams.width, layoutParams.height,
|
||||
layoutParams.width, layoutParams.height,
|
||||
displayOrientation, cameraId, isMirror,
|
||||
ConfigUtil.isDrawIrRectHorizontalMirror(context),
|
||||
ConfigUtil.isDrawIrRectVerticalMirror(context)
|
||||
)
|
||||
|
||||
recognizeViewModel.onIrCameraOpened(camera)
|
||||
recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer)
|
||||
}
|
||||
|
||||
|
||||
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
|
||||
recognizeViewModel.refreshIrPreviewData(nv21)
|
||||
}
|
||||
|
||||
override fun onCameraClosed() {
|
||||
Timber.tag(TAG).i("initIrCamera onCameraClosed: ")
|
||||
}
|
||||
|
||||
override fun onCameraError(e: java.lang.Exception) {
|
||||
Timber.tag(TAG).i("initIrCamera onCameraError: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onCameraConfigurationChanged(
|
||||
cameraID: Int,
|
||||
displayOrientation: Int
|
||||
) {
|
||||
if (irFaceRectTransformer != null) {
|
||||
irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
|
||||
}
|
||||
Timber.tag(TAG)
|
||||
.i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation")
|
||||
}
|
||||
}
|
||||
|
||||
val previewConfig = recognizeViewModel.previewConfig
|
||||
irCameraHelper = DualCameraHelper.Builder()
|
||||
.previewViewSize(
|
||||
Point(
|
||||
binding.dualCameraTexturePreviewIr.measuredWidth,
|
||||
binding.dualCameraTexturePreviewIr.measuredHeight
|
||||
)
|
||||
)
|
||||
.rotation(activity.windowManager.defaultDisplay.rotation)
|
||||
.specificCameraId(previewConfig.irCameraId)
|
||||
.previewOn(binding.dualCameraTexturePreviewIr)
|
||||
.cameraListener(irCameraListener)
|
||||
.isMirror(true)
|
||||
.previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小
|
||||
.additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度
|
||||
.build()
|
||||
irCameraHelper!!.init()
|
||||
try {
|
||||
irCameraHelper!!.start()
|
||||
} catch (e: RuntimeException) {
|
||||
ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制RGB、IR画面的实时人脸信息
|
||||
*
|
||||
* @param facePreviewInfoList RGB画面的实时人脸信息
|
||||
*/
|
||||
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) {
|
||||
// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
|
||||
if (rgbFaceRectTransformer != null) {
|
||||
val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo(
|
||||
facePreviewInfoList,
|
||||
LivenessType.RGB,
|
||||
openRectInfoDraw
|
||||
)
|
||||
// 识别成功
|
||||
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList)
|
||||
}
|
||||
|
||||
if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) {
|
||||
if (lastFaceTrackId != -1) {
|
||||
// mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||
// Timber.tag(TAG).i("$lastFaceTrackId 用户离开")
|
||||
// lastFaceTrackId = -1
|
||||
// postUserData()
|
||||
// if (mealPickupMode == 0) {
|
||||
// step1FoodRecognizing()
|
||||
// } else {
|
||||
// step2FaceRecognizing(currentFood!!)
|
||||
// }
|
||||
|
||||
resumeCamera()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
|
||||
|
||||
override fun onDisplayRemoved() {
|
||||
super.onDisplayRemoved()
|
||||
onDismissListener()
|
||||
}
|
||||
|
||||
|
||||
private fun faceRecSuccess(userId: String, isMember: Boolean) {
|
||||
activity.runOnUiThread {
|
||||
activity.showWaitingDialog("加载中,请稍后……")
|
||||
}
|
||||
activity.getMemberInfoById(userId) { memberInfo ->
|
||||
if (memberInfo == null) {
|
||||
activity.hideWaitingDialog()
|
||||
//ToastUtils.showToast("查询会员信息失败,请稍后重试")
|
||||
return@getMemberInfoById
|
||||
}
|
||||
memberInfo.member = isMember
|
||||
activity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
|
||||
if (bindResult.not()) {
|
||||
activity.hideWaitingDialog()
|
||||
//ToastUtils.showToast("订单绑定失败")
|
||||
return@bindOrder
|
||||
}
|
||||
|
||||
binding.root.postDelayed({
|
||||
activity.hideWaitingDialog()
|
||||
// TODO: 测试 memberInfo.member是否返回正常数据---------------
|
||||
activity.showPayInfo(type = 1, isVip = memberInfo.member, memberInfo = memberInfo)
|
||||
activity.hidePayTab()
|
||||
// binding.root.postDelayed({
|
||||
// dismiss()
|
||||
// }, 500)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private fun bindOrder(userId: String, block: () -> Unit) {
|
||||
// val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
||||
// val mode = if (pickupMode == 1) 1 else 2
|
||||
// userViewModel.bindOrder(userId, activity.foodOrderId, mode = mode) { bindResult ->
|
||||
// activity.runOnUiThread {
|
||||
// if (bindResult.not()) {
|
||||
// activity.hideWaitingDialog()
|
||||
// //ToastUtils.showToast("订单绑定失败")
|
||||
// return@runOnUiThread
|
||||
// }
|
||||
// block()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// private fun getMemberInfo(userId: String, block:(MemberInfo)-> Unit) {
|
||||
// userViewModel.getMemberInfoById(memberId = userId) { memberInfo ->
|
||||
// activity.runOnUiThread {
|
||||
// if (memberInfo == null) {
|
||||
// activity.hideWaitingDialog()
|
||||
// //ToastUtils.showToast("查询会员信息失败,请稍后重试")
|
||||
// return@runOnUiThread
|
||||
// }
|
||||
// block(memberInfo)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
var payQrCodePic: Any? = null
|
||||
|
||||
//总价格
|
||||
var totalPrice = 0.0
|
||||
|
||||
//余额
|
||||
private var balance = 0.0
|
||||
|
||||
//实际支付金额
|
||||
private var realPayPrice = 0.0
|
||||
|
||||
//扣除余额
|
||||
private var expensesBalance = 0.0
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun initQrCodeView() {
|
||||
|
||||
when (type) {
|
||||
0 -> {
|
||||
binding.layoutVip.gone()
|
||||
binding.layoutPaySuccess.gone()
|
||||
binding.layoutScanQrCode.visible()
|
||||
|
||||
binding.tvPayTip.text = "请扫码支付或出示付款码"
|
||||
binding.tvFoodName3.text = foodName
|
||||
binding.tvRealAmount3.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 32),
|
||||
TextBean(text = totalPrice.format2String(2), textSize = 48),
|
||||
)
|
||||
)
|
||||
binding.ivPayQrCode.load(payQrCodePic)
|
||||
}
|
||||
|
||||
1 -> {
|
||||
val memberInfo = activity.memberInfo
|
||||
binding.layoutVip.run {
|
||||
if (memberInfo != null && memberInfo.member) visible() else gone()
|
||||
}
|
||||
binding.layoutPaySuccess.gone()
|
||||
binding.layoutScanQrCode.visible()
|
||||
|
||||
balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
|
||||
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
expensesBalance = if (balance >= totalPrice) totalPrice else balance
|
||||
|
||||
memberInfo?.let { loadUserInfo(it) }
|
||||
binding.tvUserBalance.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 20),
|
||||
TextBean(text = balance.format2String(2), textSize = 32),
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvFoodName3.text = foodName
|
||||
binding.tvRealAmount3.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 32),
|
||||
TextBean(text = totalPrice.format2String(2), textSize = 48),
|
||||
)
|
||||
)
|
||||
binding.ivPayQrCode.run {
|
||||
// load(payQrCodePic)
|
||||
if (balance >= totalPrice) invisible() else visible()
|
||||
}
|
||||
|
||||
if (memberInfo !=null && memberInfo!!.member) {
|
||||
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
|
||||
} else {
|
||||
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountListNotMember())
|
||||
}
|
||||
}
|
||||
|
||||
2 -> {
|
||||
val memberInfo = activity.memberInfo
|
||||
binding.layoutVip.run {
|
||||
if (memberInfo != null && memberInfo.member) visible() else gone()
|
||||
}
|
||||
binding.layoutPaySuccess.visible()
|
||||
binding.layoutScanQrCode.gone()
|
||||
|
||||
balance = if (memberInfo != null && memberInfo.member) (memberInfo.topUpBalance
|
||||
?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
|
||||
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
expensesBalance = if (balance >= totalPrice) totalPrice else balance
|
||||
|
||||
memberInfo?.let { loadUserInfo(it) }
|
||||
val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
|
||||
binding.tvUserBalance.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 24),
|
||||
TextBean(text = remainBalance.format2String(2), textSize = 36),
|
||||
)
|
||||
)
|
||||
|
||||
val showBalance = expensesBalance.format2String(2)
|
||||
val showTotal = totalPrice.format2String(2)
|
||||
binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal 元"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadQrCodeImage(qrCodeUrl: String?) {
|
||||
binding.ivPayQrCode.load(qrCodeUrl)
|
||||
}
|
||||
|
||||
private fun loadUserInfo(item: MemberInfo) {
|
||||
binding.ivHeadPic.load(
|
||||
if (item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
|
||||
else item.faceUrl
|
||||
)
|
||||
binding.tvUserName.text = item.name
|
||||
|
||||
val phone = item.phone ?: ""
|
||||
binding.tvUserPhone.text =
|
||||
if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
|
||||
else phone
|
||||
}
|
||||
|
||||
private fun getAmountListNotMember(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
list.add(TextBean(text = "应付金额 ", textSize = 30, textColor = "#FF889AC2"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = "-${totalPrice.format2String(2)}",
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF889AC2"))
|
||||
return list
|
||||
}
|
||||
|
||||
private fun getAmountList(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = "-${expensesBalance.format2String(2)}",
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
|
||||
if (balance >= totalPrice) {
|
||||
//余额大于等于总价格,使用余额支付
|
||||
list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
val remainingBalance = balance - totalPrice
|
||||
list.add(
|
||||
TextBean(
|
||||
text = remainingBalance.format2String(2),
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
} else {
|
||||
//余额小于总价格,使用余额+扫码支付
|
||||
//实际支付金额
|
||||
list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = realPayPrice.format2String(2),
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
}
|
||||
list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF5E7585"))
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
}
|
||||
+202
-202
@@ -1,202 +1,202 @@
|
||||
package com.sw.dualscreen.presentation.pay
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Presentation
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.ext.gone
|
||||
import com.sw.dualscreen.ext.invisible
|
||||
import com.sw.dualscreen.ext.load
|
||||
import com.sw.dualscreen.ext.visible
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.TextBean
|
||||
import com.sw.dualscreen.utils.SpannedUtils
|
||||
|
||||
class ScanQrCodePayPresentation(
|
||||
val activity: PayActivity,
|
||||
display: Display,
|
||||
val type: Int,
|
||||
private val onDismissListener: () -> Unit = {}
|
||||
) : Presentation(activity, display) {
|
||||
|
||||
//type = 0,扫码支付默认显示二维码
|
||||
//type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
|
||||
//type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
|
||||
|
||||
private lateinit var binding: PresentationScanQrcodePayBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
initView()
|
||||
}
|
||||
|
||||
var foodName: String? = null
|
||||
var payQrCodePic: Any? = null
|
||||
|
||||
//总价格
|
||||
var totalPrice = 0.0
|
||||
|
||||
//余额
|
||||
private var balance = 0.0
|
||||
|
||||
//实际支付金额
|
||||
private var realPayPrice = 0.0
|
||||
|
||||
//扣除余额
|
||||
private var expensesBalance = 0.0
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun initView() {
|
||||
when (type) {
|
||||
0 -> {
|
||||
binding.layoutVip.gone()
|
||||
binding.layoutPaySuccess.gone()
|
||||
binding.layoutScanQrCode.visible()
|
||||
|
||||
binding.tvPayTip.text = "请扫码支付或出示付款码"
|
||||
binding.tvFoodName.text = foodName
|
||||
binding.tvRealAmount.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 32),
|
||||
TextBean(text = totalPrice.format2String(2), textSize = 48),
|
||||
)
|
||||
)
|
||||
binding.ivPayQrCode.load(payQrCodePic)
|
||||
}
|
||||
|
||||
1 -> {
|
||||
val memberInfo = activity.memberInfo
|
||||
binding.layoutVip.run {
|
||||
if (memberInfo != null) visible() else gone()
|
||||
}
|
||||
binding.layoutPaySuccess.gone()
|
||||
binding.layoutScanQrCode.visible()
|
||||
|
||||
balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
|
||||
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
expensesBalance = if (balance >= totalPrice) totalPrice else balance
|
||||
|
||||
memberInfo?.let { loadUserInfo(it) }
|
||||
binding.tvUserBalance.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 20),
|
||||
TextBean(text = balance.format2String(2), textSize = 32),
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvFoodName.text = foodName
|
||||
binding.tvRealAmount.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 32),
|
||||
TextBean(text = totalPrice.format2String(2), textSize = 48),
|
||||
)
|
||||
)
|
||||
binding.ivPayQrCode.run {
|
||||
// load(payQrCodePic)
|
||||
if (balance >= totalPrice) invisible() else visible()
|
||||
}
|
||||
|
||||
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
|
||||
}
|
||||
|
||||
2 -> {
|
||||
val memberInfo = activity.memberInfo
|
||||
binding.layoutVip.run {
|
||||
if (memberInfo != null) visible() else gone()
|
||||
}
|
||||
binding.layoutPaySuccess.visible()
|
||||
binding.layoutScanQrCode.gone()
|
||||
|
||||
balance = if (memberInfo != null) (memberInfo.topUpBalance
|
||||
?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
|
||||
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
expensesBalance = if (balance >= totalPrice) totalPrice else balance
|
||||
|
||||
memberInfo?.let { loadUserInfo(it) }
|
||||
val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
|
||||
binding.tvUserBalance.text = SpannedUtils.getAmountText(
|
||||
listOf(
|
||||
TextBean(text = "¥", textSize = 24),
|
||||
TextBean(text = remainBalance.format2String(2), textSize = 36),
|
||||
)
|
||||
)
|
||||
|
||||
val showBalance = expensesBalance.format2String(2)
|
||||
val showTotal = totalPrice.format2String(2)
|
||||
binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal 元"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadQrCodeImage(qrCodeUrl: String?) {
|
||||
binding.ivPayQrCode.load(qrCodeUrl)
|
||||
}
|
||||
|
||||
private fun loadUserInfo(item: MemberInfo) {
|
||||
binding.ivHeadPic.load(
|
||||
if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
|
||||
else item.faceUrl
|
||||
)
|
||||
binding.tvUserName.text = item.name
|
||||
|
||||
val phone = item.phone ?: ""
|
||||
binding.tvUserPhone.text =
|
||||
if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
|
||||
else phone
|
||||
}
|
||||
|
||||
private fun getAmountList(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = "-${expensesBalance.format2String(2)}",
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
|
||||
if (balance >= totalPrice) {
|
||||
//余额大于等于总价格,使用余额支付
|
||||
list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
val remainingBalance = balance - totalPrice
|
||||
list.add(
|
||||
TextBean(
|
||||
text = remainingBalance.format2String(2),
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
} else {
|
||||
//余额小于总价格,使用余额+扫码支付
|
||||
//实际支付金额
|
||||
list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = realPayPrice.format2String(2),
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
}
|
||||
list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF5E7585"))
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
override fun onDisplayRemoved() {
|
||||
super.onDisplayRemoved()
|
||||
onDismissListener()
|
||||
}
|
||||
}
|
||||
//package com.sw.dualscreen.presentation.pay
|
||||
//
|
||||
//import android.annotation.SuppressLint
|
||||
//import android.app.Presentation
|
||||
//import android.os.Bundle
|
||||
//import android.view.Display
|
||||
//import com.sw.dualscreen.R
|
||||
//import com.sw.dualscreen.activity.PayActivity
|
||||
//import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding
|
||||
//import com.sw.dualscreen.ext.format2String
|
||||
//import com.sw.dualscreen.ext.gone
|
||||
//import com.sw.dualscreen.ext.invisible
|
||||
//import com.sw.dualscreen.ext.load
|
||||
//import com.sw.dualscreen.ext.visible
|
||||
//import com.sw.dualscreen.model.response.MemberInfo
|
||||
//import com.sw.dualscreen.model.response.TextBean
|
||||
//import com.sw.dualscreen.utils.SpannedUtils
|
||||
//
|
||||
//class ScanQrCodePayPresentation(
|
||||
// val activity: PayActivity,
|
||||
// display: Display,
|
||||
// val type: Int,
|
||||
// private val onDismissListener: () -> Unit = {}
|
||||
//) : Presentation(activity, display) {
|
||||
//
|
||||
// //type = 0,扫码支付默认显示二维码
|
||||
// //type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
|
||||
// //type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
|
||||
//
|
||||
// private lateinit var binding: PresentationScanQrcodePayBinding
|
||||
//
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
|
||||
// setContentView(binding.root)
|
||||
// window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
// initView()
|
||||
// }
|
||||
//
|
||||
// var foodName: String? = null
|
||||
// var payQrCodePic: Any? = null
|
||||
//
|
||||
// //总价格
|
||||
// var totalPrice = 0.0
|
||||
//
|
||||
// //余额
|
||||
// private var balance = 0.0
|
||||
//
|
||||
// //实际支付金额
|
||||
// private var realPayPrice = 0.0
|
||||
//
|
||||
// //扣除余额
|
||||
// private var expensesBalance = 0.0
|
||||
//
|
||||
// @SuppressLint("SetTextI18n")
|
||||
// private fun initView() {
|
||||
// when (type) {
|
||||
// 0 -> {
|
||||
// binding.layoutVip.gone()
|
||||
// binding.layoutPaySuccess.gone()
|
||||
// binding.layoutScanQrCode.visible()
|
||||
//
|
||||
// binding.tvPayTip.text = "请扫码支付或出示付款码"
|
||||
// binding.tvFoodName.text = foodName
|
||||
// binding.tvRealAmount.text = SpannedUtils.getAmountText(
|
||||
// listOf(
|
||||
// TextBean(text = "¥", textSize = 32),
|
||||
// TextBean(text = totalPrice.format2String(2), textSize = 48),
|
||||
// )
|
||||
// )
|
||||
// binding.ivPayQrCode.load(payQrCodePic)
|
||||
// }
|
||||
//
|
||||
// 1 -> {
|
||||
// val memberInfo = activity.memberInfo
|
||||
// binding.layoutVip.run {
|
||||
// if (memberInfo != null) visible() else gone()
|
||||
// }
|
||||
// binding.layoutPaySuccess.gone()
|
||||
// binding.layoutScanQrCode.visible()
|
||||
//
|
||||
// balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
|
||||
// realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
// expensesBalance = if (balance >= totalPrice) totalPrice else balance
|
||||
//
|
||||
// memberInfo?.let { loadUserInfo(it) }
|
||||
// binding.tvUserBalance.text = SpannedUtils.getAmountText(
|
||||
// listOf(
|
||||
// TextBean(text = "¥", textSize = 20),
|
||||
// TextBean(text = balance.format2String(2), textSize = 32),
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// binding.tvFoodName.text = foodName
|
||||
// binding.tvRealAmount.text = SpannedUtils.getAmountText(
|
||||
// listOf(
|
||||
// TextBean(text = "¥", textSize = 32),
|
||||
// TextBean(text = totalPrice.format2String(2), textSize = 48),
|
||||
// )
|
||||
// )
|
||||
// binding.ivPayQrCode.run {
|
||||
//// load(payQrCodePic)
|
||||
// if (balance >= totalPrice) invisible() else visible()
|
||||
// }
|
||||
//
|
||||
// binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
|
||||
// }
|
||||
//
|
||||
// 2 -> {
|
||||
// val memberInfo = activity.memberInfo
|
||||
// binding.layoutVip.run {
|
||||
// if (memberInfo != null) visible() else gone()
|
||||
// }
|
||||
// binding.layoutPaySuccess.visible()
|
||||
// binding.layoutScanQrCode.gone()
|
||||
//
|
||||
// balance = if (memberInfo != null) (memberInfo.topUpBalance
|
||||
// ?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
|
||||
// realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
// expensesBalance = if (balance >= totalPrice) totalPrice else balance
|
||||
//
|
||||
// memberInfo?.let { loadUserInfo(it) }
|
||||
// val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
|
||||
// binding.tvUserBalance.text = SpannedUtils.getAmountText(
|
||||
// listOf(
|
||||
// TextBean(text = "¥", textSize = 24),
|
||||
// TextBean(text = remainBalance.format2String(2), textSize = 36),
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// val showBalance = expensesBalance.format2String(2)
|
||||
// val showTotal = totalPrice.format2String(2)
|
||||
// binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal 元"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun loadQrCodeImage(qrCodeUrl: String?) {
|
||||
// binding.ivPayQrCode.load(qrCodeUrl)
|
||||
// }
|
||||
//
|
||||
// private fun loadUserInfo(item: MemberInfo) {
|
||||
// binding.ivHeadPic.load(
|
||||
// if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
|
||||
// else item.faceUrl
|
||||
// )
|
||||
// binding.tvUserName.text = item.name
|
||||
//
|
||||
// val phone = item.phone ?: ""
|
||||
// binding.tvUserPhone.text =
|
||||
// if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
|
||||
// else phone
|
||||
// }
|
||||
//
|
||||
// private fun getAmountList(): List<TextBean> {
|
||||
// val list: MutableList<TextBean> = mutableListOf()
|
||||
//
|
||||
// list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
// list.add(
|
||||
// TextBean(
|
||||
// text = "-${expensesBalance.format2String(2)}",
|
||||
// textSize = 30,
|
||||
// textColor = "#FF0A1428",
|
||||
// isBold = true
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// if (balance >= totalPrice) {
|
||||
// //余额大于等于总价格,使用余额支付
|
||||
// list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
// val remainingBalance = balance - totalPrice
|
||||
// list.add(
|
||||
// TextBean(
|
||||
// text = remainingBalance.format2String(2),
|
||||
// textSize = 30,
|
||||
// textColor = "#FF0A1428",
|
||||
// isBold = true
|
||||
// )
|
||||
// )
|
||||
// } else {
|
||||
// //余额小于总价格,使用余额+扫码支付
|
||||
// //实际支付金额
|
||||
// list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
|
||||
// list.add(
|
||||
// TextBean(
|
||||
// text = realPayPrice.format2String(2),
|
||||
// textSize = 30,
|
||||
// textColor = "#FF0A1428",
|
||||
// isBold = true
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF5E7585"))
|
||||
//
|
||||
// return list
|
||||
// }
|
||||
//
|
||||
// override fun onDisplayRemoved() {
|
||||
// super.onDisplayRemoved()
|
||||
// onDismissListener()
|
||||
// }
|
||||
//}
|
||||
@@ -9,6 +9,7 @@ import com.sw.dualscreen.model.response.FaceData
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||
import com.sw.dualscreen.model.response.FoodOrder
|
||||
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||
import com.sw.dualscreen.model.response.FoodVector
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.UserFaceModel
|
||||
@@ -76,7 +77,8 @@ class RemoteRepository constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
/**
|
||||
* 获取人脸数据
|
||||
*/
|
||||
suspend fun getFaceIncrementList(
|
||||
@@ -134,16 +136,18 @@ class RemoteRepository constructor(
|
||||
suspend fun getRestInfoFoodsByType(
|
||||
foodName: String,
|
||||
pageNum: Int = 1,
|
||||
pageSize:Int = 50
|
||||
pageSize: Int = 50
|
||||
): ApiResponse<List<FoodInfo>> {
|
||||
return safeApiCall {
|
||||
apiService.getRestInfoFoodsByType(param = hashMapOf(
|
||||
"name" to foodName,
|
||||
"pageNum" to "$pageNum",
|
||||
"pageSize" to "$pageSize",
|
||||
//"订单来源权举:1-营养秤,2-档口
|
||||
"deviceType" to "2"
|
||||
))
|
||||
apiService.getRestInfoFoodsByType(
|
||||
param = hashMapOf(
|
||||
"name" to foodName,
|
||||
"pageNum" to "$pageNum",
|
||||
"pageSize" to "$pageSize",
|
||||
//"订单来源权举:1-营养秤,2-档口
|
||||
"deviceType" to "2"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +190,26 @@ class RemoteRepository constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码支付
|
||||
*/
|
||||
suspend fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?
|
||||
): ApiResponse<String?> {
|
||||
return safeApiCall {
|
||||
apiService.qrCodePay(
|
||||
param = hashMapOf(
|
||||
"authCode" to authCode,
|
||||
"outTradeNo" to orderNo,
|
||||
"memberId" to memberId,
|
||||
"orderType" to "0"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getDinnerType(
|
||||
// restId: String = GlobalData.restId,
|
||||
): ApiResponse<DinnerType> {
|
||||
@@ -247,7 +271,7 @@ class RemoteRepository constructor(
|
||||
/**
|
||||
* 会员支付
|
||||
*/
|
||||
suspend fun memberPay(param: HashMap<String, String>): ApiResponse<Any?> {
|
||||
suspend fun memberPay(param: HashMap<String, String?>): ApiResponse<Any?> {
|
||||
return safeApiCall {
|
||||
apiService.memberPay(param = param)
|
||||
}
|
||||
@@ -276,10 +300,12 @@ class RemoteRepository constructor(
|
||||
*/
|
||||
suspend fun getMemberInfoByPhone(phone: String, key: String): ApiResponse<MemberInfo?> {
|
||||
return safeApiCall {
|
||||
apiService.getMemberInfoByPhone(param = hashMapOf(
|
||||
"phone" to phone,
|
||||
"password" to key
|
||||
))
|
||||
apiService.getMemberInfoByPhone(
|
||||
param = hashMapOf(
|
||||
"phone" to phone,
|
||||
"password" to key
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,12 +343,26 @@ class RemoteRepository constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteCollectFood(foodId:String?, version: String?): ApiResponse<Any?> {
|
||||
suspend fun deleteCollectFood(foodId: String?, version: String?): ApiResponse<Any?> {
|
||||
return safeApiCall {
|
||||
apiService.deleteCollectFood(param = mutableMapOf(
|
||||
"foodId" to foodId,
|
||||
"version" to version
|
||||
))
|
||||
apiService.deleteCollectFood(
|
||||
param = mutableMapOf(
|
||||
"foodId" to foodId,
|
||||
"version" to version
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFoodOrderList(userId: String): ApiResponse<FoodOrderModel?> {
|
||||
return safeApiCall {
|
||||
apiService.getFoodOrderList(userId = userId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getMemberDiscount(userId: String): ApiResponse<Double?> {
|
||||
return safeApiCall {
|
||||
apiService.getMemberDiscount(userId = userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
typealias Callback = (Double) -> Unit
|
||||
typealias WeightCallback = (Double) -> Unit
|
||||
typealias WeightCallback = (Int) -> Unit
|
||||
|
||||
private const val TAG = "SensorScaleUtils"
|
||||
|
||||
@@ -24,6 +24,7 @@ object SensorScaleUtils {
|
||||
var isZero = false
|
||||
private var callback: Callback? = {}
|
||||
private var lastWeight: Double? = null
|
||||
private var lastCallbackWeight: Double? = null
|
||||
|
||||
private fun init() {
|
||||
mSensorScale = SensorScale(object : OnScaleResult {
|
||||
@@ -31,21 +32,28 @@ object SensorScaleUtils {
|
||||
* 读取重量
|
||||
*/
|
||||
override fun readWeight(state: Int, value: Double) {
|
||||
val stateStr = when (state) {
|
||||
SensorScale.STATE_STABLE -> "稳定"
|
||||
SensorScale.STATE_UNSTABLE -> "不稳定"
|
||||
SensorScale.STATE_OVER_WEIGHT -> "量程溢出"
|
||||
else -> "未知"
|
||||
}
|
||||
// val stateStr = when (state) {
|
||||
// SensorScale.STATE_STABLE -> "稳定"
|
||||
// SensorScale.STATE_UNSTABLE -> "不稳定"
|
||||
// SensorScale.STATE_OVER_WEIGHT -> "量程溢出"
|
||||
// else -> "未知"
|
||||
// }
|
||||
// Log.d(TAG, "readWeight state = ${stateStr}, weight = $value")
|
||||
// 只使用稳定值
|
||||
if (state == SensorScale.STATE_STABLE && lastWeight != value) {
|
||||
lastWeight = value
|
||||
callback?.invoke(value)
|
||||
}
|
||||
if (state == SensorScale.STATE_STABLE) {
|
||||
if (lastWeight != value) {
|
||||
lastWeight = value
|
||||
callback?.invoke(value)
|
||||
}
|
||||
|
||||
weightCallbackList.forEach {
|
||||
it.invoke(value)
|
||||
// 去重:仅在重量变化时回调 weightCallbackList,避免同一重量反复触发
|
||||
if (lastCallbackWeight != value) {
|
||||
lastCallbackWeight = value
|
||||
weightCallbackList.forEach {
|
||||
val currentWeight = value * 1000
|
||||
it.invoke(currentWeight.roundToInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +95,7 @@ object SensorScaleUtils {
|
||||
// Thread.sleep(1000)
|
||||
|
||||
// TODO: 暂时不标定---------
|
||||
//zero()
|
||||
|
||||
zero()
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
@@ -130,13 +137,15 @@ object SensorScaleUtils {
|
||||
/**
|
||||
* 零位标定
|
||||
*/
|
||||
fun zero() {
|
||||
fun zero(isShowToastRemind: Boolean = false) {
|
||||
Timber.d("zero isOpened = $isOpened, mSensorScale = $mSensorScale")
|
||||
if (!isOpened) return
|
||||
mSensorScale?.zero {
|
||||
isZero = true
|
||||
Timber.d("zero 零位标定操作成功")
|
||||
ToastUtils.showToast("零位标定操作成功")
|
||||
if (isShowToastRemind) {
|
||||
ToastUtils.showToast("零位标定操作成功")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ class CameraUtils(private var activity: ComponentActivity) {
|
||||
photoCaptureHelper = PhotoCaptureHelper(
|
||||
context = activity,
|
||||
cameraController = cameraController!!,
|
||||
onSuccess = {},
|
||||
onError = { msg ->
|
||||
//toast(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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class FileUtil {
|
||||
private static boolean isSaveLog = true;
|
||||
public static final String FILE_STR_PATH = App.getContext().getExternalCacheDir().getAbsolutePath();
|
||||
// public static final String FILE_STR_PATH = App.getContext().getExternalCacheDir().getAbsolutePath();
|
||||
// 使用内部存储,不会被系统缓存清理策略影响
|
||||
public static final String FILE_STR_PATH = new File(App.getContext().getFilesDir(), "logs").getAbsolutePath();
|
||||
// 单线程执行器,保证日志串行写入,避免多线程并发竞争
|
||||
|
||||
// 单线程执行器,保证日志串行写入,避免多线程并发竞争
|
||||
private static final ExecutorService LOG_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
public static final int SIZETYPE_B = 1;//获取文件大小单位为B的double值
|
||||
public static final int SIZETYPE_KB = 2;//获取文件大小单位为KB的double值
|
||||
public static final int SIZETYPE_MB = 3;//获取文件大小单位为MB的double值
|
||||
@@ -610,24 +617,19 @@ public class FileUtil {
|
||||
if (!isSaveLog) {
|
||||
return;
|
||||
}
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
super.run();
|
||||
// 提交到单线程队列,串行执行,避免并发写入竞争
|
||||
LOG_EXECUTOR.execute(() -> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String log : logs) {
|
||||
sb.append("===")
|
||||
.append((AppUtil.formatDateGetCurrentTime()))
|
||||
.append("===")
|
||||
.append("\n")
|
||||
.append(log)
|
||||
.append("\n");
|
||||
}
|
||||
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
|
||||
FileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
|
||||
|
||||
}
|
||||
}.start();
|
||||
});
|
||||
}
|
||||
|
||||
private static final String[][] MIME_MapTable = {
|
||||
|
||||
@@ -0,0 +1,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? {
|
||||
return try {
|
||||
val options = BitmapFactory.Options()
|
||||
//options.inSampleSize = 2; // 这会将图片的尺寸缩小到原来的1/2
|
||||
options.inJustDecodeBounds = false
|
||||
// options.inPreferredConfig = Bitmap.Config.ARGB_8888
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
// 先只读尺寸,计算合适的采样率
|
||||
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||
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 ->
|
||||
BitmapFactory.decodeStream(stream)
|
||||
BitmapFactory.decodeStream(stream, null, options)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
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.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCaptureException
|
||||
import androidx.camera.view.CameraController
|
||||
@@ -19,9 +22,9 @@ import java.io.File
|
||||
class PhotoCaptureHelper(
|
||||
private val context: Context,
|
||||
private val cameraController: CameraController,
|
||||
private val onSuccess: (Uri) -> Unit = {},
|
||||
private val onError: (String) -> Unit = {}
|
||||
) {
|
||||
|
||||
private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf()
|
||||
fun addSuccessCallback(callback:(Uri) -> Unit) {
|
||||
if (callbackList.contains(callback).not()) {
|
||||
@@ -41,7 +44,8 @@ class PhotoCaptureHelper(
|
||||
*/
|
||||
fun takePhoto(
|
||||
fileNamePrefix: String = "IMG_",
|
||||
fileExtension: String = ".jpg"
|
||||
fileExtension: String = ".jpg",
|
||||
isRetry: Boolean = false
|
||||
) {
|
||||
Timber.d("开始拍照采集")
|
||||
|
||||
@@ -63,20 +67,22 @@ class PhotoCaptureHelper(
|
||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||
val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
|
||||
Timber.d("照片保存成功: $photoUri")
|
||||
onSuccess(photoUri)
|
||||
callbackList.forEach {
|
||||
it(photoUri)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(exception: ImageCaptureException) {
|
||||
Timber.e(exception)
|
||||
val errorMsg = "拍照失败: ${exception.message}"
|
||||
if (errorMsg.contains("Not bound to a valid Camera")) {
|
||||
if (bindCamera != null) {
|
||||
bindCamera?.invoke()
|
||||
//takePhoto()
|
||||
}
|
||||
|
||||
if (errorMsg.contains("Not bound to a valid Camera") && !isRetry) {
|
||||
Timber.w(exception, "相机未绑定,尝试重新绑定后重试")
|
||||
bindCamera?.invoke()
|
||||
// 延迟重试,等待相机异步绑定完成
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
takePhoto(fileNamePrefix, fileExtension, isRetry = true)
|
||||
}, 300)
|
||||
return
|
||||
}
|
||||
Timber.e(exception, errorMsg)
|
||||
onError(errorMsg)
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
package com.sw.dualscreen.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioManager;
|
||||
import android.media.SoundPool;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class SoundPoolUtil {
|
||||
|
||||
private static final String TAG = SoundPoolUtil.class.getSimpleName();
|
||||
private static SoundPoolUtil mSound;
|
||||
private SoundPool mSoundPool;
|
||||
private boolean isLoadC = false;
|
||||
private Map<String, Integer> idCache;
|
||||
private List<Integer> sidCache;
|
||||
|
||||
public SoundPoolUtil() {
|
||||
idCache = new HashMap<>();
|
||||
sidCache = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AudioAttributes aab = new AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build();
|
||||
mSoundPool = new SoundPool.Builder()
|
||||
.setMaxStreams(10)
|
||||
.setAudioAttributes(aab)
|
||||
.build();
|
||||
} else {
|
||||
mSoundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 8);
|
||||
}
|
||||
// mSoundPool = new SoundPool(60, AudioManager.USE_DEFAULT_STREAM_TYPE, 7);
|
||||
mSoundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener());
|
||||
}
|
||||
|
||||
public static SoundPoolUtil getInstance() {
|
||||
|
||||
synchronized (SoundPoolUtil.class) {
|
||||
if (mSound == null) {
|
||||
mSound = new SoundPoolUtil();
|
||||
}
|
||||
}
|
||||
return mSound;
|
||||
}
|
||||
|
||||
private int loadCompleteSize = 0;
|
||||
private LoadCompletion completionListener;
|
||||
|
||||
public void setCompletionListener(LoadCompletion listener) {
|
||||
this.completionListener = listener;
|
||||
}
|
||||
|
||||
private class MyOnLoadCompleteListener implements SoundPool.OnLoadCompleteListener {
|
||||
|
||||
@Override
|
||||
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
|
||||
loadCompleteSize++;
|
||||
|
||||
L.e("loadSize" + loadSize + "===" + loadCompleteSize);
|
||||
if (loadSize == loadCompleteSize) {
|
||||
isLoadC = true;
|
||||
if (completionListener != null) {
|
||||
completionListener.onCompletion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载指定资源
|
||||
*
|
||||
* @param name
|
||||
* @param path
|
||||
*/
|
||||
public void loadR(String name, String path) {
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(name)) {
|
||||
idCache.put(name, mSoundPool.load(path, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int loadSize = 0;
|
||||
|
||||
/**
|
||||
* 加载指定路径列表的资源
|
||||
*
|
||||
* @param map
|
||||
*/
|
||||
public void loadR(Map<String, String> map) {
|
||||
loadSize = map.size();
|
||||
Set<Map.Entry<String, String>> entries = map.entrySet();
|
||||
for (Map.Entry<String, String> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(key)) {
|
||||
idCache.put(key, mSoundPool.load(entry.getValue(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定AssetFileDescriptor的资源
|
||||
*
|
||||
* @param name
|
||||
* @param afd
|
||||
*/
|
||||
public void loadRF(String name, AssetFileDescriptor afd) {
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(name)) {
|
||||
idCache.put(name, mSoundPool.load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定AssetFileDescriptor列表的资源
|
||||
*
|
||||
* @param map
|
||||
*/
|
||||
public void loadRF(Map<String, AssetFileDescriptor> map) {
|
||||
Set<Map.Entry<String, AssetFileDescriptor>> entries = map.entrySet();
|
||||
for (Map.Entry<String, AssetFileDescriptor> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(key)) {
|
||||
idCache.put(key, mSoundPool.load(entry.getValue().getFileDescriptor(), entry.getValue().getStartOffset(), entry.getValue().getLength(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定列表资源
|
||||
*
|
||||
* @param context
|
||||
* @param map
|
||||
*/
|
||||
public void loadR(Context context, Map<String, Integer> map) {
|
||||
loadSize = map.size();
|
||||
Set<Map.Entry<String, Integer>> entries = map.entrySet();
|
||||
for (Map.Entry<String, Integer> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
if (checkSoundPool()) {
|
||||
|
||||
if (!idCache.containsKey(key)) {
|
||||
idCache.put(key, mSoundPool.load(context, entry.getValue(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载单个音频
|
||||
*
|
||||
* @param context
|
||||
* @param name
|
||||
* @param res
|
||||
*/
|
||||
public void loadR(Context context, String name, int res) {
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(name)) {
|
||||
idCache.put(name, mSoundPool.load(context, res, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并返用于停止、暂停、恢复的StreamId
|
||||
*
|
||||
* @param name
|
||||
* @param times
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, int times) {
|
||||
L.e(String.format("play %s times=%s", name, times));
|
||||
return this.play(name, 1, 1, 1, times, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并指定播放次数和频率
|
||||
*
|
||||
* @param name
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, int times, int rate) {
|
||||
return this.play(name, 1, 1, 1, times, rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并指定优先级和播放频率
|
||||
*
|
||||
* @param name
|
||||
* @param property
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, int property, int times, int rate) {
|
||||
return this.play(name, 1, 1, property, times, rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并指定左右声道、优先级、播放次数、播放频率
|
||||
*
|
||||
* @param name
|
||||
* @param leftVolume
|
||||
* @param rightVolume
|
||||
* @param property
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, float leftVolume, float rightVolume, int property, int times, int rate) {
|
||||
int streamId = -1;
|
||||
if (checkSoundPool()) {
|
||||
L.d(TAG, "play: " + name);
|
||||
if (idCache.containsKey(name) && isLoadC) {
|
||||
L.d(TAG, "name:" + idCache.get(name));
|
||||
streamId = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate);
|
||||
L.d(TAG, "streadmId:" + streamId);
|
||||
sidCache.add(streamId);
|
||||
}
|
||||
}
|
||||
return streamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表
|
||||
*
|
||||
* @param names
|
||||
* @param times
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> play(List<String> names, int times) {
|
||||
|
||||
return this.play(names, 1, 1, 1, times, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定次数和频率
|
||||
*
|
||||
* @param names
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> play(List<String> names, int times, int rate) {
|
||||
|
||||
return this.play(names, 1, 1, 1, times, rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定所有参数
|
||||
*
|
||||
* @param names
|
||||
* @param leftVolume
|
||||
* @param rightVolume
|
||||
* @param property
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> play(List<String> names, int leftVolume, int rightVolume, int property, int times, int rate) {
|
||||
List<Integer> streamIds = new ArrayList<>();
|
||||
if (checkSoundPool()) {
|
||||
for (String name : names) {
|
||||
if (idCache.containsKey(name) && isLoadC) {
|
||||
int a = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate);
|
||||
streamIds.add(a);
|
||||
sidCache.add(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
return streamIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止指定id音频
|
||||
*/
|
||||
public void stop(int r) {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.stop(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止指定列表音频
|
||||
*/
|
||||
public void stopAll() {
|
||||
if (checkSoundPool()) {
|
||||
for (int r : sidCache) {
|
||||
mSoundPool.stop(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停指定音效
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
public void pause(int r) {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.pause(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停指定列表音频
|
||||
*
|
||||
* @param list
|
||||
*/
|
||||
public void pause(List<Integer> list) {
|
||||
if (checkSoundPool()) {
|
||||
for (int r : list) {
|
||||
mSoundPool.pause(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停所有音效
|
||||
*/
|
||||
public void pauseAll() {
|
||||
mSoundPool.autoPause();
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复指定音频播放
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
public void resume(int r) {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.resume(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复指定列表的音频
|
||||
*
|
||||
* @param list
|
||||
*/
|
||||
public void resume(List<Integer> list) {
|
||||
if (checkSoundPool()) {
|
||||
for (int r : list) {
|
||||
mSoundPool.resume(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复所有暂停的音频
|
||||
*/
|
||||
public void resumeAll() {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.autoResume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定音频
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public void unLoad(String name) {
|
||||
if (checkSoundPool()) {
|
||||
if (idCache.containsKey(name)) {
|
||||
mSoundPool.unload(idCache.get(name));
|
||||
idCache.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定列表的音频
|
||||
*
|
||||
* @param names
|
||||
*/
|
||||
public void unLoad(List<String> names) {
|
||||
if (checkSoundPool()) {
|
||||
for (String name : names) {
|
||||
if (idCache.containsKey(name)) {
|
||||
mSoundPool.unload(idCache.get(name));
|
||||
idCache.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 释放所有资源,如果想继续播放,需要重新加载资源
|
||||
*/
|
||||
public void release() {
|
||||
if (checkSoundPool()) {
|
||||
mSound = null;
|
||||
mSoundPool.release();
|
||||
idCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkSoundPool() {
|
||||
if (mSoundPool != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface LoadCompletion {
|
||||
void onCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.sw.dualscreen.utils;
|
||||
|
||||
import com.sw.dualscreen.MyApp;
|
||||
|
||||
public class SpTool {
|
||||
|
||||
public static final String LAST_FACE_TIMESTAMP = "faceTimestamp";
|
||||
|
||||
public static long getLastFaceTimestamp() {
|
||||
return SPUtil.Companion.getInstance(MyApp.getContext(), "default_sp").get(LAST_FACE_TIMESTAMP, 0L);
|
||||
}
|
||||
|
||||
public static void setLastFaceTimestamp(long timestamp) {
|
||||
SPUtil.Companion.getInstance(MyApp.getContext(), "default_sp").put(LAST_FACE_TIMESTAMP, timestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.sw.dualscreen.utils
|
||||
|
||||
import com.sw.dualscreen.GlobalKey
|
||||
import com.sw.dualscreen.MyApp
|
||||
import com.sw.dualscreen.utils.SPUtil.Companion.getInstance
|
||||
|
||||
object SpTool {
|
||||
const val LAST_FACE_TIMESTAMP: String = "faceTimestamp"
|
||||
|
||||
const val IS_FIRST_GET_FACE = "isFirstGetFace"
|
||||
|
||||
private var spUtil: SPUtil? = null
|
||||
|
||||
init {
|
||||
spUtil = getInstance(MyApp.instance!!, "default_sp")
|
||||
}
|
||||
|
||||
|
||||
var lastFaceTimestamp: Long
|
||||
get() = spUtil?.get(LAST_FACE_TIMESTAMP, 0L) ?: 0L
|
||||
set(timestamp) {
|
||||
spUtil?.put(LAST_FACE_TIMESTAMP, timestamp)
|
||||
}
|
||||
|
||||
var firstGetFace: Boolean
|
||||
get() = spUtil?.get(IS_FIRST_GET_FACE, true) ?: true
|
||||
set(value) {
|
||||
spUtil?.boolean(IS_FIRST_GET_FACE, value)
|
||||
}
|
||||
|
||||
var baseUrl: String?
|
||||
get() = spUtil?.get(GlobalKey.KEY_BASE_URL, "")
|
||||
set(value) {
|
||||
spUtil?.put(GlobalKey.KEY_BASE_URL, value)
|
||||
}
|
||||
var settlementMode: Int
|
||||
get() = spUtil?.get(GlobalKey.KEY_SETTLEMENT_MODE, 0) ?: 0
|
||||
set(value) {
|
||||
spUtil?.put(GlobalKey.KEY_SETTLEMENT_MODE, value)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import com.sw.dualscreen.databinding.BottomSheetDialogBinding
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.utils.Debouncer
|
||||
import com.sw.dualscreen.utils.KeyboardUtils
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||
import timber.log.Timber
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
|
||||
@@ -24,7 +24,7 @@ import androidx.core.graphics.drawable.toDrawable
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
class CustomBottomSheetDialog(
|
||||
val viewModel: UserViewModel,
|
||||
val viewModel: NetViewModelV2,
|
||||
val itemClickCallback: (FoodInfo) -> Unit
|
||||
) : BottomSheetDialogFragment() {
|
||||
private lateinit var binding: BottomSheetDialogBinding
|
||||
@@ -87,7 +87,7 @@ class CustomBottomSheetDialog(
|
||||
|
||||
companion object {
|
||||
fun newInstance(
|
||||
viewModel: UserViewModel,
|
||||
viewModel: NetViewModelV2,
|
||||
itemClickCallback: (FoodInfo) -> Unit
|
||||
): CustomBottomSheetDialog {
|
||||
return CustomBottomSheetDialog(viewModel, itemClickCallback)
|
||||
|
||||
@@ -21,7 +21,7 @@ class HollowOvalInRectView @JvmOverloads constructor(
|
||||
private val rectWidthDp = 400f // 矩形宽度
|
||||
private val rectHeightDp = 510f // 矩形高度
|
||||
private val ovalWidthDp = 400f // 椭圆宽度
|
||||
private val ovalHeightDp = 436f // 椭圆高度
|
||||
private val ovalHeightDp = 416f // 椭圆高度
|
||||
|
||||
// 转换后的像素尺寸
|
||||
private val rectWidth: Float
|
||||
|
||||
@@ -65,7 +65,9 @@ abstract class BaseViewModel() : ViewModel() {
|
||||
}
|
||||
val message = response.msg
|
||||
Timber.d("msg = ${message}, code = $code")
|
||||
ToastUtils.showToast("${message}(${code})")
|
||||
if (message!=null && !message.startsWith("未知错误")) {
|
||||
ToastUtils.showToast("${message}(${code})")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import com.arcsoft.face.ErrorInfo
|
||||
import com.sw.dualscreen.GlobalData
|
||||
import com.sw.dualscreen.GlobalKey
|
||||
import com.sw.dualscreen.model.request.UserNutritionParam
|
||||
import com.sw.dualscreen.model.response.ApiResponse
|
||||
import com.sw.dualscreen.model.response.DeviceConfig
|
||||
import com.sw.dualscreen.model.response.DinnerType
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.FoodOrder
|
||||
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||
import com.sw.dualscreen.model.response.FoodVector
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.UserFaceModel
|
||||
@@ -16,6 +18,7 @@ import com.sw.dualscreen.model.response.UserFaceModel2
|
||||
import com.sw.dualscreen.model.response.UserNutrition
|
||||
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
||||
import com.sw.dualscreen.utils.Debouncer
|
||||
import com.sw.dualscreen.utils.FileUtil
|
||||
import com.sw.dualscreen.utils.SPUtil
|
||||
import com.sw.dualscreen.utils.SpTool
|
||||
import com.sw.plate.App
|
||||
@@ -89,51 +92,70 @@ class UserViewModel : BaseViewModel() {
|
||||
/**
|
||||
* 获取人脸数据
|
||||
*/
|
||||
fun getUserFaceCache(pageNo: Int = 1, pageSize: Int = PAGE_SIZE) {
|
||||
fun getUserFaceCache(
|
||||
pageNo: Int = 1,
|
||||
pageSize: Int = PAGE_SIZE,
|
||||
onSuccess: () -> Unit = {},
|
||||
onFailure: (String) -> Unit = {}
|
||||
) {
|
||||
// Initialize current page number
|
||||
var currentPageNo = pageNo
|
||||
// Debug log to track current page
|
||||
Timber.tag(TAG).d("getUserFaceCache index = $currentPageNo")
|
||||
// Launch a coroutine to perform the face data retrieval
|
||||
launch {
|
||||
// Set loading state to false
|
||||
_loadFaceResult.value = false
|
||||
//获取全量数据时,时间戳改为0
|
||||
SpTool.setLastFaceTimestamp(0)
|
||||
SpTool.lastFaceTimestamp = 0
|
||||
FileUtil.saveLog("获取人脸全量数据开始,重置时间戳为0")
|
||||
val response = repository.getUserFaceCache(currentPageNo)
|
||||
if (parseResponse(response)) {
|
||||
// 获取成功一次后缓存状态
|
||||
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
|
||||
withContext(Dispatchers.Default) {
|
||||
val list: List<UserFaceModel> = response.data ?: emptyList()
|
||||
if (pageNo == 1 && list.isEmpty()) {
|
||||
ToastUtils.showToast("查询人脸数据为空")
|
||||
return@withContext
|
||||
}
|
||||
// val item = list.firstOrNull { it.userId == "1951105919342936066" }
|
||||
// Timber.tag(TAG).d("getUserFaceCache userId = ${item?.userId}")
|
||||
val faceEntity = list.map {
|
||||
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
|
||||
}
|
||||
faceApi.updateFaceData(currentPageNo, faceEntity)
|
||||
if (list.size >= pageSize) {
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
currentPageNo++
|
||||
getUserFaceCache(currentPageNo)
|
||||
return@withContext
|
||||
}
|
||||
//查询完成所有数据,保存时间戳
|
||||
if (list.isNotEmpty()) {
|
||||
//非空最后一条数据的是时间戳,空使用上次list最后一条时间戳
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.setLastFaceTimestamp(lastFaceTimestamp)
|
||||
_loadFaceResult.value = true
|
||||
val check = parseResponse(response)
|
||||
if (!check) {
|
||||
// 获取失败
|
||||
onFailure("获取人脸数据失败,${response.msg}(${response.code})")
|
||||
return@launch
|
||||
}
|
||||
// 获取成功一次后缓存状态
|
||||
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
|
||||
withContext(Dispatchers.Default) {
|
||||
val list: List<UserFaceModel> = response.data ?: emptyList()
|
||||
if (pageNo == 1 && list.isEmpty()) {
|
||||
//ToastUtils.showToast("查询人脸数据为空")
|
||||
onFailure("查询人脸数据为空")
|
||||
return@withContext
|
||||
}
|
||||
|
||||
//val item = list.map { it.member }
|
||||
//Timber.tag(TAG).d("getUserFaceCache item = $item")
|
||||
val faceEntity = list.map {
|
||||
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr)).also { item ->
|
||||
item.userType = if (it.member) "1" else "2"
|
||||
item.cardNo = it.cardNo
|
||||
}
|
||||
}
|
||||
faceApi.updateFaceData(currentPageNo, faceEntity)
|
||||
if (list.size >= pageSize) {
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
currentPageNo++
|
||||
getUserFaceCache(currentPageNo)
|
||||
return@withContext
|
||||
}
|
||||
//查询完成所有数据,保存时间戳
|
||||
if (list.isNotEmpty()) {
|
||||
//非空最后一条数据的是时间戳,空使用上次list最后一条时间戳
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||
_loadFaceResult.value = true
|
||||
onSuccess()
|
||||
}
|
||||
// val nextPageIndex = response.result?.nextPageIndex ?: -1
|
||||
// if (nextPageIndex > 0) {
|
||||
// getUserFaceCache(nextPageIndex)
|
||||
// } else {
|
||||
// _loadFaceResult.value = true
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +223,8 @@ class UserViewModel : BaseViewModel() {
|
||||
//非空实现最后一条数据的是时间戳,空使用上次list最后一条时间戳
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.setLastFaceTimestamp(lastFaceTimestamp)
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||
onAllQueryFinished()
|
||||
}
|
||||
}
|
||||
@@ -356,6 +379,25 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码支付
|
||||
*/
|
||||
fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?,
|
||||
block: (Boolean, String?) -> Unit
|
||||
) {
|
||||
launch {
|
||||
val response = repository.qrCodePay(authCode, orderNo, memberId)
|
||||
if (parseResponse(response)) {
|
||||
block(true, response.data)
|
||||
} else {
|
||||
block(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getDinnerType() {
|
||||
Timber.tag(TAG).d("getDinnerType")
|
||||
_dinnerTypeInfo.value = null
|
||||
@@ -474,7 +516,7 @@ class UserViewModel : BaseViewModel() {
|
||||
/**
|
||||
* 会员支付
|
||||
*/
|
||||
fun memberPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
|
||||
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("memberPay")
|
||||
launchWithLoading {
|
||||
val response = repository.memberPay(param)
|
||||
@@ -571,19 +613,22 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getCollectedFoodVector(param: MutableMap<String, String>, block: (List<FoodVector>?) -> Unit) {
|
||||
fun getCollectedFoodVector(
|
||||
param: MutableMap<String, String>,
|
||||
block: (List<FoodVector>?) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("getCollectedFoodVector")
|
||||
launchWithLoading {
|
||||
val response = repository.getCollectedFoodVector(param)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data?:emptyList())
|
||||
block(response.data ?: emptyList())
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCollectFood(foodId:String?, version: String?, block: (Boolean) -> Unit) {
|
||||
fun deleteCollectFood(foodId: String?, version: String?, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("deleteCollectFood")
|
||||
launchWithLoading {
|
||||
val response = repository.deleteCollectFood(foodId, version)
|
||||
@@ -594,4 +639,28 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) {
|
||||
Timber.tag(TAG).d("getFoodOrderList")
|
||||
launchWithLoading {
|
||||
val response = repository.getFoodOrderList(userId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberDiscount")
|
||||
launchWithLoading {
|
||||
val response = repository.getMemberDiscount(userId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F2F2F2"/>
|
||||
<!-- <stroke android:color="#cdcdcd" android:width="1dp"/>-->
|
||||
<corners android:radius="5dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- 眼睛轮廓 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
|
||||
<!-- 瞳孔(path绘制圆形) -->
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
|
||||
<!-- 斜线表示隐藏 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:pathData="M 4 4 L 20 20" />
|
||||
</vector>
|
||||
@@ -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_gravity="center_vertical"
|
||||
android:layout_marginStart="28dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:textColor="#ff0a1428"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold"
|
||||
@@ -48,19 +49,23 @@
|
||||
android:textColor="#FF0A1428"
|
||||
android:textSize="52sp"
|
||||
android:textStyle="bold" />
|
||||
<!-- <TextView-->
|
||||
<!-- android:id="@+id/tvShowWeight"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="80dp"-->
|
||||
<!-- android:textSize="30sp"-->
|
||||
<!-- android:gravity="center"-->
|
||||
<!-- tools:text="100克"/>-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvShowWeight"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:textSize="20sp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/black"
|
||||
tools:text="100克"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/foodDisplayLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="560dp"
|
||||
android:layout_marginHorizontal="28dp"
|
||||
android:layout_marginTop="80dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:visibility="visible">
|
||||
|
||||
<androidx.camera.view.PreviewView
|
||||
@@ -84,53 +89,21 @@
|
||||
android:layout_margin="27dp"
|
||||
android:background="@drawable/ic_camera_rect" />
|
||||
<!-- preview_border-->
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerview"
|
||||
<FrameLayout
|
||||
android:id="@+id/flFoodList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="14dp"
|
||||
android:layout_marginTop="42dp"
|
||||
android:layout_weight="1"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="8"
|
||||
tools:listitem="@layout/list_item_search_food" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvToSearch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="38dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:padding="10dp"
|
||||
android:text="以上都不是,手动搜索"
|
||||
android:textColor="#FFFF3232"
|
||||
android:textSize="31sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="54dp"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvWeightRealInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="12sp"
|
||||
tools:text="以上都不是,手动搜索" />
|
||||
</LinearLayout>
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flPay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:layout_marginTop="45dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<RadioGroup
|
||||
android:id="@+id/rgSetting"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
android:layout_height="75dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Space
|
||||
@@ -31,7 +31,7 @@
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="50dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="35dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:text="经营设置"
|
||||
android:textColor="@color/setting_radio"
|
||||
android:textSize="30sp"
|
||||
@@ -53,7 +53,7 @@
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="50dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="35dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:text="餐品采集"
|
||||
android:textColor="@color/setting_radio"
|
||||
android:textSize="30sp"
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="600dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_white_radius12"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="40dp"
|
||||
android:paddingVertical="36dp">
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="环境切换"
|
||||
android:textColor="#ff141428"
|
||||
android:textSize="36sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- 分割线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="#FFDCDCF0" />
|
||||
|
||||
<!-- 环境选项 -->
|
||||
<RadioGroup
|
||||
android:id="@+id/rgEnv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbTest"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:text="本地环境"
|
||||
android:textColor="#ff141428"
|
||||
android:textSize="28sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbUat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:text="测试环境"
|
||||
android:textColor="#ff141428"
|
||||
android:textSize="28sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbProd"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:text="生产环境"
|
||||
android:textColor="#ff141428"
|
||||
android:textSize="28sp" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="#FFDCDCF0" />
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="88dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCancel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_border_red"
|
||||
android:text="取消"
|
||||
android:textColor="#FFFF3232"
|
||||
android:textSize="28sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnConfirm"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_btn_save2"
|
||||
android:text="确认"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="28sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -6,6 +6,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 结算设置模块 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -16,6 +17,85 @@
|
||||
android:paddingTop="35dp"
|
||||
android:paddingBottom="42dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="17dp"
|
||||
android:text="结算设置"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="36sp"
|
||||
android:textStyle="bold"
|
||||
app:drawableStartCompat="@drawable/line_shape" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="(不允许修改,由后端配置)"
|
||||
android:textColor="#FFFF3232"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rgSettlement"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="35dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbJointPayment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/setting_check_selector"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text="联合支付"
|
||||
android:textColor="@color/setting_radio"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbIndependentPayment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/setting_check_selector"
|
||||
android:button="@null"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text="独立支付"
|
||||
android:textColor="@color/setting_radio"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
tools:checked="true" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 计费设置模块 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/setting_border"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginTop="42dp"
|
||||
android:paddingHorizontal="32dp"
|
||||
android:paddingTop="35dp"
|
||||
android:paddingBottom="42dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -134,6 +214,7 @@
|
||||
android:background="@drawable/setting_border_gray">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDishWeightLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginStart="42dp"
|
||||
|
||||
@@ -11,16 +11,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:background="@drawable/setting_border"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp">
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvChargeSetting"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="35dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:drawablePadding="17dp"
|
||||
android:text="餐品采集"
|
||||
android:textColor="@color/black"
|
||||
@@ -34,7 +32,6 @@
|
||||
android:id="@+id/btnCollectedFood"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
@@ -51,7 +48,7 @@
|
||||
android:id="@+id/flPreview"
|
||||
android:layout_width="456dp"
|
||||
android:layout_height="342dp"
|
||||
android:layout_marginTop="41dp"
|
||||
android:layout_marginTop="20dp"
|
||||
app:layout_constraintStart_toStartOf="@id/tvChargeSetting"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvChargeSetting">
|
||||
|
||||
@@ -114,9 +111,9 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvFoodList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="382dp"
|
||||
android:layout_height="358dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginVertical="10dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -132,7 +129,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
@@ -167,22 +164,22 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="18dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:minHeight="348dp"
|
||||
android:layout_weight="1"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="10"
|
||||
tools:itemCount="11"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
tools:listitem="@layout/list_item_search_food"/>
|
||||
tools:listitem="@layout/list_item_search_food3"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnSave"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="保存"
|
||||
android:background="@drawable/bg_btn_save"
|
||||
android:textColor="@color/white"
|
||||
|
||||
@@ -34,24 +34,41 @@
|
||||
android:textStyle="bold"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInputNumber"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginHorizontal="64dp"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:background="@drawable/setting_border_gray3"
|
||||
android:hint="输入消费码"
|
||||
android:textColorHint="#ffc4cfda"
|
||||
android:textColor="#ff0a1428"
|
||||
android:layout_marginTop="32dp"
|
||||
android:textSize="36sp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text=""
|
||||
android:paddingHorizontal="50dp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||
android:layout_marginTop="32dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInputNumber"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword"
|
||||
android:background="@drawable/setting_border_gray3"
|
||||
android:hint="输入消费码"
|
||||
android:textColorHint="#ffc4cfda"
|
||||
android:textColor="#ff0a1428"
|
||||
android:textSize="36sp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text=""
|
||||
android:paddingHorizontal="50dp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivTogglePassword"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:paddingEnd="16dp"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:src="@drawable/ic_eye_close"
|
||||
android:scaleType="centerInside"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnConfirm"
|
||||
|
||||
@@ -285,7 +285,7 @@
|
||||
<TextView
|
||||
android:id="@+id/meatTv"
|
||||
style="@style/takeFoodNameTv"
|
||||
android:text="蛋肉"
|
||||
android:text="肉蛋豆"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="HardcodedText,UseCompatTextViewDrawableXml">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/llFoodTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="取餐明细"
|
||||
android:textColor="#ff33446a"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
android:drawablePadding="17dp"
|
||||
android:drawableStart="@drawable/ic_take_food" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvUserName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="26sp"
|
||||
tools:text="张三" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="#FFDDE5F0" />
|
||||
|
||||
<include layout="@layout/layout_food_list_header" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="#FFDDE5F0" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvFoodList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:overScrollMode="never"
|
||||
tools:listitem="@layout/layout_food_list_header" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalWeight"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#ff889ac2"
|
||||
android:textSize="26sp"
|
||||
tools:text="总重量:680克"
|
||||
android:drawablePadding="17dp"
|
||||
android:drawableStart="@drawable/ic_flag_weight" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalCalorie"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#ff889ac2"
|
||||
android:textSize="26sp"
|
||||
tools:text="总热量:793千卡"
|
||||
android:drawablePadding="17dp"
|
||||
android:drawableStart="@drawable/ic_flag_calorie" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- 菜品名称列,权重2.5 -->
|
||||
<TextView
|
||||
android:id="@+id/tvFoodName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2.1"
|
||||
android:text="菜品名称"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="26sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center_vertical|start" />
|
||||
|
||||
<!-- 规格列,权重1.0 -->
|
||||
<TextView
|
||||
android:id="@+id/tvPriceNorm"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.6"
|
||||
android:text="规格"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="26sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- 重量列,权重1.0 -->
|
||||
<TextView
|
||||
android:id="@+id/tvWeight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:text="重量(克)"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="26sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- 小计列,权重1.0 -->
|
||||
<TextView
|
||||
android:id="@+id/tvAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:text="小计(元)"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="26sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvToSearch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="38dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:padding="10dp"
|
||||
tools:text="以上都不是,手动搜索"
|
||||
android:textColor="#FFFF3232"
|
||||
android:textSize="31sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvFoodList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/tvToSearch"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginHorizontal="14dp"
|
||||
android:layout_marginTop="42dp"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="8"
|
||||
tools:listitem="@layout/list_item_search_food" />
|
||||
|
||||
<include
|
||||
android:id="@+id/detailInclude"
|
||||
layout="@layout/layout_eat_detail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvFoodList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="264dp"
|
||||
android:layout_marginHorizontal="14dp"
|
||||
android:layout_marginTop="42dp"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:itemCount="8"
|
||||
tools:listitem="@layout/list_item_search_food" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvToSearch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="38dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="10dp"
|
||||
tools:text="以上都不是,手动搜索"
|
||||
android:textColor="#FFFF3232"
|
||||
android:textSize="31sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/detailInclude"
|
||||
layout="@layout/layout_eat_detail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="14dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -2,7 +2,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp">
|
||||
android:layout_height="130dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivPageBack"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="212dp"
|
||||
android:layout_height="159dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:background="@drawable/bg_gray2">
|
||||
|
||||
<ImageView
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- 菜品名称列,权重2.5 -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2.1"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFoodName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="菜品名称"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="25sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/tvLabel"
|
||||
android:gravity="center_vertical|start" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="档口"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="13sp"
|
||||
android:background="@drawable/bg_order_flag"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:paddingVertical="1dp"
|
||||
android:layout_marginHorizontal="2dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/tvFoodName"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- 规格列,权重1.0 -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.6"
|
||||
android:gravity="center">
|
||||
<TextView
|
||||
android:id="@+id/tvPriceNorm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="规格"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="25sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center" />
|
||||
<TextView
|
||||
android:id="@+id/tvPriceFlag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="[大份]"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="15sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 重量列,权重1.0 -->
|
||||
<TextView
|
||||
android:id="@+id/tvWeight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
tools:text="重量(克)"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="25sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- 小计列,权重1.0 -->
|
||||
<TextView
|
||||
android:id="@+id/tvAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
tools:text="小计(元)"
|
||||
android:textColor="#FF889AC2"
|
||||
android:textSize="25sp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -8,8 +8,8 @@
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:layout_marginHorizontal="14dp"
|
||||
android:layout_marginVertical="14dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="16dp"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:textAlignment="center"
|
||||
tools:textColor="@color/black"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user