Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a769eae2a0 | ||
|
|
0241caa5fc | ||
|
|
512e0b5b4f | ||
|
|
a8ade16058 | ||
|
|
ffd00266b8 | ||
|
|
43f5434cbd | ||
|
|
76f60f8ee7 | ||
|
|
28a475ca43 | ||
|
|
16dba32eb7 | ||
|
|
5a7b090c02 | ||
|
|
b6ed47ff87 | ||
|
|
088aabc535 | ||
|
|
061680b32b | ||
|
|
5c3e51626c | ||
|
|
936bf206dc | ||
|
|
8e4dcb9f61 | ||
|
|
8ba4ba6db0 | ||
|
|
37f86e202f | ||
|
|
737baf8486 | ||
|
|
bf6b2547f3 | ||
|
|
6abd2f9b0b | ||
|
|
c19d84a7a6 | ||
|
|
8f1f15dd74 | ||
|
|
416a8366e1 | ||
|
|
03c5aee785 | ||
|
|
e3b01ff789 | ||
|
|
b6c9fe5aed | ||
|
|
45562a9fc1 | ||
|
|
7f455f2d8f | ||
|
|
dde0bbc4d5 | ||
|
|
d378cdcf97 | ||
|
|
e5b682f021 | ||
|
|
0498e124b3 | ||
|
|
30b2b178a9 | ||
|
|
2ba55ef027 | ||
|
|
a45690d3be | ||
|
|
ab0d9b35f5 | ||
|
|
0127697560 | ||
|
|
dbe3753821 | ||
|
|
b16147b1b2 | ||
|
|
d6528d33fc | ||
|
|
5479091959 | ||
|
|
12da153df3 | ||
|
|
3ef93c5f5e | ||
|
|
91800a6bb1 | ||
|
|
f40c2046f1 | ||
|
|
ee9f27bb9c | ||
|
|
5c230e38c3 | ||
|
|
10d1f966da | ||
|
|
57f8920442 | ||
|
|
fb4278d2b7 | ||
|
|
fcea0d785d | ||
|
|
c4a6101a7b | ||
|
|
bf8444e5d5 | ||
|
|
549e8d303f | ||
|
|
b51be58c64 | ||
|
|
a6d2441704 | ||
|
|
e7d11530a6 | ||
|
|
c56769769b | ||
|
|
8673cec545 | ||
|
|
8ec7ea6a79 | ||
|
|
a80e3d1ba6 | ||
|
|
6d825d7235 | ||
|
|
4f1860eb37 | ||
|
|
45a7bf8369 | ||
|
|
f0b50ef82f | ||
|
|
824dc5f867 | ||
|
|
7232735073 | ||
|
|
8a1630f2a4 | ||
|
|
39c0ca7d72 |
+3
-6
@@ -1,15 +1,12 @@
|
||||
*.iml
|
||||
.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,7 +61,8 @@
|
||||
<!-- </activity>-->
|
||||
<!-- <activity android:name="com.sw.dualscreen.activity.CollectedDataActivity" />-->
|
||||
<activity android:name="com.sw.dualscreen.activity.SettingActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:launchMode="singleTask" />
|
||||
<activity android:name="com.sw.dualscreen.activity.CollectedFoodActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
<activity android:name="com.sw.dualscreen.activity.PayActivity"
|
||||
|
||||
@@ -17,11 +17,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,7 +154,7 @@ 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
|
||||
@@ -166,10 +167,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
/**
|
||||
* 处理扫描枪数据
|
||||
*/
|
||||
open fun scanGunKeyEventCallback(scanInfo: String) {
|
||||
Timber.tag("TAG").d("handleScanKeyInfo: $scanInfo")
|
||||
fun scanGunKeyEventCallback(scanInfo: String){
|
||||
if (this is PayActivity) {
|
||||
qrCodePay(scanInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (keyEventHelper != null) {
|
||||
if (keyEventHelper!!.isScanGunEvent(event)) {
|
||||
@@ -179,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
|
||||
@@ -15,10 +16,11 @@ 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.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.dualscreen.viewmodel.NetViewModelV2
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
@@ -41,7 +43,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
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 {
|
||||
@@ -63,6 +65,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
|
||||
var foodOrderId: String = ""
|
||||
var eatNum: Int = 1
|
||||
var isMember: Boolean = false
|
||||
|
||||
val presentation by lazy {
|
||||
PayPresentation(
|
||||
@@ -81,7 +84,8 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
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)
|
||||
@@ -95,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
|
||||
}
|
||||
@@ -156,10 +162,12 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
presentation?.dismiss()
|
||||
presentation.dismiss()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
var memberDiscount = 1.0
|
||||
|
||||
var memberInfo: MemberInfo? = null
|
||||
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
|
||||
this.memberInfo = memberInfo
|
||||
@@ -171,36 +179,50 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
// display = displays[1],
|
||||
// type = type
|
||||
// ) {
|
||||
// scanQrCodePayPresentation?.dismiss()
|
||||
// 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()
|
||||
// scanQrCodePaypresentation.show()
|
||||
// }
|
||||
presentation.let {
|
||||
it.pageType = PayPresentation.QR_CODE_PAY
|
||||
it.type = type
|
||||
it.foodName = foodInfo?.foodName
|
||||
//确认使用vipPrice 还是 specPrice
|
||||
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||
it.totalPrice = (onePrice ?: 0.0) * eatNum
|
||||
presentation.initView()
|
||||
//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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateQrCodeImage(qrCodeUrl: String?) {
|
||||
// scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl)
|
||||
presentation?.loadQrCodeImage(qrCodeUrl)
|
||||
// scanQrCodePaypresentation.loadQrCodeImage(qrCodeUrl)
|
||||
presentation.loadQrCodeImage(qrCodeUrl)
|
||||
}
|
||||
|
||||
private var debouncer = Debouncer(5000)
|
||||
|
||||
fun showPaySuccess(isVip: Boolean) {
|
||||
playSoundOnPaySuccess()
|
||||
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(
|
||||
@@ -232,7 +254,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun memberPay(param: HashMap<String, String>, block: () -> Unit) {
|
||||
fun memberPay(param: HashMap<String, String?>, block: () -> Unit) {
|
||||
userViewModel.memberPay(param) {
|
||||
runOnUiThread {
|
||||
if (it.not()) {
|
||||
@@ -277,21 +299,29 @@ 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) } }
|
||||
)
|
||||
}
|
||||
|
||||
override fun scanGunKeyEventCallback(scanInfo: String) {
|
||||
super.scanGunKeyEventCallback(scanInfo)
|
||||
userViewModel.qrCodePay(scanInfo, foodOrderId) { payState, backData ->
|
||||
fun qrCodePay(scanInfo: String) {
|
||||
showWaitingDialog("支付中……")
|
||||
userViewModel.qrCodePay(scanInfo, foodOrderId, memberInfo?.id) { payState, backData ->
|
||||
Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData")
|
||||
hideWaitingDialog()
|
||||
if (payState) {
|
||||
showPaySuccess(false)
|
||||
showPaySuccess(isMember)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,4 +370,12 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun addBackEventListener() {
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,12 @@ import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.ClickBackEvent
|
||||
import com.sw.dualscreen.model.response.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
|
||||
|
||||
@@ -23,7 +23,9 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
||||
// }
|
||||
payActivity.foodInfo?.let {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
// payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
val price = it.specPrice ?: 0.0
|
||||
payAmount = price * payActivity.memberDiscount * payActivity.eatNum
|
||||
}
|
||||
showSubScreen()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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
|
||||
@@ -13,6 +15,8 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
private lateinit var payActivity: PayActivity
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
// 密码是否显示的标志
|
||||
private var isPasswordVisible = false
|
||||
override fun inflateViewBinding(): FragmentNumberPayBinding {
|
||||
return FragmentNumberPayBinding.inflate(layoutInflater)
|
||||
}
|
||||
@@ -27,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()
|
||||
@@ -41,7 +51,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
// ToastUtils.showToast("会员验证成功")
|
||||
// }
|
||||
// }
|
||||
|
||||
payActivity.isMember = false
|
||||
payActivity.getMemberInfoByPhone(phone, key) { memberInfo ->
|
||||
if (memberInfo == null) {
|
||||
binding.root.postDelayed({
|
||||
@@ -52,6 +62,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
}
|
||||
//肯定是会员
|
||||
memberInfo.member = true
|
||||
payActivity.isMember = true
|
||||
//绑定订单使用faceUserId
|
||||
payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
|
||||
if (bindResult.not()) {
|
||||
@@ -126,6 +137,30 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
// 切换密码显示/隐藏
|
||||
private fun togglePasswordVisibility() {
|
||||
val editText = binding.etInputNumber
|
||||
val imageView = binding.ivTogglePassword
|
||||
|
||||
// 保存当前光标位置
|
||||
val cursorPosition = editText.selectionStart
|
||||
|
||||
if (isPasswordVisible) {
|
||||
// 当前显示,改为隐藏
|
||||
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
imageView.setImageResource(R.drawable.ic_eye_close)
|
||||
isPasswordVisible = false
|
||||
} else {
|
||||
// 当前隐藏,改为显示
|
||||
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
imageView.setImageResource(R.drawable.ic_eye_open)
|
||||
isPasswordVisible = true
|
||||
}
|
||||
|
||||
// 恢复光标位置
|
||||
editText.setSelection(cursorPosition)
|
||||
}
|
||||
|
||||
// private fun bindOrder(userId: String, block: () -> Unit) {
|
||||
// payActivity.bindOrder(userId) { bindResult ->
|
||||
// if (bindResult.not()) {
|
||||
|
||||
@@ -75,7 +75,9 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
}
|
||||
|
||||
if (memberInfo != null && memberInfo!!.member) {
|
||||
totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
// totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
val price = payActivity.foodInfo?.specPrice ?: 0.0
|
||||
totalPrice = price * payActivity.memberDiscount * payActivity.eatNum
|
||||
binding.layoutVip.visible()
|
||||
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
|
||||
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
|
||||
@@ -289,7 +291,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
//订单号
|
||||
"orderNo" to payActivity.foodOrderId,
|
||||
//用户id
|
||||
"memberId" to memberInfo!!.id!!
|
||||
"memberId" to memberInfo?.id
|
||||
),
|
||||
block = callback
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -96,12 +100,14 @@ data class UserNutrition(
|
||||
val fruitsVegetablesNearExcess: String? = null,
|
||||
//果蔬超量
|
||||
val fruitsVegetablesExcess: String? = null,
|
||||
//肉蛋推荐值
|
||||
val meatEggsRecommend: String? = null,
|
||||
//肉蛋即将超量
|
||||
val meatEggsNearExcess: String? = null,
|
||||
//肉蛋超量
|
||||
val meatEggsExcess: String? = null,
|
||||
//肉蛋豆推荐值
|
||||
val meatEggsBeansRecommend: String? = null,
|
||||
//肉蛋豆即将超量
|
||||
val meatEggsBeansNearExcess: String? = null,
|
||||
//肉蛋豆超量
|
||||
val meatEggsBeansExcess: String? = null,
|
||||
//总重量
|
||||
val eatWeightSum:Double? = null,
|
||||
)
|
||||
|
||||
//{"code":"00000","data":"{\"orderType\":0,\"orderPayFrom\":2,\"payType\":\"1\",\"paySuc\":0,\"totalFee\":0.01,\"orderCode\":\"1998193614899372032\",\"message\":\"支付成功\",\"payCode\":\"1998193614899372032508227\"}","msg":"成功","total":0}
|
||||
@@ -123,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 = ""
|
||||
)
|
||||
@@ -131,6 +137,9 @@ data class PaySuccessEvent(
|
||||
data class ChargeModeEvent(
|
||||
var chargeMode: Int = 0
|
||||
)
|
||||
data class ResetRecognizeEvent(
|
||||
var state: Int = 0
|
||||
)
|
||||
|
||||
data class ClickBackEvent(
|
||||
var name: String = ""
|
||||
@@ -139,7 +148,9 @@ data class ClickBackEvent(
|
||||
data class DeviceConfig(
|
||||
var arcsoftAppId: String? = null,
|
||||
var arcsoftSdkKey: String? = null,
|
||||
var arcsoftActiveKey: String? = null
|
||||
var arcsoftActiveKey: String? = null,
|
||||
//结算模式:1-独立支付模式,2-联合结算模式
|
||||
var payType: Int = 1
|
||||
)
|
||||
|
||||
data class FoodVector(
|
||||
@@ -159,3 +170,32 @@ data class WeightRecord(
|
||||
var lastWeight: Int = 0,
|
||||
var currentWeight: Int = 0
|
||||
)
|
||||
|
||||
data class FoodItem(
|
||||
var id: String? = null,
|
||||
var foodId: String? = null,
|
||||
var foodName: String? = null,
|
||||
var foodMaterialId: String? = null,
|
||||
var specId: String? = null,
|
||||
var num: Int = 0,
|
||||
var eatNum: Int = 0,
|
||||
var price: Double = 0.0,
|
||||
var foodPrice: Double = 0.0,
|
||||
var discount: Double = 0.0,
|
||||
var income: Double = 0.0,
|
||||
var foodDifferenceId: Long = 0,
|
||||
var createTime: String? = null,
|
||||
var specName: String? = null,
|
||||
var specWeight: Int = 0,
|
||||
var orderFrom: Int = 0,
|
||||
var isLocalData: Boolean = false
|
||||
)
|
||||
|
||||
data class FoodOrderModel(
|
||||
val calorie: Double = 0.0,
|
||||
val incomeSum: Double = 0.0,
|
||||
val discountSum: Double = 0.0,
|
||||
val eatWeightSum: Int = 0,
|
||||
val orderNo: String? = null,
|
||||
val list: List<FoodItem>? = null
|
||||
)
|
||||
@@ -43,6 +43,8 @@ data class FoodInfo(
|
||||
val recommendCalorie: Double? = null,
|
||||
//图片
|
||||
var foodImg: String? = "",
|
||||
//按单烹制 ID(菜品列表返回,就餐时原样回传,用于溯源码追踪)
|
||||
var cookOrderId: String? = "",
|
||||
|
||||
//-----------------------------------
|
||||
var score: Int = 0,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sw.dualscreen.model.response.v2
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* 新系统采集菜品信息
|
||||
* 对应接口:/neglect/booth/collect/page
|
||||
*/
|
||||
@Parcelize
|
||||
data class CollectedFoodV2(
|
||||
val foodId: String?,
|
||||
val foodName: String?,
|
||||
val version: String?,
|
||||
val foodVector: String?,
|
||||
val picUrls: List<String>?,
|
||||
/** 已采集数量(客户端手动赋值,非接口字段,用于兼容旧版 UI) */
|
||||
var foodCount: Int = 0
|
||||
) : Parcelable
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.sw.dualscreen.model.response.v2
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* 新系统人脸数据 VO
|
||||
* 对应接口:/nutrition/neglect/common/face/page 和 /nutrition/neglect/common/face/increment
|
||||
*/
|
||||
@Parcelize
|
||||
data class FaceVO(
|
||||
val userFaceId: String?,
|
||||
val userId: String?,
|
||||
val faceFeature: String?,
|
||||
/** 后端返回 String 类型(防止 JS 大数精度丢失),映射时自行转为 Long */
|
||||
val faceUpdateTimestamp: String?,
|
||||
val cardNo: String?,
|
||||
val member: Boolean?,
|
||||
val faceDeleted: Boolean?,
|
||||
/** 人员类型:由后端定义,如 "1"-会员、"2"-临时用户、"3"-其他等 */
|
||||
val personType: String?
|
||||
) : Parcelable
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.sw.dualscreen.model.response.v2
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* 新系统菜品信息
|
||||
* 对应接口:/neglect/booth/food/by-names
|
||||
*/
|
||||
@Parcelize
|
||||
data class NewFoodInfo(
|
||||
val foodId: Long,
|
||||
val foodName: String?,
|
||||
val calorie: BigDecimal?,
|
||||
val protein: BigDecimal?,
|
||||
val fat: BigDecimal?,
|
||||
val carbohydrate: BigDecimal?,
|
||||
val price: BigDecimal?,
|
||||
val specPrice: BigDecimal?,
|
||||
val specWeight: BigDecimal?,
|
||||
val stapleFood: BigDecimal?,
|
||||
val fruitsVegetables: BigDecimal?,
|
||||
val meatEggs: BigDecimal?,
|
||||
val foodMaterialId: Long?,
|
||||
val specId: Long?,
|
||||
val foodImg: String?,
|
||||
val foodLabel: String?,
|
||||
val vipPrice: BigDecimal? = null,
|
||||
val recommendCalorie: Int = 0,
|
||||
val tablewareStatus: Boolean = false,
|
||||
val tablewareWeight: Int = 0,
|
||||
//按单烹制 ID(菜品列表返回,就餐时原样回传,用于溯源码追踪)
|
||||
var cookOrderId: String? = "",
|
||||
) : Parcelable
|
||||
|
||||
/** NewFoodInfo → FoodInfo 映射(V2 数据模型兼容旧版 UI) */
|
||||
fun NewFoodInfo.toFoodInfo() = FoodInfo(
|
||||
foodId = foodId.toString(),
|
||||
foodName = foodName,
|
||||
calorie = calorie?.toDouble(),
|
||||
protein = protein?.toDouble(),
|
||||
fat = fat?.toDouble(),
|
||||
carbohydrate = carbohydrate?.toDouble(),
|
||||
specPrice = specPrice?.toDouble(),
|
||||
vipPrice = vipPrice?.toDouble(),
|
||||
foodMaterialId = foodMaterialId?.toString(),
|
||||
specId = specId?.toString(),
|
||||
specWeight = specWeight?.toDouble(),
|
||||
stapleFood = stapleFood?.toDouble(),
|
||||
fruitsVegetables = fruitsVegetables?.toDouble(),
|
||||
meatEggs = meatEggs?.toDouble(),
|
||||
recommendCalorie = recommendCalorie.toDouble(),
|
||||
foodImg = foodImg,
|
||||
cookOrderId = cookOrderId,
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.sw.dualscreen.model.response.v2
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* 新系统会员信息
|
||||
* 对应接口:/neglect/booth/member/info 和 /neglect/booth/member/info-by-phone
|
||||
*/
|
||||
@Parcelize
|
||||
data class NewMemberInfo(
|
||||
val id: String?,
|
||||
val phone: String?,
|
||||
val name: String?,
|
||||
val faceUrl: String?,
|
||||
val topUpBalance: BigDecimal?,
|
||||
val rewardBalance: BigDecimal? = BigDecimal.ZERO,
|
||||
val integralBalance: Int?,
|
||||
val member: Boolean?
|
||||
) : Parcelable
|
||||
|
||||
/** NewMemberInfo → MemberInfo 映射(V2 数据模型兼容旧版 UI,faceUserId 复用 id) */
|
||||
fun NewMemberInfo.toMemberInfo() = MemberInfo(
|
||||
id = id,
|
||||
faceUserId = id,
|
||||
phone = phone,
|
||||
name = name,
|
||||
faceUrl = faceUrl,
|
||||
topUpBalance = topUpBalance?.toDouble(),
|
||||
rewardBalance = rewardBalance?.toDouble(),
|
||||
integralBalance = integralBalance,
|
||||
member = member ?: false
|
||||
)
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.sw.dualscreen.model.response.v2
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.sw.dualscreen.model.response.FoodItem
|
||||
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* 新系统结算订单数据
|
||||
* 对应接口:/neglect/booth/order/settlement
|
||||
*/
|
||||
@Parcelize
|
||||
data class SettlementOrder(
|
||||
val calorie: BigDecimal?,
|
||||
val incomeSum: BigDecimal?,
|
||||
val discountSum: BigDecimal?,
|
||||
val eatWeightSum: Int?,
|
||||
val orderNo: String?,
|
||||
val list: List<SettlementFoodItem>?
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class SettlementFoodItem(
|
||||
val foodId: String?,
|
||||
val foodName: String?,
|
||||
val specId: String?,
|
||||
val specName: String?,
|
||||
val specWeight: BigDecimal?,
|
||||
val eatNum: Int?,
|
||||
val eatWeight: Int?,
|
||||
val price: BigDecimal?
|
||||
) : Parcelable
|
||||
|
||||
/** SettlementOrder → FoodOrderModel 映射(V2 数据模型兼容旧版 UI) */
|
||||
fun SettlementOrder.toFoodOrderModel() = FoodOrderModel(
|
||||
calorie = calorie?.toDouble() ?: 0.0,
|
||||
incomeSum = incomeSum?.toDouble() ?: 0.0,
|
||||
discountSum = discountSum?.toDouble() ?: 0.0,
|
||||
eatWeightSum = eatWeightSum ?: 0,
|
||||
orderNo = orderNo,
|
||||
list = list?.map { it.toFoodItem() }
|
||||
)
|
||||
|
||||
private fun SettlementFoodItem.toFoodItem() = FoodItem(
|
||||
foodId = foodId,
|
||||
foodName = foodName,
|
||||
specId = specId,
|
||||
specName = specName,
|
||||
specWeight = specWeight?.toInt() ?: 0,
|
||||
eatNum = eatNum ?: 0,
|
||||
price = price?.toDouble() ?: 0.0,
|
||||
// 重量栏读取 num 字段,这里用实际食用重量 eatWeight 赋值
|
||||
num = eatWeight ?: 0
|
||||
)
|
||||
@@ -1,8 +1,15 @@
|
||||
package com.sw.dualscreen.network
|
||||
|
||||
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?>
|
||||
|
||||
/**
|
||||
@@ -220,12 +221,10 @@ interface ApiService {
|
||||
* 二维码支付
|
||||
* @param orderType 0付款 1会员充值
|
||||
*/
|
||||
@GET
|
||||
@POST
|
||||
suspend fun qrCodePay(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay",
|
||||
@Query("authCode") authCode: String,
|
||||
@Query("outTradeNo") orderNo: String,
|
||||
@Query("orderType") orderType:Int
|
||||
@Body param: HashMap<String, String?>
|
||||
): ApiResponse<String?>
|
||||
|
||||
/**
|
||||
@@ -313,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)
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.sw.dualscreen.presentation.pay
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.app.Presentation
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Point
|
||||
@@ -11,24 +10,20 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.TextUtils
|
||||
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 android.widget.TextView
|
||||
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.GlobalKey
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.databinding.PresentationFacePayBinding
|
||||
import com.sw.dualscreen.databinding.PresentationPayBinding
|
||||
import com.sw.dualscreen.ext.dp
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
@@ -38,10 +33,8 @@ import com.sw.dualscreen.ext.load
|
||||
import com.sw.dualscreen.ext.visible
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.TextBean
|
||||
import com.sw.dualscreen.utils.SPUtil
|
||||
import com.sw.dualscreen.utils.SpannedUtils
|
||||
import com.sw.dualscreen.view.CustomDialog
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
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
|
||||
@@ -58,11 +51,12 @@ import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class PayPresentation(
|
||||
val activity: PayActivity,
|
||||
display: Display,
|
||||
val userViewModel: UserViewModel,
|
||||
val userViewModel: NetViewModelV2,
|
||||
val recognizeViewModel: RecognizeViewModel,
|
||||
private val onDismissListener: () -> Unit = {}
|
||||
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
||||
@@ -299,13 +293,16 @@ class PayPresentation(
|
||||
.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"
|
||||
if (userId == null) return@Observer
|
||||
activity.isMember = isMember
|
||||
if (userId.isNullOrBlank()) return@Observer
|
||||
// TODO: 测试支付用户id
|
||||
// userId = "1987710988425662466"
|
||||
isExistNotPayOrder.set(true)
|
||||
faceRecSuccess(userId, isMember)
|
||||
})
|
||||
|
||||
@@ -314,6 +311,7 @@ class PayPresentation(
|
||||
})
|
||||
}
|
||||
|
||||
private var isExistNotPayOrder = AtomicBoolean(false)
|
||||
|
||||
private fun initArcView() {
|
||||
//在布局结束后才做初始化操作
|
||||
@@ -605,6 +603,7 @@ class PayPresentation(
|
||||
//ToastUtils.showToast("订单绑定失败")
|
||||
return@bindOrder
|
||||
}
|
||||
|
||||
binding.root.postDelayed({
|
||||
activity.hideWaitingDialog()
|
||||
// TODO: 测试 memberInfo.member是否返回正常数据---------------
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.sw.dualscreen.model.response.FaceData
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.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"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,9 +193,20 @@ class RemoteRepository constructor(
|
||||
/**
|
||||
* 二维码支付
|
||||
*/
|
||||
suspend fun qrCodePay(authCode: String,orderNo: String): ApiResponse<String?> {
|
||||
suspend fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?
|
||||
): ApiResponse<String?> {
|
||||
return safeApiCall {
|
||||
apiService.qrCodePay(authCode = authCode, orderNo=orderNo, orderType = 0)
|
||||
apiService.qrCodePay(
|
||||
param = hashMapOf(
|
||||
"authCode" to authCode,
|
||||
"outTradeNo" to orderNo,
|
||||
"memberId" to memberId,
|
||||
"orderType" to "0"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +271,7 @@ class RemoteRepository constructor(
|
||||
/**
|
||||
* 会员支付
|
||||
*/
|
||||
suspend fun memberPay(param: HashMap<String, String>): ApiResponse<Any?> {
|
||||
suspend fun memberPay(param: HashMap<String, String?>): ApiResponse<Any?> {
|
||||
return safeApiCall {
|
||||
apiService.memberPay(param = param)
|
||||
}
|
||||
@@ -285,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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,12 +343,26 @@ class RemoteRepository constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteCollectFood(foodId:String?, version: String?): ApiResponse<Any?> {
|
||||
suspend fun deleteCollectFood(foodId: String?, version: String?): ApiResponse<Any?> {
|
||||
return safeApiCall {
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sw.dualscreen.utils
|
||||
|
||||
import com.sw.dualscreen.GlobalKey
|
||||
import com.sw.dualscreen.MyApp
|
||||
import com.sw.dualscreen.utils.SPUtil.Companion.getInstance
|
||||
|
||||
@@ -16,15 +17,26 @@ object SpTool {
|
||||
|
||||
|
||||
var lastFaceTimestamp: Long
|
||||
get() = spUtil?.get(LAST_FACE_TIMESTAMP, 0L)?:0L
|
||||
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
|
||||
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
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
package com.sw.dualscreen.viewmodel
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.arcsoft.face.ErrorInfo
|
||||
import com.sw.dualscreen.GlobalData
|
||||
import com.sw.dualscreen.model.request.UserNutritionParam
|
||||
import com.sw.dualscreen.model.request.v2.BindUserOrderRequest
|
||||
import com.sw.dualscreen.model.request.v2.PlaceOrderRequest
|
||||
import com.sw.dualscreen.model.response.ApiResponse
|
||||
import com.sw.dualscreen.model.response.DinnerType
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.UserNutrition
|
||||
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||
import com.sw.dualscreen.model.response.v2.FaceVO
|
||||
import com.sw.dualscreen.model.response.v2.NewFoodInfo
|
||||
import com.sw.dualscreen.model.response.v2.NewMemberInfo
|
||||
import com.sw.dualscreen.model.response.v2.SettlementOrder
|
||||
import com.sw.dualscreen.model.response.v2.toFoodInfo
|
||||
import com.sw.dualscreen.model.response.v2.toFoodOrderModel
|
||||
import com.sw.dualscreen.model.response.v2.toMemberInfo
|
||||
import com.sw.dualscreen.network.ApiClient
|
||||
import com.sw.dualscreen.utils.FileUtil
|
||||
import com.sw.dualscreen.utils.SpTool
|
||||
import com.sw.plate.App
|
||||
import com.sw.plate.utils.Base64
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import com.sw.plate.utils.arcface.FaceApi
|
||||
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
class NetViewModelV2 : BaseViewModel() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "NetViewModelV2"
|
||||
const val PAGE_SIZE = 100L
|
||||
}
|
||||
|
||||
private val faceApi: FaceApi = FaceApi()
|
||||
|
||||
/** 人脸加载完成 */
|
||||
private val _loadFaceResult = MutableStateFlow<Boolean>(false)
|
||||
val loadFaceResult: StateFlow<Boolean> = _loadFaceResult
|
||||
|
||||
/** 饭点类型 早餐/午餐/晚餐 */
|
||||
private val _dinnerTypeInfo = MutableStateFlow<DinnerType?>(null)
|
||||
val dinnerTypeInfo: StateFlow<DinnerType?> = _dinnerTypeInfo
|
||||
|
||||
fun getDeviceConfig(
|
||||
onSuccess: (config: com.sw.dualscreen.model.response.DeviceConfig?) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
) {
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getDeviceConfig()
|
||||
if (parseResponse(response)) {
|
||||
onSuccess(response.data)
|
||||
} else {
|
||||
onFailure(response.msg ?: "获取设备配置失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取设备配置异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取人脸全量数据(递归分页 + FaceApi 集成) */
|
||||
fun getFacePage(
|
||||
pageNum: Long = 1L,
|
||||
pageSize: Long = PAGE_SIZE,
|
||||
onSuccess: () -> Unit = {},
|
||||
onFailure: (String) -> Unit = {}
|
||||
) {
|
||||
var currentPageNum = pageNum
|
||||
Timber.tag(TAG).d("getFacePage index = $currentPageNum")
|
||||
launch {
|
||||
_loadFaceResult.value = false
|
||||
// 重置持久化时间戳和内存累加器
|
||||
SpTool.lastFaceTimestamp = 0
|
||||
lastFaceTimestamp = 0L
|
||||
FileUtil.saveLog("获取人脸全量数据开始,重置时间戳为0")
|
||||
val response = ApiClient.repositoryV2.getFacePage(currentPageNum, pageSize)
|
||||
if (!parseResponse(response)) {
|
||||
onFailure("获取人脸数据失败,${response.msg}(${response.code})")
|
||||
return@launch
|
||||
}
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = response.data ?: emptyList()
|
||||
if (pageNum == 1L && list.isEmpty()) {
|
||||
onFailure("查询人脸数据为空")
|
||||
return@withContext
|
||||
}
|
||||
val faceEntity = list.map { vo ->
|
||||
FaceEntity(
|
||||
vo.userId, // userName
|
||||
Base64.decode(vo.faceFeature), // featureData
|
||||
vo.personType ?: if (vo.member == true) "1" else "2", // userType
|
||||
vo.cardNo ?: "", // cardNo
|
||||
vo.userId ?: "", // userId
|
||||
vo.userFaceId ?: "", // userFaceId
|
||||
vo.member ?: false, // member
|
||||
vo.faceUpdateTimestamp?.toLongOrNull() ?: 0L // faceUpdateTimestamp
|
||||
)
|
||||
}
|
||||
faceApi.updateFaceData(currentPageNum.toInt(), faceEntity)
|
||||
// 取当前页最大时间戳(后端返回 String 类型,需转为 Long 比较),跨页累加防止中间页有更大值被覆盖
|
||||
val pageMaxTimestamp = list.maxOf { it.faceUpdateTimestamp?.toLongOrNull() ?: 0L }
|
||||
lastFaceTimestamp = maxOf(lastFaceTimestamp, pageMaxTimestamp)
|
||||
if (list.size >= pageSize) {
|
||||
currentPageNum++
|
||||
getFacePage(currentPageNum, pageSize, onSuccess, onFailure)
|
||||
return@withContext
|
||||
}
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||
_loadFaceResult.value = true
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取人脸增量数据(FaceApi 集成) */
|
||||
fun getFaceIncrement(
|
||||
pageNum: Long = 1L,
|
||||
pageSize: Long = PAGE_SIZE,
|
||||
timestamp: Long,
|
||||
onAllQueryFinished: () -> Unit,
|
||||
onPageQueryFinished: (List<FaceVO>) -> Unit,
|
||||
onFailure: (String) -> Unit = {}
|
||||
) {
|
||||
Timber.tag(TAG).d("getFaceIncrement timestamp=$timestamp")
|
||||
launch {
|
||||
val response = ApiClient.repositoryV2.getFaceIncrement(pageNum, pageSize, timestamp)
|
||||
if (!parseResponse(response)) {
|
||||
onFailure(response.msg ?: "获取增量人脸数据失败")
|
||||
return@launch
|
||||
}
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = response.data ?: emptyList()
|
||||
if (pageNum == 1L && list.isEmpty()) {
|
||||
return@withContext
|
||||
}
|
||||
onPageQueryFinished(list)
|
||||
// 取列表中最大时间戳(后端返回 String 类型,需转为 Long),而非依赖列表最后一条
|
||||
lastFaceTimestamp = list.maxOf { it.faceUpdateTimestamp?.toLongOrNull() ?: 0L }
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||
onAllQueryFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getFoodByNames(
|
||||
names: String,
|
||||
onSuccess: (list: List<NewFoodInfo>) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
) {
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getFoodByNames(names.split(","))
|
||||
if (parseResponse(response)) {
|
||||
onSuccess(response.data ?: emptyList())
|
||||
} else {
|
||||
onFailure(response.msg ?: "查询菜品失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "查询菜品异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun placeOrder(
|
||||
orderRequest: PlaceOrderRequest,
|
||||
onSuccess: (orderNo: String?) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
) {
|
||||
launchWithLoading {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.placeOrder(orderRequest)
|
||||
if (parseResponse(response)) {
|
||||
onSuccess(response.data)
|
||||
} else {
|
||||
onFailure(response.msg ?: "下单失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "开餐下单异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bindUserOrder(
|
||||
userId: Long,
|
||||
orderNo: String,
|
||||
mode: Int? = null,
|
||||
onSuccess: () -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
) {
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.bindUserOrder(userId, orderNo, mode)
|
||||
if (parseResponse(response)) {
|
||||
onSuccess()
|
||||
} else {
|
||||
onFailure(response.msg ?: "绑定订单失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "绑定订单异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCollectPage(
|
||||
pageNum: Long = 1L,
|
||||
pageSize: Long = PAGE_SIZE,
|
||||
foodName: String? = null,
|
||||
onSuccess: (list: List<CollectedFoodV2>) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
) {
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getCollectPage(pageNum, pageSize, foodName)
|
||||
if (parseResponse(response)) {
|
||||
onSuccess(response.data ?: emptyList())
|
||||
} else {
|
||||
onFailure(response.msg ?: "获取采集列表失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取采集列表异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCollectVectorPage(
|
||||
pageNum: Long = 1L,
|
||||
pageSize: Long = PAGE_SIZE,
|
||||
onSuccess: (list: List<CollectedFoodV2>) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
) {
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getCollectVectorPage(pageNum, pageSize)
|
||||
if (parseResponse(response)) {
|
||||
onSuccess(response.data ?: emptyList())
|
||||
} else {
|
||||
onFailure(response.msg ?: "获取采集列表失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取采集列表异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 上传采集图片(suspend,直接返回 picUrls 列表) */
|
||||
suspend fun uploadCollect(
|
||||
foodId: Long,
|
||||
foodName: String,
|
||||
version: String,
|
||||
foodVector: String,
|
||||
fileList: List<File>
|
||||
): List<String>? {
|
||||
Timber.tag(TAG).d("uploadCollect foodId=$foodId, foodName=$foodName")
|
||||
val fileListNotNull = fileList.filter { it.exists() }
|
||||
if (fileListNotNull.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
val response = ApiClient.repositoryV2.uploadCollect(
|
||||
foodId, foodName, version, foodVector, fileListNotNull
|
||||
)
|
||||
if (!parseResponse(response)) {
|
||||
return null
|
||||
}
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 删除采集菜品 */
|
||||
fun deleteCollect(
|
||||
foodId: String?,
|
||||
version: String?,
|
||||
block: (Boolean) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("deleteCollect foodId=$foodId, version=$version")
|
||||
val id = foodId?.toLongOrNull() ?: run {
|
||||
block(false)
|
||||
return
|
||||
}
|
||||
launchWithLoading {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.deleteCollect(id, version ?: "")
|
||||
if (parseResponse(response)) {
|
||||
block(true)
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "删除采集菜品异常")
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 以下方法暂用 V1 API 实现(待 V2 接口补充) ==========
|
||||
|
||||
/** 激活虹软人脸识别引擎 */
|
||||
fun activeEngine() {
|
||||
Timber.tag(TAG).d("activeEngine")
|
||||
faceApi.activeEngine(
|
||||
App.getContext(),
|
||||
GlobalData.appId,
|
||||
GlobalData.sdkKey,
|
||||
GlobalData.activeKey,
|
||||
object : FaceApi.ActiveCallback {
|
||||
override fun onSuccess(activeCode: Int) {
|
||||
Timber.tag(TAG).d("activeEngine activeCode = $activeCode")
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
when (activeCode) {
|
||||
ErrorInfo.MOK -> {
|
||||
ToastUtils.showToast("激活引擎成功")
|
||||
}
|
||||
ErrorInfo.MERR_ASF_ALREADY_ACTIVATED -> {
|
||||
// 引擎已激活,无需再次激活
|
||||
}
|
||||
else -> {
|
||||
ToastUtils.showToast("激活引擎失败($activeCode)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFail(e: Exception?) {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
ToastUtils.showToast("激活引擎异常,${e?.message}")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取支付二维码 */
|
||||
fun getQrCodeImg(
|
||||
orderId: String,
|
||||
userId: String? = null,
|
||||
totalFee: String? = null,
|
||||
block: (String?) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("getQrCodeImg orderId = $orderId, userId = $userId")
|
||||
launch {
|
||||
val response = repository.getQrCodeImg(
|
||||
orderNo = orderId, memberId = userId, totalFee = totalFee
|
||||
)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 扫码支付 */
|
||||
fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?,
|
||||
block: (Boolean, String?) -> Unit
|
||||
) {
|
||||
launch {
|
||||
val response = repository.qrCodePay(authCode, orderNo, memberId)
|
||||
if (parseResponse(response)) {
|
||||
block(true, response.data)
|
||||
} else {
|
||||
block(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 现金支付 */
|
||||
fun cashPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("cashPay")
|
||||
launchWithLoading {
|
||||
val response = repository.cashPay(param)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data ?: false)
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 会员支付 */
|
||||
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("memberPay")
|
||||
launchWithLoading {
|
||||
val response = repository.memberPay(param)
|
||||
if (parseResponse(response)) {
|
||||
block(true)
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询订单支付状态 */
|
||||
suspend fun queryOrderState(orderId: String, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("queryOrderState")
|
||||
val response = repository.queryOrderState(orderNo = orderId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data == "1")
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取饭点类型 */
|
||||
fun getDinnerType(
|
||||
onSuccess: (DinnerType?) -> Unit = {},
|
||||
onFailure: (message: String) -> Unit = {}
|
||||
) {
|
||||
_dinnerTypeInfo.value = null
|
||||
launch {
|
||||
try {
|
||||
val response = repository.getDinnerType()
|
||||
if (parseResponse(response)) {
|
||||
_dinnerTypeInfo.value = response.data
|
||||
onSuccess(response.data)
|
||||
} else {
|
||||
onFailure(response.msg ?: "获取饭点类型失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取饭点类型异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== V1 兼容包装方法(暂用 V1 API,后续逐步迁移至 V2) ==========
|
||||
|
||||
private var lastFaceTimestamp = 0L
|
||||
|
||||
/** 搜索食物 */
|
||||
fun searchByFoodName(foodName: String, action: (List<FoodInfo>) -> Unit = {}) {
|
||||
Timber.tag(TAG).d("searchFood foodName = $foodName")
|
||||
launchWithLoading {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.searchFood(name = foodName)
|
||||
if (parseResponse(response)) {
|
||||
val list = (response.data ?: emptyList()).map { it.toFoodInfo() }
|
||||
action(list)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "搜索菜品异常")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取用户就餐营养数据 */
|
||||
fun getUserNutritionData(
|
||||
userId: String,
|
||||
block: (UserNutrition?) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("getUserNutritionData userId = $userId")
|
||||
val uid = userId.toLongOrNull() ?: run {
|
||||
block(null)
|
||||
return
|
||||
}
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getUserCurrentFood(uid)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取就餐营养数据异常")
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取订单列表 */
|
||||
fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) {
|
||||
Timber.tag(TAG).d("getFoodOrderList userId = $userId")
|
||||
val uid = userId.toLongOrNull() ?: run {
|
||||
block(null)
|
||||
return
|
||||
}
|
||||
launchWithLoading {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getSettlementOrders(uid)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data?.toFoodOrderModel())
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "查询订单列表异常")
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据 ID 查询会员信息 */
|
||||
fun getMemberInfoById(memberId: String, block: (MemberInfo?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberInfo memberId=$memberId")
|
||||
val uid = memberId.toLongOrNull() ?: run {
|
||||
block(null)
|
||||
return
|
||||
}
|
||||
launchWithLoading {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getMemberInfo(uid)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data?.toMemberInfo())
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "查询会员信息异常")
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据手机号查询会员信息 */
|
||||
fun getMemberInfoByPhone(phone: String, key: String, block: (MemberInfo?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberInfoByPhone phone=$phone")
|
||||
launchWithLoading {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getMemberInfoByPhone(phone, key)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data?.toMemberInfo())
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "查询会员异常")
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取会员折扣 */
|
||||
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberDiscount userId = $userId")
|
||||
val uid = userId.toLongOrNull() ?: run {
|
||||
block(null)
|
||||
return
|
||||
}
|
||||
launchWithLoading {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getMemberDiscount(uid)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data?.toDoubleOrNull())
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "查询会员折扣异常")
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.sw.dualscreen.model.response.DeviceConfig
|
||||
import com.sw.dualscreen.model.response.DinnerType
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.FoodOrder
|
||||
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||
import com.sw.dualscreen.model.response.FoodVector
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.UserFaceModel
|
||||
@@ -17,6 +18,7 @@ import com.sw.dualscreen.model.response.UserFaceModel2
|
||||
import com.sw.dualscreen.model.response.UserNutrition
|
||||
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
||||
import com.sw.dualscreen.utils.Debouncer
|
||||
import com.sw.dualscreen.utils.FileUtil
|
||||
import com.sw.dualscreen.utils.SPUtil
|
||||
import com.sw.dualscreen.utils.SpTool
|
||||
import com.sw.plate.App
|
||||
@@ -90,54 +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.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.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
|
||||
_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
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +224,7 @@ class UserViewModel : BaseViewModel() {
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||
onAllQueryFinished()
|
||||
}
|
||||
}
|
||||
@@ -363,9 +382,14 @@ class UserViewModel : BaseViewModel() {
|
||||
/**
|
||||
* 二维码支付
|
||||
*/
|
||||
fun qrCodePay(authCode: String,orderNo: String, block:(Boolean, String?)-> Unit){
|
||||
fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?,
|
||||
block: (Boolean, String?) -> Unit
|
||||
) {
|
||||
launch {
|
||||
val response = repository.qrCodePay(authCode, orderNo)
|
||||
val response = repository.qrCodePay(authCode, orderNo, memberId)
|
||||
if (parseResponse(response)) {
|
||||
block(true, response.data)
|
||||
} else {
|
||||
@@ -373,6 +397,7 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getDinnerType() {
|
||||
Timber.tag(TAG).d("getDinnerType")
|
||||
_dinnerTypeInfo.value = null
|
||||
@@ -491,7 +516,7 @@ class UserViewModel : BaseViewModel() {
|
||||
/**
|
||||
* 会员支付
|
||||
*/
|
||||
fun memberPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
|
||||
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("memberPay")
|
||||
launchWithLoading {
|
||||
val response = repository.memberPay(param)
|
||||
@@ -588,19 +613,22 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getCollectedFoodVector(param: MutableMap<String, String>, block: (List<FoodVector>?) -> Unit) {
|
||||
fun getCollectedFoodVector(
|
||||
param: MutableMap<String, String>,
|
||||
block: (List<FoodVector>?) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("getCollectedFoodVector")
|
||||
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)
|
||||
@@ -611,4 +639,28 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) {
|
||||
Timber.tag(TAG).d("getFoodOrderList")
|
||||
launchWithLoading {
|
||||
val response = repository.getFoodOrderList(userId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberDiscount")
|
||||
launchWithLoading {
|
||||
val response = repository.getMemberDiscount(userId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F2F2F2"/>
|
||||
<!-- <stroke android:color="#cdcdcd" android:width="1dp"/>-->
|
||||
<corners android:radius="5dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- 眼睛轮廓 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
|
||||
<!-- 瞳孔(path绘制圆形) -->
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
|
||||
<!-- 斜线表示隐藏 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:pathData="M 4 4 L 20 20" />
|
||||
</vector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- 眼睛轮廓 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
|
||||
<!-- 瞳孔(path绘制圆形) -->
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
|
||||
</vector>
|
||||
@@ -22,6 +22,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_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"
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:layout_marginHorizontal="14dp"
|
||||
android:layout_marginVertical="14dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="16dp"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/color_text_dialog"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/tvFoodName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
tools:background="@drawable/bg_text_main"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:textAlignment="center"
|
||||
tools:textColor="@color/black"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="肉沫干拌面" />
|
||||
@@ -13,11 +13,13 @@
|
||||
android:padding="28dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTopInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/setting_border"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="26dp">
|
||||
android:paddingBottom="26dp"
|
||||
android:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFoodName"
|
||||
@@ -30,10 +32,10 @@
|
||||
android:textSize="40sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- android:layout_width="300dp"-->
|
||||
<!-- android:layout_height="450dp"-->
|
||||
<!-- android:layout_width="330dp"-->
|
||||
<!-- android:layout_height="495dp"-->
|
||||
<!--android:layout_width="300dp"-->
|
||||
<!--android:layout_height="450dp"-->
|
||||
<!--android:layout_width="330dp"-->
|
||||
<!--android:layout_height="495dp"-->
|
||||
<FrameLayout
|
||||
android:id="@+id/flCameraView"
|
||||
android:layout_width="match_parent"
|
||||
@@ -187,6 +189,20 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flFoodDetail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/setting_border"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<include
|
||||
android:id="@+id/detailInclude"
|
||||
layout="@layout/layout_eat_detail"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flBottomView"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
agp = "8.10.1"
|
||||
core = "3.4.1"
|
||||
androidCore = "3.3.0"
|
||||
eventbus = "3.2.0"
|
||||
glide = "4.16.0"
|
||||
kotlin = "2.0.21"
|
||||
coreKtx = "1.10.1"
|
||||
@@ -13,7 +14,10 @@ material = "1.10.0"
|
||||
activity = "1.8.0"
|
||||
constraintlayout = "2.1.4"
|
||||
accompanistPermissions = "0.37.3"
|
||||
cameraCore = "1.3.0"
|
||||
cameraCore = "1.4.1"
|
||||
objectboxVersion = "5.0.1"
|
||||
refreshLayoutKernel = "3.0.0-alpha"
|
||||
refreshHeaderClassics = "3.0.0-alpha"
|
||||
retrofit = "3.0.0"
|
||||
okhttp = "4.12.0"
|
||||
timber = "5.0.1"
|
||||
@@ -30,11 +34,13 @@ pytorch = "1.13.0"
|
||||
|
||||
recyclerview = "1.3.2"
|
||||
|
||||
#baseQuickAdapter = "4.1.2"
|
||||
baseQuickAdapter = "3.0.4"
|
||||
baseQuickAdapter4 = "4.1.2"
|
||||
|
||||
[libraries]
|
||||
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
|
||||
core = { module = "com.google.zxing:core", version.ref = "core" }
|
||||
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
|
||||
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
@@ -56,7 +62,10 @@ hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", ve
|
||||
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
||||
|
||||
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||
objectbox-android-objectbrowser = { module = "io.objectbox:objectbox-android-objectbrowser", version.ref = "objectboxVersion" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
refresh-header-classics = { module = "io.github.scwang90:refresh-header-classics", version.ref = "refreshHeaderClassics" }
|
||||
refresh-layout-kernel = { module = "io.github.scwang90:refresh-layout-kernel", version.ref = "refreshLayoutKernel" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
||||
@@ -68,10 +77,12 @@ androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref
|
||||
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
|
||||
|
||||
#noinspection Aligned16KB
|
||||
pytorch-android = { group = "org.pytorch", name = "pytorch_android", version.ref = "pytorch" }
|
||||
pytorch-android-torchvision = { group = "org.pytorch", name = "pytorch_android_torchvision", version.ref = "pytorch" }
|
||||
|
||||
#base-quick-adapter-ktx = { module = "io.github.cymchad:BaseRecyclerViewAdapterHelper4", version.ref = "baseQuickAdapter" }
|
||||
#baseRecyclerViewAdapterHelper = { module = "com.github.CymChad:BaseRecyclerViewAdapterHelper", version.ref = "baseQuickAdapter" }
|
||||
baseRecyclerViewAdapterHelper4 = { module = "io.github.cymchad:BaseRecyclerViewAdapterHelper4", version.ref = "baseQuickAdapter4" }
|
||||
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
#Mon Jul 14 11:28:37 CST 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip
|
||||
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
|
||||
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -68,5 +68,5 @@ dependencies {
|
||||
|
||||
val objectboxVersion = "5.0.1"
|
||||
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
||||
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||
releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user