4 Commits
Author SHA1 Message Date
zxj 71ce668e3c 调整了个人营养数据计算逻辑,修复了摄像头逻辑 2025-08-07 10:49:50 +08:00
zxj 561a2fc9b2 格式化文档 2025-08-07 10:47:21 +08:00
zxj 08611349aa 精简了称sdk调用 2025-08-06 14:45:07 +08:00
zxj bbf2e605b5 修复了多次步骤2到步骤3问题 2025-08-05 14:23:13 +08:00
22 changed files with 850 additions and 237 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-07-31T00:35:57.879961400Z"> <DropdownSelection timestamp="2025-08-05T03:50:06.075639Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=2FD2511004054749" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=2FD2511004054749" />
+271
View File
@@ -0,0 +1,271 @@
{
"info": {
"_postman_id": "adfceb8d-2c35-41bf-82d9-40b87e10524d",
"name": "档口机",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "751386"
},
"item": [{
"name": "device获取token",
"request": {
"auth": {
"type": "noauth"
},
"method": "GET",
"header": [],
"url": {
"
raw": "http://device.shuziweidao.com:8889/sys/getEquipmentToken?qrcodeId=3ea47dc0-3cf0-3c2f-909c-265a9a65572e",
"protocol": "http",
"host": [
"device",
"shuziweidao",
"com"
],
"port": "8889",
"path": [
"sys",
"getEquipmentToken"
],
"query": [{
"key": "qrcodeId",
"value": "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
}
]
}
},
"response": []
}, {
"name": "获取配置信息",
"request": {
"method": "GET",
"header": [{
"key": "X-Access-Token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTM4NDIzNDEsInVzZXJuYW1lIjoiYWRtaW4ifQ.gcKT7BeMWNve8NydRmTTrdXW8wdS7W_Ot9kVdtllyuc",
"type": "text"
}
],
"url": {
"
raw": "http://device.shuziweidao.com:8889/equipment/stEquipment/queryByEquipmentCode?equipmentCode=3ea47dc0-3cf0-3c2f-909c-265a9a65572e",
"protocol": "http",
"host": [
"device",
"shuziweidao",
"com"
],
"port": "8889",
"path": [
"equipment",
"stEquipment",
"queryByEquipmentCode"
],
"query": [{
"key": "equipmentCode",
"value": "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
}
]
}
},
"response": []
}, {
"name": "获取业务服务器token",
"request": {
"method": "GET",
"header": [],
"url": {
"
raw": "http://192.168.1.207/gateway/local/sys/getEquipmentToken?qrcodeId=3ea47dc0-3cf0-3c2f-909c-265a9a65572e",
"protocol": "http",
"host": [
"192",
"168",
"1",
"207"
],
"path": [
"gateway",
"local",
"sys",
"getEquipmentToken"
],
"query": [{
"key": "qrcodeId",
"value": "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
}
]
}
},
"response": []
}, {
"name": "获取人脸数据",
"request": {
"method": "GET",
"header": [{
"key": "X-Access-Token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiIyOCIsInR5cGUiOiI0IiwidXNlcklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMSJ9.Jy7Dc2k5FlqUAmGReRn-uEg3Du5gtlyUhXKUMZLlO-E",
"type": "text"
}
],
"url": {
"raw": "http://192.168.1.207/gateway/local/stapi/cquser/getUserFaceCache/v2?pageIndex=0",
"protocol": "http",
"host": [
"192",
"168",
"1",
"207"
],
"path": [
"gateway",
"local",
"stapi",
"cquser",
"getUserFaceCache",
"v2"
],
"query": [{
"key": "pageIndex",
"value": "0"
}
]
}
},
"response": []
}, {
"name": "获取档口菜品信息",
"request": {
"method": "GET",
"header": [{
"key": "X-Access-Token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiIyOCIsInR5cGUiOiI0IiwidXNlcklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMSJ9.Jy7Dc2k5FlqUAmGReRn-uEg3Du5gtlyUhXKUMZLlO-E",
"type": "text"
}
],
"url": {
"
raw": "http://192.168.1.207/gateway/local/zhstapi/zhst/getRestInfoFoodsByType/stall?restId=1678234139391512577&type=0&foodName=西红柿",
"protocol": "http",
"host": [
"192",
"168",
"1",
"207"
],
"path": [
"gateway",
"local",
"zhstapi",
"zhst",
"getRestInfoFoodsByType",
"stall"
],
"query": [{
"key": "restId",
"value": "1678234139391512577"
}, {
"key": "type",
"value": "0",
"description": "0全部 1餐次"
}, {
"key": "foodName",
"value": "西红柿"
}
]
}
},
"response": []
}, {
"name": "通过用户信息获取就餐数据",
"request": {
"method": "GET",
"header": [{
"key": "X-Access-Token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiIyOCIsInR5cGUiOiI0IiwidXNlcklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMSJ9.Jy7Dc2k5FlqUAmGReRn-uEg3Du5gtlyUhXKUMZLlO-E",
"type": "text"
}
],
"url": {
"
raw": "http://192.168.1.207/gateway/local/zhstapi/zhst/getUserNutritionData/face?restId=1678234139391512577&userId=4958698b725b4e89a514fa8552aa5fa3&foodId=1701417944860119041",
"protocol": "http",
"host": [
"192",
"168",
"1",
"207"
],
"path": [
"gateway",
"local",
"zhstapi",
"zhst",
"getUserNutritionData",
"face"
],
"query": [{
"key": "restId",
"value": "1678234139391512577"
}, {
"key": "userId",
"value": "4958698b725b4e89a514fa8552aa5fa3"
}, {
"key": "foodId",
"value": "1701417944860119041"
}
]
}
},
"response": []
}, {
"name": "提交就餐数据",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"
raw": "http://192.168.1.207/gateway/local/zhstapi/zhst/userEatFood/doubleBalance/face/stall?restId=1678234139391512577&userId=4958698b725b4e89a514fa8552aa5fa3&foodId=1701417944860119041&faceTime&faceEndTime&eatWeight&foodWeight&deviceId",
"protocol": "http",
"host": [
"192",
"168",
"1",
"207"
],
"path": [
"gateway",
"local",
"zhstapi",
"zhst",
"userEatFood",
"doubleBalance",
"face",
"stall"
],
"body": [{
"appVersion": "1",
"deviceId": "abe60247-99d1-3c2a-9f10-c0b76f4b3f3b",
"eatWeight": 0.278,
"faceEndTime": 1754385701611,
"faceTime": 1754385694475,
"foodId": "1701417944860119041",
"foodWeight": 0.278,
"restId": "1678234139391512577",
"userId": "1937692681894932481"
}
]
}
},
"response": []
}
]
}
@@ -7,6 +7,10 @@ object GlobalData {
// var globalEquipmentCode: String = "202501171634" // var globalEquipmentCode: String = "202501171634"
var restId: String = "" var restId: String = ""
/**
* 设备id
*/
var deviceId: String = ""
/** /**
* app版本号 * app版本号
*/ */
@@ -1,6 +1,7 @@
package com.sw.dualscreen package com.sw.dualscreen
import com.sw.plate.App import com.sw.plate.App
import com.sw.plate.utils.AppUtil
import timber.log.Timber import timber.log.Timber
class MyApp : App() { class MyApp : App() {
@@ -11,5 +12,8 @@ class MyApp : App() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
val deviceId = AppUtil.getUDID(this)
Timber.d("UDID = ${AppUtil.getUDID(this)}")
GlobalData.deviceId = deviceId
} }
} }
@@ -4,7 +4,9 @@ import android.content.Context
import android.graphics.Outline import android.graphics.Outline
import android.graphics.Typeface import android.graphics.Typeface
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.View import android.view.View
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import android.widget.AdapterView import android.widget.AdapterView
@@ -35,7 +37,6 @@ import com.sw.dualscreen.sdk.SensorScaleUtils
import com.sw.dualscreen.utils.GlideUtils import com.sw.dualscreen.utils.GlideUtils
import com.sw.dualscreen.view.CustomBottomSheetDialog import com.sw.dualscreen.view.CustomBottomSheetDialog
import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.AppUtil
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -54,7 +55,9 @@ class MainActivity : AppCompatActivity() {
private var checkedItem: FoodInfo? = null private var checkedItem: FoodInfo? = null
private var imageAnalysis: ImageAnalysis? = null private var imageAnalysis: ImageAnalysis? = null
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
private var canIdentify: Boolean = true private var canIdentify: Boolean = false
private var bottomSheetDialog: CustomBottomSheetDialog? = null
private var lastWeight: Double = 0.0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -62,7 +65,6 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.main) setContentView(binding.main)
Timber.d("UDID = ${AppUtil.getUDID(this)}")
initView() initView()
setupSecondaryDisplay() setupSecondaryDisplay()
setupCamera() setupCamera()
@@ -83,10 +85,10 @@ class MainActivity : AppCompatActivity() {
} }
binding.previewView.clipToOutline = true binding.previewView.clipToOutline = true
binding.tvToSearch.setOnClickListener { binding.tvToSearch.setOnClickListener {
val bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) { bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
updateCurrentFood(it) updateCurrentFood(it)
} }
bottomSheetDialog.show(supportFragmentManager, "CustomBottomSheetDialog") bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
} }
val spinnerAdapter = ArrayAdapter<String>( val spinnerAdapter = ArrayAdapter<String>(
@@ -122,22 +124,26 @@ class MainActivity : AppCompatActivity() {
) )
binding.recyclerview.adapter = adapter binding.recyclerview.adapter = adapter
binding.tvFoodName.setOnClickListener { binding.tvFoodName.setOnClickListener {
if (presentation.currentStep == 2) { SensorScaleUtils.zero()
presentation.step3()
} else {
presentation.step1()
}
} }
registerDataChange() registerDataChange()
} }
fun updateCurrentFood(foodInfo: FoodInfo?) { fun updateCurrentFood(foodInfo: FoodInfo?) {
presentation.updateFood(foodInfo)
if (foodInfo != null) { if (foodInfo != null) {
presentation.updateFood(foodInfo)
binding.tvFoodName.text = foodInfo.foodName binding.tvFoodName.text = foodInfo.foodName
binding.previewView.visibility = View.GONE binding.previewView.visibility = View.GONE
binding.ivImg.visibility = View.VISIBLE binding.ivImg.visibility = View.VISIBLE
GlideUtils.loadRoundCornerImage(this, foodInfo.imgUrl, binding.ivImg, 12) val imgUrl = foodInfo.imgUrl
if (TextUtils.isEmpty(imgUrl)) {
takePhoto { imgUri ->
GlideUtils.loadRoundCornerImage(this, imgUri, binding.ivImg, 12)
presentation.updateFood(foodInfo.copy(imgUrl = imgUri.toString()))
}
} else {
GlideUtils.loadRoundCornerImage(this, foodInfo.imgUrl, binding.ivImg, 12)
}
pauseAnalysis() pauseAnalysis()
} else { } else {
binding.tvFoodName.text = "-" binding.tvFoodName.text = "-"
@@ -156,9 +162,9 @@ class MainActivity : AppCompatActivity() {
// presentation.step2() // presentation.step2()
} }
} }
viewModel.getIdentifiedFoodList()
SensorScaleUtils.startScale { weight -> SensorScaleUtils.startScale { weight ->
// Timber.d("weight = $weight") Timber.d("registerDataChange weight = $weight")
presentation.updateWeight(weight)
if (weight > 0.0) { if (weight > 0.0) {
if (canIdentify) { if (canIdentify) {
canIdentify = false canIdentify = false
@@ -289,24 +295,35 @@ class MainActivity : AppCompatActivity() {
} }
// 拍照功能 // 拍照功能
private fun takePhoto() { private fun takePhoto(callback: (Uri) -> Unit) {
Timber.d("takePhoto")
// 临时停止分析以避免干扰 // 临时停止分析以避免干扰
isAnalyzing = false isAnalyzing = false
// 创建输出选项 // 创建输出选项
val outputFileOptions = ImageCapture.OutputFileOptions.Builder( val executor = ContextCompat.getMainExecutor(this)
File( val cacheDir = cacheDir
cacheDir.path, "IMG_${System.currentTimeMillis()}.jpg" val photoFile = File.createTempFile(
) "IMG_${System.currentTimeMillis()}",
).build() ".jpg",
cacheDir
)
val cacheOutputOptions =
ImageCapture.OutputFileOptions.Builder(photoFile).build()
// 执行拍照 // 执行拍照
imageCapture?.takePicture( imageCapture?.takePicture(
outputFileOptions, cacheOutputOptions,
executor, executor,
object : ImageCapture.OnImageSavedCallback { object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
runOnUiThread { runOnUiThread {
ToastUtils.showToast("照片保存成功: ${outputFileResults.savedUri}") val savedUri = outputFileResults.savedUri
Timber.d("takePhoto savedUri = $savedUri")
ToastUtils.showToast("照片保存成功: $savedUri")
if (savedUri != null) {
callback(savedUri)
}
} }
// 恢复图像分析 // 恢复图像分析
isAnalyzing = true isAnalyzing = true
@@ -343,6 +360,7 @@ class MainActivity : AppCompatActivity() {
presentation.setStepChangeCallback { step -> presentation.setStepChangeCallback { step ->
if (step == 1) { if (step == 1) {
updateCurrentFood(null) updateCurrentFood(null)
bottomSheetDialog?.dismiss()
} }
} }
presentation.show() presentation.show()
@@ -35,3 +35,12 @@ fun String?.maskPhone(): String {
} }
return sb.toString() return sb.toString()
} }
fun String?.toSafeDouble(): Double {
if (this == null) return 0.0
return try {
toDouble()
} catch (e: Exception) {
0.0
}
}
@@ -14,5 +14,5 @@ data class UserNutritionParam(
val faceEndTime: Long, val faceEndTime: Long,
val eatWeight: Double, val eatWeight: Double,
val foodWeight: Double, val foodWeight: Double,
val deviceId: String = "" val deviceId: String = GlobalData.deviceId
) : Parcelable ) : Parcelable
@@ -35,7 +35,7 @@ data class UserNutritionData(
* 食物类型和实际摄入量清单 * 食物类型和实际摄入量清单
*/ */
@SerializedName("foodTypeAndRealIntakeVoList") @SerializedName("foodTypeAndRealIntakeVoList")
val foodTypeAndRealIntakeVoList: List<String?>? = listOf(), val foodTypeAndRealIntakeVoList: List<FoodTypeAndRealIntakeVo?>? = listOf(),
@SerializedName("foodWeight") @SerializedName("foodWeight")
val foodWeight: String? = "", val foodWeight: String? = "",
@SerializedName("message") @SerializedName("message")
@@ -66,7 +66,7 @@ data class UserNutritionData(
@SerializedName("stFoodInfoPagoda") @SerializedName("stFoodInfoPagoda")
val stFoodInfoPagoda: StFoodInfoPagoda? = StFoodInfoPagoda(), val stFoodInfoPagoda: StFoodInfoPagoda? = StFoodInfoPagoda(),
@SerializedName("stUserFoodInfoList") @SerializedName("stUserFoodInfoList")
val stUserFoodInfoList: List<String?>? = listOf(), val stUserFoodInfoList: List<StUserFoodInfo?>? = listOf(),
/** /**
* 每日总热量 * 每日总热量
*/ */
@@ -104,6 +104,26 @@ data class UserNutritionData(
if (TextUtils.isEmpty(totalEnergyCalculateScore)) 0.0 if (TextUtils.isEmpty(totalEnergyCalculateScore)) 0.0
else totalEnergyCalculateScore!!.toDouble() else totalEnergyCalculateScore!!.toDouble()
@Parcelize
data class FoodTypeAndRealIntakeVo(
@SerializedName("childMaterClassName")
val childMaterClassName: String? = "",
@SerializedName("foodId")
val foodId: String? = "",
@SerializedName("goodsId")
val goodsId: String? = "",
@SerializedName("goodsName")
val goodsName: String? = "",
@SerializedName("materClassName")
val materClassName: String? = "",
@SerializedName("materId")
val materId: String? = "",
@SerializedName("materialType")
val materialType: String? = "",
@SerializedName("realityIntake")
val realityIntake: String? = ""
) : Parcelable
@Parcelize @Parcelize
data class StFoodInfoPagoda( data class StFoodInfoPagoda(
/** /**
@@ -178,4 +198,56 @@ data class UserNutritionData(
else meat!!.toDouble() else meat!!.toDouble()
} }
@Parcelize
data class StUserFoodInfo(
@SerializedName("canteenId")
val canteenId: String? = "",
@SerializedName("createBy")
val createBy: String? = "",
@SerializedName("createTime")
val createTime: String? = "",
@SerializedName("dataSource")
val dataSource: String? = "",
@SerializedName("dataType")
val dataType: String? = "",
@SerializedName("delFlag")
val delFlag: String? = "",
@SerializedName("deviceId")
val deviceId: String? = "",
@SerializedName("dinnerType")
val dinnerType: String? = "",
@SerializedName("eatDay")
val eatDay: String? = "",
@SerializedName("eatNum")
val eatNum: String? = "",
@SerializedName("eatWeight")
val eatWeight: Double? = 0.0,
@SerializedName("foodId")
val foodId: String? = "",
@SerializedName("foodImg")
val foodImg: String? = "",
@SerializedName("foodMaterialId")
val foodMaterialId: String? = "",
@SerializedName("foodName")
val foodName: String? = "",
@SerializedName("foodWeight")
val foodWeight: String? = "",
@SerializedName("id")
val id: String? = "",
@SerializedName("isSync")
val isSync: String? = "",
@SerializedName("isSyncCopy")
val isSyncCopy: String? = "",
@SerializedName("specId")
val specId: String? = "",
@SerializedName("stBasicDiningInformationUserESVo")
val stBasicDiningInformationUserESVo: String? = "",
@SerializedName("stallType")
val stallType: String? = "",
@SerializedName("type")
val type: String? = "",
@SerializedName("userId")
val userId: String? = ""
) : Parcelable
} }
@@ -97,6 +97,6 @@ interface ApiService {
@POST @POST
suspend fun postUserNutritionData( suspend fun postUserNutritionData(
@Url url: String = "${GlobalData.appBaseUrl}/zhstapi/zhst/userEatFood/doubleBalance/face/stall", @Url url: String = "${GlobalData.appBaseUrl}/zhstapi/zhst/userEatFood/doubleBalance/face/stall",
@Body param: UserNutritionParam @Body param: List<UserNutritionParam>
): ApiResponse<String> ): ApiResponse<String>
} }
@@ -8,8 +8,6 @@ import android.graphics.Bitmap
import android.graphics.Outline import android.graphics.Outline
import android.graphics.Point import android.graphics.Point
import android.hardware.Camera import android.hardware.Camera
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
@@ -19,7 +17,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
@@ -32,10 +29,10 @@ import com.sw.dualscreen.databinding.PresentationSecondaryScreenBinding
import com.sw.dualscreen.ext.dp import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.maskName import com.sw.dualscreen.ext.maskName
import com.sw.dualscreen.model.request.UserNutritionParam
import com.sw.dualscreen.model.response.DinnerTypeInfo import com.sw.dualscreen.model.response.DinnerTypeInfo
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.UserNutritionData import com.sw.dualscreen.model.response.UserNutritionData
import com.sw.dualscreen.sdk.SensorScaleUtils
import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.GlideUtils import com.sw.dualscreen.utils.GlideUtils
import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.dualscreen.viewmodel.UserViewModel
@@ -49,9 +46,11 @@ import com.sw.plate.utils.arcface.PreviewConfig
import com.sw.plate.utils.arcface.camera.CameraListener import com.sw.plate.utils.arcface.camera.CameraListener
import com.sw.plate.utils.arcface.camera.DualCameraHelper import com.sw.plate.utils.arcface.camera.DualCameraHelper
import com.sw.plate.utils.arcface.face.constants.LivenessType import com.sw.plate.utils.arcface.face.constants.LivenessType
import com.sw.plate.utils.arcface.face.model.CompareResult
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@@ -67,19 +66,23 @@ class SecondaryScreenPresentation(
val recognizeViewModel: RecognizeViewModel val recognizeViewModel: RecognizeViewModel
) : Presentation(context, display), ViewTreeObserver.OnGlobalLayoutListener { ) : Presentation(context, display), ViewTreeObserver.OnGlobalLayoutListener {
private lateinit var binding: PresentationSecondaryScreenBinding private lateinit var binding: PresentationSecondaryScreenBinding
private var cameraDevice: CameraDevice? = null
// private var cameraCaptureSession: CameraCaptureSession? = null
private lateinit var cameraManager: CameraManager
private var cameraIdList: Array<String> = arrayOf()
// 当前食物信息 // 当前食物信息
private var currentFood: FoodInfo? = null private var currentFood: FoodInfo? = null
private var lastFood: FoodInfo? = null
private var userNutritionData: UserNutritionData? = null private var userNutritionData: UserNutritionData? = null
private var dinnerType: DinnerTypeInfo? = null private var dinnerTypeInfo: DinnerTypeInfo? = null
private lateinit var itemUserNutritionBinding: ItemUserNutritionBinding private lateinit var itemUserNutritionBinding: ItemUserNutritionBinding
private lateinit var stepChangeCallback: (Int) -> Unit private lateinit var stepChangeCallback: (Int) -> Unit
// 上一次的计算热量结果
private var lastCalcResultInfo: UserNutritionUtils.CalcResultInfo? = null
private var lastWeight = 0.0
private val debouncer = Debouncer(500)
private var faceTime: Long = 0L // 人脸识别时的时间
private var recognitionWeight: Double = 0.0 // 人脸识别时的重量
private var lastFaceTrackId: Int = 0 // 上一次的人脸信息
// 当前步骤 // 当前步骤
var currentStep: Int = 0 var currentStep: Int = 0
@@ -90,6 +93,7 @@ class SecondaryScreenPresentation(
setContentView(binding.root) setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.transparent) window?.setBackgroundDrawableResource(android.R.color.transparent)
initView() initView()
setupArcCamera()
// binding.includeUserHeat.ivHeat.showText("1070千卡") // binding.includeUserHeat.ivHeat.showText("1070千卡")
registerDataChange() registerDataChange()
} }
@@ -98,17 +102,20 @@ class SecondaryScreenPresentation(
activity.lifecycleScope.launch { activity.lifecycleScope.launch {
viewModel.nutritionData.collect { viewModel.nutritionData.collect {
Timber.d("registerDataChange nutritionData = $it") Timber.d("registerDataChange nutritionData = $it")
if (it == null) return@collect
userNutritionData = it userNutritionData = it
if (it == null) return@collect
step3() step3()
itemUserNutritionBinding.tvUserName.text = it.userName.maskName() itemUserNutritionBinding.tvUserName.text = it.userName.maskName()
itemUserNutritionBinding.tvRecommendHeat.text = itemUserNutritionBinding.tvRecommendHeat.text =
"推荐热量:${it.recommendMin}-${it.recommendMax}" "推荐热量:${it.recommendMin}-${it.recommendMax}"
updateWeight(lastWeight / 1000)
} }
} }
activity.lifecycleScope.launch { activity.lifecycleScope.launch {
viewModel.dinnerTypeInfo.collect { viewModel.dinnerTypeInfo.drop(1).collect {
dinnerType = it Timber.d("registerDataChange dinnerTypeInfo = $it")
dinnerTypeInfo = it
} }
} }
} }
@@ -138,22 +145,9 @@ class SecondaryScreenPresentation(
* 启用虹软人脸失败 * 启用虹软人脸失败
*/ */
private fun setupArcCamera() { private fun setupArcCamera() {
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
// 获取摄像头列表
cameraIdList = cameraManager.cameraIdList
if (cameraIdList.isEmpty()) {
Toast.makeText(context, "没有可用的摄像头", Toast.LENGTH_SHORT).show()
return
}
cameraIdList.forEach {
Timber.d("摄像头id列表 = ${it}")
}
initArcViewModel() initArcViewModel()
initArcView() initArcView()
openRectInfoDraw = true openRectInfoDraw = true
openCamera(cameraIdList.last())
} }
/** /**
@@ -162,18 +156,21 @@ class SecondaryScreenPresentation(
fun step1() { fun step1() {
Timber.d("step1") Timber.d("step1")
currentStep = 1 currentStep = 1
//回复上一次的设置
lastWeight = 0.0
lastFood = null
lastCalcResultInfo = null
recognitionWeight = 0.0
stepChangeCallback(currentStep) stepChangeCallback(currentStep)
binding.tvFoodInfo.text = "菜品识别中" binding.tvFoodName.text = "菜品识别中"
binding.ivFaceBg.visibility = View.GONE binding.ivFaceBg.visibility = View.GONE
binding.flPreview.visibility = View.VISIBLE binding.flPreview.visibility = View.VISIBLE
binding.ivImg.visibility = View.VISIBLE binding.ivImg.visibility = View.VISIBLE
binding.flFace.visibility = View.GONE binding.flFace.visibility = View.INVISIBLE
pauseCamera() pauseCamera()
binding.tvBottomTip.visibility = View.VISIBLE binding.tvBottomTip.visibility = View.VISIBLE
itemUserNutritionBinding.clNutritionData.visibility = View.GONE itemUserNutritionBinding.clNutritionData.visibility = View.GONE
stopSensorScale()
viewModel.getDinnerType()
} }
/** /**
@@ -184,27 +181,34 @@ class SecondaryScreenPresentation(
currentStep = 2 currentStep = 2
stepChangeCallback(currentStep) stepChangeCallback(currentStep)
currentFood = foodInfo currentFood = foodInfo
viewModel.getDinnerType()
resumeCamera() resumeCamera()
binding.tvFoodInfo.text = foodInfo.foodName binding.tvFoodName.text = foodInfo.foodName
binding.ivImg.visibility = View.GONE
binding.ivFaceBg.visibility = View.VISIBLE
binding.flPreview.visibility = View.GONE binding.flPreview.visibility = View.GONE
binding.flFace.visibility = View.VISIBLE binding.flFace.visibility = View.VISIBLE
binding.ivFaceBg.visibility = View.VISIBLE binding.ivFaceBg.visibility = View.GONE
binding.tvFaceTip.visibility = View.VISIBLE binding.tvFaceTip.visibility = View.VISIBLE
binding.tvBottomTip.visibility = View.VISIBLE
setupArcCamera() itemUserNutritionBinding.clNutritionData.visibility = View.GONE
} }
fun updateFood(foodInfo: FoodInfo?) { fun updateFood(foodInfo: FoodInfo?) {
Timber.d("step2 updateFood") Timber.d("updateFood")
currentFood = foodInfo currentFood = foodInfo
if (foodInfo != null) { if (foodInfo != null) {
if (currentStep != 2) { if (currentStep == 1) {
step2(foodInfo) step2(foodInfo)
} else { } else {
binding.tvFoodInfo.text = foodInfo.foodName binding.tvFoodName.text = foodInfo.foodName
GlideUtils.loadRoundCornerImage(
context,
url = currentFood!!.imgUrl,
imageView = binding.ivImg,
radius = 12
)
} }
} }
} }
@@ -220,10 +224,7 @@ class SecondaryScreenPresentation(
ToastUtils.showToast("选中物品为空3") ToastUtils.showToast("选中物品为空3")
return return
} }
binding.tvFoodInfo.text = currentFood!!.foodName binding.tvFoodName.text = currentFood!!.foodName
startSensorScale()
GlideUtils.loadRoundCornerImage( GlideUtils.loadRoundCornerImage(
context, context,
url = currentFood!!.imgUrl, url = currentFood!!.imgUrl,
@@ -231,124 +232,132 @@ class SecondaryScreenPresentation(
radius = 12 radius = 12
) )
binding.flPreview.visibility = View.VISIBLE binding.flPreview.visibility = View.VISIBLE
// binding.previewView.visibility = View.GONE
binding.ivImg.visibility = View.VISIBLE binding.ivImg.visibility = View.VISIBLE
binding.flFace.visibility = View.GONE binding.flFace.visibility = View.INVISIBLE
binding.ivFaceBg.visibility = View.GONE binding.ivFaceBg.visibility = View.GONE
binding.tvFaceTip.visibility = View.GONE binding.tvFaceTip.visibility = View.GONE
binding.tvBottomTip.visibility = View.GONE binding.tvBottomTip.visibility = View.GONE
itemUserNutritionBinding.clNutritionData.visibility = View.VISIBLE itemUserNutritionBinding.clNutritionData.visibility = View.VISIBLE
viewModel.getUserNutritionData(
userId = userNutritionData!!.userId!!,
foodId = currentFood!!.id!!
)
} }
private fun startSensorScale() { fun updateWeight(weight: Double) {
var lastWeight = 0.0 lastWeight = weight * 1000 // 将千克转成克
val debouncer = Debouncer(1000) Timber.d("updateWeight lastWeight = $weight")
SensorScaleUtils.startScale { weight -> if (currentFood == null) {
if (weight == lastWeight) return@startScale Timber.e("updateWeight currentFood is null")
Timber.d("registerDataChange weight = $weight") return
lastWeight = weight }
if (currentFood == null) return@startScale if (userNutritionData == null) {
if (userNutritionData == null) return@startScale Timber.e("updateWeight userNutritionData is null")
if (dinnerType == null) return@startScale return
debouncer.debounce { }
val calcResultInfo = UserNutritionUtils.calculateNutrition( if (dinnerTypeInfo == null) {
currentFood!!, Timber.e("updateWeight dinnerType is null")
userNutritionData!!, return
weight, }
dinnerType = dinnerType!!.dinnerType!!.dinnerType!! calculateNutrition(recognitionWeight - lastWeight)
}
private fun calculateNutrition(weight: Double) {
debouncer.debounce {
Timber.d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight")
val dinnerType = dinnerTypeInfo!!.dinnerType!!.dinnerType!!
Timber.d("calculateNutrition foodName = ${currentFood!!.foodName}, dinnerType = $dinnerType, userId = ${userNutritionData!!.userId}")
val calcResultInfo = UserNutritionUtils.calculateNutrition(
currentFood!!,
userNutritionData!!,
weight,
dinnerType = dinnerType
)
Timber.d("calculateNutrition calcResultInfo = $calcResultInfo")
if (lastCalcResultInfo == calcResultInfo) {
Timber.d("calculateNutrition 与上次计算结果无变化")
return@debounce
}
lastCalcResultInfo = calcResultInfo
val columnMax = UserNutritionUtils.getMaxInt(calcResultInfo)
activity.runOnUiThread {
val totalKcal = calcResultInfo.totalKcal
val grain = calcResultInfo.grain
val fruits = calcResultInfo.fruits
val meat = calcResultInfo.meat
// 设置热量
itemUserNutritionBinding.customKcalColumn.setImageDrawable(
true,
totalKcal.max < totalKcal.current
) )
Timber.d("calcResultInfo = $calcResultInfo") var maxKcal = totalKcal.max
activity.runOnUiThread { Timber.d("maxKcal = $maxKcal")
val totalKcal = calcResultInfo.totalKcal if (maxKcal <= 0.0) {
val grain = calcResultInfo.grain maxKcal = 100.0
val fruits = calcResultInfo.fruits
val meat = calcResultInfo.meat
itemUserNutritionBinding.customKcalColumn.setImageDrawable(
true,
totalKcal.first < totalKcal.second
)
itemUserNutritionBinding.totalKcalTv.text = totalKcal.second.format2String()
itemUserNutritionBinding.customKcalColumn.setCustomHeightPercent(
(totalKcal.second / 100).toFloat(),
true
)
itemUserNutritionBinding.customFoodColumn.setImageDrawable(
false,
grain.first < grain.second
)
itemUserNutritionBinding.totalFoodTv.text = grain.second.format2String()
itemUserNutritionBinding.customFoodColumn.setCustomHeightPercent(
(grain.second / 100).toFloat(),
true
)
itemUserNutritionBinding.customVegetableColumn.setImageDrawable(
false,
fruits.first < fruits.second
)
itemUserNutritionBinding.totalVegetableTv.text = fruits.second.format2String()
itemUserNutritionBinding.customVegetableColumn.setCustomHeightPercent(
(fruits.second / 100).toFloat(),
true
)
itemUserNutritionBinding.customMeatColumn.setImageDrawable(
false,
meat.first < meat.second
)
itemUserNutritionBinding.totalMeatTv.text = meat.second.format2String()
itemUserNutritionBinding.customMeatColumn.setCustomHeightPercent(
(meat.second / 100).toFloat(),
true
)
} }
Timber.d("maxKcal 1 = $maxKcal")
itemUserNutritionBinding.totalKcalTv.text = totalKcal.current.format2String()
itemUserNutritionBinding.customKcalColumn.setCustomHeightPercent(
(totalKcal.current / maxKcal).toFloat(),
true
)
// 设置主食
itemUserNutritionBinding.customFoodColumn.setImageDrawable(
false,
grain.max < grain.current
)
itemUserNutritionBinding.totalFoodTv.text = grain.current.format2String()
itemUserNutritionBinding.customFoodColumn.setCustomHeightPercent(
(grain.current / columnMax).toFloat(),
true
)
// 设置果蔬
itemUserNutritionBinding.customVegetableColumn.setImageDrawable(
false,
fruits.max < fruits.current
)
itemUserNutritionBinding.totalVegetableTv.text = fruits.current.format2String()
itemUserNutritionBinding.customVegetableColumn.setCustomHeightPercent(
(fruits.current / columnMax).toFloat(),
true
)
// 设置肉蛋
itemUserNutritionBinding.customMeatColumn.setImageDrawable(
false,
meat.max < meat.current
)
itemUserNutritionBinding.totalMeatTv.text = meat.current.format2String()
itemUserNutritionBinding.customMeatColumn.setCustomHeightPercent(
(meat.current / columnMax).toFloat(),
true
)
} }
} }
} }
private fun stopSensorScale() { private fun openCamera() {
SensorScaleUtils.closeScale() Timber.d("openCamera")
}
private fun openCamera(cameraId: String) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED PackageManager.PERMISSION_GRANTED
) { ) {
ToastUtils.showToast("无摄像头权限")
return return
} }
try { try {
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() { val cameraCount = Camera.getNumberOfCameras()
override fun onOpened(device: CameraDevice) { if (cameraCount < 3){
cameraDevice = device ToastUtils.showToast("摄像头数量异常")
// createCameraPreviewSession() return
recognizeViewModel.init( }
PreviewConfig( recognizeViewModel.init(
2, 4, 0, 0 PreviewConfig(
) 2, 1, 90, 90
) )
initRgbCamera() )
resumeCamera() initRgbCamera()
} if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) {
initIrCamera()
override fun onDisconnected(device: CameraDevice) { }
device.close()
cameraDevice = null
}
override fun onError(device: CameraDevice, error: Int) {
device.close()
cameraDevice = null
}
}, null)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Camera2", "打开摄像头失败", e) Timber.e(e, "打开摄像头失败")
} }
} }
@@ -360,9 +369,12 @@ class SecondaryScreenPresentation(
private var isRecognition = false private var isRecognition = false
private var rgbCameraHelper: DualCameraHelper? = null private var rgbCameraHelper: DualCameraHelper? = null
private var rgbFaceRectTransformer: FaceRectTransformer? = null private var rgbFaceRectTransformer: FaceRectTransformer? = null
private val livenessType = LivenessType.RGB private val livenessType = LivenessType.IR
private var openRectInfoDraw = false private var openRectInfoDraw = false
private var irCameraHelper: DualCameraHelper? = null
private var irFaceRectTransformer: FaceRectTransformer? = null
private fun initArcViewModel() { private fun initArcViewModel() {
recognizeViewModel.setLiveType(livenessType) recognizeViewModel.setLiveType(livenessType)
@@ -403,17 +415,24 @@ class SecondaryScreenPresentation(
Timber.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") Timber.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}")
}) })
recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? -> recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? ->
Timber.i("recognizeNotice observe notice = $notice") // Timber.i("recognizeNotice observe notice = $notice")
if (currentStep == 3 && notice == "leave") {
// step1()
}
}) })
recognizeViewModel.recognizeUserId.observe(activity, Observer { userId: String? -> recognizeViewModel.recognizeUserId.observe(
Timber.i("recognizeUserId observe userId = $userId") activity,
if (userId == null) return@Observer Observer { compareResult: CompareResult ->
viewModel.getUserNutritionData(userId = userId, foodId = currentFood!!.id!!) Timber.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}")
}) faceTime = System.currentTimeMillis()
recognitionWeight = lastWeight
lastFaceTrackId = compareResult.trackId
val faceEntity = compareResult.faceEntity
val userId = faceEntity.userName
if (userId == null) return@Observer
viewModel.getUserNutritionData(
userId = userId,
foodId = currentFood!!.id!!
)
})
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info -> recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
Timber.i("drawRectInfoText observe info = $info") Timber.i("drawRectInfoText observe info = $info")
@@ -495,12 +514,13 @@ class SecondaryScreenPresentation(
displayOrientation: Int, displayOrientation: Int,
isMirror: Boolean isMirror: Boolean
) { ) {
Timber.d("Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
activity.runOnUiThread({ activity.runOnUiThread({
val previewSizeRgb = camera.getParameters().getPreviewSize() val previewSizeRgb = camera.getParameters().getPreviewSize()
val layoutParams = adjustPreviewViewSize( val layoutParams = adjustPreviewViewSize(
binding.dualCameraTexturePreviewRgb, binding.dualCameraTexturePreviewRgb,
binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView,
previewSizeRgb, 90, 1f previewSizeRgb, displayOrientation, 1f
) )
Timber.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") Timber.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
Timber.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") Timber.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
@@ -562,7 +582,7 @@ class SecondaryScreenPresentation(
) )
) )
.rotation(activity.windowManager.defaultDisplay.rotation) .rotation(activity.windowManager.defaultDisplay.rotation)
.additionalRotation(90) // 角度 .additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度
.previewSize(recognizeViewModel.loadPreviewSize()) .previewSize(recognizeViewModel.loadPreviewSize())
.specificCameraId(previewConfig.rgbCameraId) .specificCameraId(previewConfig.rgbCameraId)
.isMirror(true) .isMirror(true)
@@ -573,12 +593,96 @@ class SecondaryScreenPresentation(
rgbCameraHelper!!.start() rgbCameraHelper!!.start()
} }
/**
* 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用
*/
private fun initIrCamera() {
Timber.d("initIrCamera: livenessType = $livenessType")
if (livenessType === LivenessType.RGB) {
return
}
val irCameraListener: CameraListener = object : CameraListener {
override fun onCameraOpened(
camera: Camera,
cameraId: Int,
displayOrientation: Int,
isMirror: Boolean
) {
Timber.d("IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
val previewSizeIr = camera.getParameters().getPreviewSize()
val layoutParams = adjustPreviewViewSize(
binding.dualCameraTexturePreviewRgb,
binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr,
previewSizeIr, displayOrientation, 0.25f
)
irFaceRectTransformer = FaceRectTransformer(
previewSizeIr.width, previewSizeIr.height,
layoutParams.width, layoutParams.height, displayOrientation, cameraId, isMirror,
ConfigUtil.isDrawIrRectHorizontalMirror(context),
ConfigUtil.isDrawIrRectVerticalMirror(context)
)
recognizeViewModel.onIrCameraOpened(camera)
recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer)
}
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
recognizeViewModel.refreshIrPreviewData(nv21)
}
override fun onCameraClosed() {
Timber.i("onCameraClosed: ")
}
override fun onCameraError(e: java.lang.Exception) {
Timber.i("onCameraError: ${e.message}")
e.printStackTrace()
}
override fun onCameraConfigurationChanged(
cameraID: Int,
displayOrientation: Int
) {
if (irFaceRectTransformer != null) {
irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
}
Timber.i("onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation")
}
}
val previewConfig = recognizeViewModel.previewConfig
irCameraHelper = DualCameraHelper.Builder()
.previewViewSize(
Point(
binding.dualCameraTexturePreviewIr.measuredWidth,
binding.dualCameraTexturePreviewIr.measuredHeight
)
)
.rotation(activity.windowManager.defaultDisplay.rotation)
.specificCameraId(previewConfig.irCameraId)
.previewOn(binding.dualCameraTexturePreviewIr)
.cameraListener(irCameraListener)
.isMirror(true)
.previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小
.additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度
.build()
irCameraHelper!!.init()
try {
irCameraHelper!!.start()
} catch (e: RuntimeException) {
ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice))
}
}
/** /**
* 绘制RGB、IR画面的实时人脸信息 * 绘制RGB、IR画面的实时人脸信息
* *
* @param facePreviewInfoList RGB画面的实时人脸信息 * @param facePreviewInfoList RGB画面的实时人脸信息
*/ */
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) { private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) {
// Timber.d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
if (rgbFaceRectTransformer != null) { if (rgbFaceRectTransformer != null) {
val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo( val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo(
facePreviewInfoList, facePreviewInfoList,
@@ -588,6 +692,32 @@ class SecondaryScreenPresentation(
// 识别成功 // 识别成功
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList)
} }
if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) {
if (lastFaceTrackId != 0) {
Timber.i("$lastFaceTrackId 用户离开")
lastFaceTrackId = 0
postUserData()
step1()
}
}
}
fun postUserData() {
Timber.d("postUserData")
if (userNutritionData == null || currentFood == null) {
Timber.d("postUserData userNutritionData = ${userNutritionData == null}, currentFood = ${currentFood == null}")
return
}
val userNutritionParam = UserNutritionParam(
userId = userNutritionData?.userId!!,
foodId = currentFood?.id!!,
faceTime = faceTime,
faceEndTime = System.currentTimeMillis(),
eatWeight = recognitionWeight - lastWeight,
foodWeight = lastWeight
)
viewModel.postUserNutritionData(listOf<UserNutritionParam>(userNutritionParam))
} }
fun openRectInfoDraw(view: View?) { fun openRectInfoDraw(view: View?) {
@@ -599,6 +729,7 @@ class SecondaryScreenPresentation(
override fun onGlobalLayout() { override fun onGlobalLayout() {
Timber.d("onGlobalLayout") Timber.d("onGlobalLayout")
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this)
openCamera()
} }
@@ -618,13 +749,16 @@ class SecondaryScreenPresentation(
rgbCameraHelper!!.release() rgbCameraHelper!!.release()
rgbCameraHelper = null rgbCameraHelper = null
} }
SensorScaleUtils.closeScale() if (irCameraHelper != null) {
irCameraHelper!!.release()
irCameraHelper = null
}
recognizeViewModel.destroy() recognizeViewModel.destroy()
super.onStop() super.onStop()
} }
fun resumeCamera() { fun resumeCamera() {
Timber.d("resumeCamera") Timber.d("resumeCamera isRecognition = $isRecognition")
isRecognition = true isRecognition = true
if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) {
rgbCameraHelper!!.start() rgbCameraHelper!!.start()
@@ -632,7 +766,7 @@ class SecondaryScreenPresentation(
} }
fun pauseCamera() { fun pauseCamera() {
Timber.d("pauseCamera") Timber.d("pauseCamera isRecognition = $isRecognition")
isRecognition = false isRecognition = false
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
@@ -2,8 +2,12 @@ package com.sw.dualscreen.presentation
import android.text.TextUtils import android.text.TextUtils
import com.sw.dualscreen.ext.toSafeDouble
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.UserNutritionData import com.sw.dualscreen.model.response.UserNutritionData
import timber.log.Timber
import kotlin.math.max
import kotlin.math.min
object UserNutritionUtils { object UserNutritionUtils {
@@ -16,12 +20,12 @@ object UserNutritionUtils {
weight: Double, weight: Double,
dinnerType: String dinnerType: String
): CalcResultInfo { ): CalcResultInfo {
// 1. 初始化变量 // 初始化变量
var foodKcal = 0.0 var foodKcal = 0.0
var totalKcal = 0.0 var totalKcal = 0.0
var (vegetable, meat, fruits, grain) = List(4) { 0.0 } var (vegetable, meat, fruits, grain) = List(4) { 0.0 }
// 2. 处理食物信息 // 处理食物信息
foodKcal = calculateValue(foodInfo.stFoodInfoMaterial?.energyKcal, weight) foodKcal = calculateValue(foodInfo.stFoodInfoMaterial?.energyKcal, weight)
grain = calculateValue(foodInfo.stFoodInfoPagodaAPPVO?.grainValue(), weight) grain = calculateValue(foodInfo.stFoodInfoPagodaAPPVO?.grainValue(), weight)
@@ -29,33 +33,52 @@ object UserNutritionUtils {
vegetable = calculateValue(foodInfo.stFoodInfoPagodaAPPVO?.vegetableValue(), weight) vegetable = calculateValue(foodInfo.stFoodInfoPagodaAPPVO?.vegetableValue(), weight)
meat = calculateValue(foodInfo.stFoodInfoPagodaAPPVO?.meatValue(), weight) meat = calculateValue(foodInfo.stFoodInfoPagodaAPPVO?.meatValue(), weight)
// 3. 合并用户数据 // 合并用户数据
grain += userModel.stFoodInfoPagoda?.grainValue() ?: 0.0 grain += userModel.stFoodInfoPagoda?.grainValue() ?: 0.0
fruits += userModel.stFoodInfoPagoda?.fruitsValue() ?: 0.0 fruits += userModel.stFoodInfoPagoda?.fruitsValue() ?: 0.0
vegetable = vegetable.plus(userModel.stFoodInfoPagoda?.vegetableValue() ?: 0.0) vegetable = vegetable.plus(userModel.stFoodInfoPagoda?.vegetableValue() ?: 0.0)
meat += userModel.stFoodInfoPagoda?.meatValue() ?: 0.0 meat += userModel.stFoodInfoPagoda?.meatValue() ?: 0.0
totalKcal = foodKcal + userModel.energyValue() totalKcal = foodKcal + userModel.energyValue()
val fruitsAndVeg = fruits + vegetable if (totalKcal < 0){
totalKcal = 0.0
// 4. 判断当餐最大热量 }
// 判断当餐最大热量
val maxKcal = val maxKcal =
userModel.totalEnergyCalculateScoreValue() * calculateDinnerTypeRatio(dinnerType) / 10 userModel.totalEnergyCalculateScoreValue() * calculateDinnerTypeRatio(dinnerType) / 10
val pagoda = userModel.stFoodInfoPagoda val pagoda = userModel.stFoodInfoPagoda
val fruitsInfo = parseRecommend(pagoda?.fruitsRecommend, fruits)
val vegetableInfo = parseRecommend(pagoda?.vegetableRecommend, vegetable)
val calcResult = CalcResultInfo( val calcResult = CalcResultInfo(
totalKcal = maxKcal to totalKcal, totalKcal = CalcInfo(
grain = parseMax(pagoda?.grainRecommend) to grain, max = maxKcal,
fruits = parseMax(pagoda?.fruitsRecommend) + parseMax(pagoda?.vegetableRecommend) to fruitsAndVeg, min = 0.0,
meat = parseMax(pagoda?.meatRecommend) to meat current = totalKcal
),
grain = parseRecommend(pagoda?.grainRecommend, grain),
fruits = CalcInfo( // 果蔬 = 水果+蔬菜
max = fruitsInfo.max + vegetableInfo.max,
min = fruitsInfo.min + vegetableInfo.min,
current = fruitsInfo.current + vegetableInfo.current
),
meat = parseRecommend(pagoda?.meatRecommend, meat)
) )
return calcResult return calcResult
} }
fun parseMax(recommend: String?): Double { fun parseRecommend(recommend: String?, current: Double): CalcInfo {
if (TextUtils.isEmpty(recommend) || !recommend!!.contains("-")) return 0.0 var newCurrent = if (current < 0) 0.0 else current
if (TextUtils.isEmpty(recommend) || !recommend!!.contains("-")) {
Timber.e("parseRecommend recommend 格式错误")
return CalcInfo(
newCurrent,
0.0,
newCurrent
)
}
val (min, max) = recommend.split("-") val (min, max) = recommend.split("-")
return max.toDouble() return CalcInfo(max = max.toSafeDouble(), min = min.toSafeDouble(), current = newCurrent)
} }
private fun calculateDinnerTypeRatio(dinnerType: String): Int { private fun calculateDinnerTypeRatio(dinnerType: String): Int {
@@ -66,14 +89,28 @@ object UserNutritionUtils {
} }
} }
/**
* 计算每百克含量
*/
private fun calculateValue(nutrient: Double?, weight: Double): Double { private fun calculateValue(nutrient: Double?, weight: Double): Double {
return weight * (nutrient ?: 1.0) return weight * (nutrient ?: 1.0) / 100
} }
fun getMaxInt(calcResultInfo: CalcResultInfo): Int {
val max1 = max(calcResultInfo.grain.max, calcResultInfo.fruits.max)
return max(max1, calcResultInfo.meat.max).toInt()
}
data class CalcInfo(
var max: Double,
var min: Double,
var current: Double,
)
data class CalcResultInfo( data class CalcResultInfo(
var totalKcal: Pair<Double, Double>, var totalKcal: CalcInfo,
var grain: Pair<Double, Double>, var grain: CalcInfo,
var fruits: Pair<Double, Double>, var fruits: CalcInfo,
var meat: Pair<Double, Double>, var meat: CalcInfo,
) )
} }
@@ -99,7 +99,7 @@ class RemoteRepository constructor(
* 提交就餐数据 * 提交就餐数据
*/ */
suspend fun postUserNutritionData( suspend fun postUserNutritionData(
param: UserNutritionParam param: List<UserNutritionParam>
): ApiResponse<String> { ): ApiResponse<String> {
return safeApiCall { apiService.postUserNutritionData(param = param) } return safeApiCall { apiService.postUserNutritionData(param = param) }
} }
@@ -1,6 +1,5 @@
package com.sw.dualscreen.sdk package com.sw.dualscreen.sdk
import android.util.Log
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
import com.wabon.wbintelligenthardwaresdk.api.SensorScale import com.wabon.wbintelligenthardwaresdk.api.SensorScale
import com.wabon.wbintelligenthardwaresdk.api.SensorScale.OnScaleResult import com.wabon.wbintelligenthardwaresdk.api.SensorScale.OnScaleResult
@@ -19,7 +18,10 @@ object SensorScaleUtils {
const val baudRate = 115200 const val baudRate = 115200
private var mSensorScale: SensorScale? = null private var mSensorScale: SensorScale? = null
private var isOpened: Boolean = false private var isOpened: Boolean = false
private var isRead: Boolean = false
private var isZero = false
private var callback: Callback? = {} private var callback: Callback? = {}
private var lastWeight: Double? = null
private fun init() { private fun init() {
mSensorScale = SensorScale(object : OnScaleResult { mSensorScale = SensorScale(object : OnScaleResult {
@@ -35,7 +37,8 @@ object SensorScaleUtils {
} }
// Log.d(TAG, "readWeight state = ${stateStr}, weight = $value") // Log.d(TAG, "readWeight state = ${stateStr}, weight = $value")
// 只使用稳定值 // 只使用稳定值
if (state == SensorScale.STATE_STABLE) { if (state == SensorScale.STATE_STABLE && lastWeight != value) {
lastWeight = value
callback?.invoke(value) callback?.invoke(value)
} }
} }
@@ -44,11 +47,11 @@ object SensorScaleUtils {
* 读取鉴别率 * 读取鉴别率
*/ */
override fun readIdentify(rate: Int) { override fun readIdentify(rate: Int) {
Log.e(TAG, "readIdentify rate = $rate") Timber.e("readIdentify rate = $rate")
} }
override fun fail(errCode: Int) { override fun fail(errCode: Int) {
Log.e(TAG, "fail code = $errCode") Timber.e("fail code = $errCode")
} }
}) })
} }
@@ -58,24 +61,24 @@ object SensorScaleUtils {
* @param autoScale 是否开启自动读取 * @param autoScale 是否开启自动读取
*/ */
fun startScale(autoScale: Boolean = true, callback: Callback?) { fun startScale(autoScale: Boolean = true, callback: Callback?) {
Timber.d("startScale isOpened = $isOpened")
if (isOpened) { if (isOpened) {
startContinuousRead(callback = callback) startContinuousRead(callback = callback)
return return
} }
this.callback = callback this.callback = callback
init() init()
mSensorScale?.zero {
Timber.d("startScale: zero")
}
mSensorScale?.openScale(serialPort, baudRate) { open -> mSensorScale?.openScale(serialPort, baudRate) { open ->
isOpened = open isOpened = open
Timber.d("isOpened = $isOpened") Timber.d("isOpened = $isOpened")
if (open) { if (open) {
// zero()
if (autoScale) { if (autoScale) {
Thread({ Thread({
Thread.sleep(1000) Thread.sleep(1000)
// 打开后需要等待后才能调用,否则会 1001 SDK未初始化 // 打开后需要等待后才能调用,否则会 1001 SDK未初始化
mSensorScale?.startContinuousRead() mSensorScale?.startContinuousRead()
}).start() }).start()
} }
} }
@@ -86,23 +89,24 @@ object SensorScaleUtils {
* 开启自动读取重量 * 开启自动读取重量
*/ */
fun startContinuousRead(callback: Callback?) { fun startContinuousRead(callback: Callback?) {
this.callback = callback Timber.d("开启自动读取 = $isRead")
Log.d(TAG, "开启自动读取 = $isOpened") if (isRead) {
if (!isOpened) {
return return
} }
this.callback = callback
mSensorScale?.startContinuousRead() mSensorScale?.startContinuousRead()
isRead = true
} }
/** /**
* 手动读取重量 * 手动读取重量
*/ */
fun readWeight(callback: Callback?) { fun readWeight(callback: Callback?) {
this.callback = callback Timber.d("isOpened = $isOpened")
Log.d(TAG, "isOpened = $isOpened")
if (!isOpened) { if (!isOpened) {
return return
} }
this.callback = callback
mSensorScale?.readWeight() mSensorScale?.readWeight()
} }
@@ -110,9 +114,11 @@ object SensorScaleUtils {
* 零位标定 * 零位标定
*/ */
fun zero() { fun zero() {
if (!isOpened) return Timber.d("zero isZero = $isZero")
if (isZero) return
isZero = true
mSensorScale?.zero { mSensorScale?.zero {
Log.d(TAG, "零位标定操作成功") Timber.d("零位标定操作成功")
ToastUtils.showToast("零位标定操作成功") ToastUtils.showToast("零位标定操作成功")
} }
} }
@@ -123,7 +129,7 @@ object SensorScaleUtils {
fun tare() { fun tare() {
if (!isOpened) return if (!isOpened) return
mSensorScale?.tare { mSensorScale?.tare {
Log.d(TAG, "去皮置零操作成功") Timber.d("去皮置零操作成功")
ToastUtils.showToast("去皮置零操作成功") ToastUtils.showToast("去皮置零操作成功")
} }
} }
@@ -132,18 +138,19 @@ object SensorScaleUtils {
* 停止自动读取重量 * 停止自动读取重量
*/ */
fun stopContinuousRead() { fun stopContinuousRead() {
Log.d(TAG, "isOpened = $isOpened") Timber.d("isOpened = $isOpened")
if (!isOpened) { if (!isOpened) {
return return
} }
mSensorScale?.stopContinuousRead() mSensorScale?.stopContinuousRead()
isRead = false
} }
/** /**
* 关闭称重 * 关闭称重
*/ */
fun closeScale() { fun closeScale() {
Log.d(TAG, "closeScale") Timber.d("closeScale")
isOpened = false isOpened = false
mSensorScale?.closeScale() mSensorScale?.closeScale()
mSensorScale == null mSensorScale == null
@@ -1,5 +1,6 @@
package com.sw.dualscreen.view package com.sw.dualscreen.view
import android.content.DialogInterface
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
@@ -7,6 +8,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@@ -29,6 +31,7 @@ class CustomBottomSheetDialog(
private lateinit var binding: BottomSheetDialogBinding private lateinit var binding: BottomSheetDialogBinding
private lateinit var adapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> private lateinit var adapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
private var checkedItem: FoodInfo? = null private var checkedItem: FoodInfo? = null
private val debouncer = Debouncer(2000)
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@@ -41,13 +44,8 @@ class CustomBottomSheetDialog(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val debouncer = Debouncer(3000)
initView() initView()
binding.ivSearch.setOnClickListener {
debouncer.debounce {
viewModel.searchByFoodName(binding.etSearch.text.toString())
}
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -82,6 +80,7 @@ class CustomBottomSheetDialog(
} }
private fun initView() { private fun initView() {
binding.viewClose.setOnClickListener { dismiss() }
adapter = createAdapter() adapter = createAdapter()
binding.recyclerview.layoutManager = GridLayoutManager(context, 2) binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
// 添加间距装饰(12dp) // 添加间距装饰(12dp)
@@ -93,9 +92,26 @@ class CustomBottomSheetDialog(
) )
) )
binding.recyclerview.adapter = adapter binding.recyclerview.adapter = adapter
binding.ivSearch.setOnClickListener {
searchInfo()
}
binding.etSearch.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchInfo()
true
} else {
false
}
}
registerDataChange() registerDataChange()
} }
private fun searchInfo(){
debouncer.debounce {
viewModel.searchByFoodName(binding.etSearch.text.toString())
}
}
private fun registerDataChange() { private fun registerDataChange() {
lifecycleScope.launch { lifecycleScope.launch {
viewModel.searchFoodInfoList.collect { viewModel.searchFoodInfoList.collect {
@@ -131,7 +147,14 @@ class CustomBottomSheetDialog(
) )
} }
override fun onCancel(dialog: DialogInterface) {
Timber.d("onCancel")
viewModel.cleanSearchFoodInfoList()
super.onCancel(dialog)
}
override fun dismiss() { override fun dismiss() {
Timber.d("dismiss")
viewModel.cleanSearchFoodInfoList() viewModel.cleanSearchFoodInfoList()
super.dismiss() super.dismiss()
} }
@@ -15,6 +15,8 @@ import android.view.animation.AlphaAnimation;
import com.sw.dualscreen.R; import com.sw.dualscreen.R;
import com.sw.plate.utils.AppUtil; import com.sw.plate.utils.AppUtil;
import timber.log.Timber;
public class CustomImageView extends androidx.appcompat.widget.AppCompatImageView { public class CustomImageView extends androidx.appcompat.widget.AppCompatImageView {
private AlphaAnimation alphaAnimation; private AlphaAnimation alphaAnimation;
private String textToDraw; private String textToDraw;
@@ -95,11 +97,22 @@ public class CustomImageView extends androidx.appcompat.widget.AppCompatImageVie
} }
public void setCustomHeightPercent(float percent, boolean isAnimation) { public void setCustomHeightPercent(float percent, boolean isAnimation) {
setCustomHeight((int) (percent * MAX_HEIGHT), isAnimation); Timber.d("setCustomHeightPercent percent = %s", percent);
if (percent<= 0){
percent = 0f;
}
setCustomHeight((int) (percent * MAX_HEIGHT), false);
} }
// 用于设置自定义高度的方法 // 用于设置自定义高度的方法
public void setCustomHeight(int height, boolean isAnimation) { public void setCustomHeight(int height, boolean isAnimation) {
Timber.d("setCustomHeight height = %s", height);
if (height > 0) {
height += 30;
}
if (height > MAX_HEIGHT){
height = MAX_HEIGHT;
}
ViewGroup.LayoutParams layoutParams = getLayoutParams(); ViewGroup.LayoutParams layoutParams = getLayoutParams();
int height1 = layoutParams.height; int height1 = layoutParams.height;
if (height1 != height) { if (height1 != height) {
@@ -68,7 +68,6 @@ abstract class BaseViewModel() : ViewModel() {
GlobalData.restId = equipmentInfo.canteenId!! GlobalData.restId = equipmentInfo.canteenId!!
} }
fun handleError(exception: Exception) { fun handleError(exception: Exception) {
Timber.d("handleError ${exception.message}") Timber.d("handleError ${exception.message}")
} }
@@ -40,7 +40,7 @@ class UserViewModel : BaseViewModel() {
val nutritionData: StateFlow<UserNutritionData?> = _nutritionData val nutritionData: StateFlow<UserNutritionData?> = _nutritionData
/** /**
* 饭点类型 * 饭点类型 早餐/午餐/晚餐
*/ */
private val _dinnerTypeInfo = MutableStateFlow<DinnerTypeInfo?>(null) private val _dinnerTypeInfo = MutableStateFlow<DinnerTypeInfo?>(null)
val dinnerTypeInfo: StateFlow<DinnerTypeInfo?> = _dinnerTypeInfo val dinnerTypeInfo: StateFlow<DinnerTypeInfo?> = _dinnerTypeInfo
@@ -179,6 +179,7 @@ class UserViewModel : BaseViewModel() {
*/ */
fun getUserNutritionData(userId: String, foodId: String) { fun getUserNutritionData(userId: String, foodId: String) {
Timber.d("getUserNutritionData userId = ${userId}, foodId = $foodId") Timber.d("getUserNutritionData userId = ${userId}, foodId = $foodId")
_nutritionData.value = null
launchWithLoading { launchWithLoading {
val response = repository.getUserNutritionData(userId = userId, foodId = foodId) val response = repository.getUserNutritionData(userId = userId, foodId = foodId)
if (parseResponse(response)) { if (parseResponse(response)) {
@@ -200,9 +201,9 @@ class UserViewModel : BaseViewModel() {
/** /**
* 提交就餐数据 * 提交就餐数据
*/ */
fun postUserNutritionData(param: UserNutritionParam) { fun postUserNutritionData(param: List<UserNutritionParam>) {
Timber.d("postUserNutritionData param = $param") Timber.d("postUserNutritionData param = $param")
launchWithLoading { launch {
val response = repository.postUserNutritionData(param) val response = repository.postUserNutritionData(param)
if (parseResponse(response)) { if (parseResponse(response)) {
@@ -11,6 +11,7 @@
android:paddingBottom="0dp"> android:paddingBottom="0dp">
<View <View
android:id="@+id/view_close"
android:layout_width="90dp" android:layout_width="90dp"
android:layout_height="10dp" android:layout_height="10dp"
android:background="@drawable/view_shape_point" /> android:background="@drawable/view_shape_point" />
@@ -41,6 +42,7 @@
android:hint="输入菜品名称" android:hint="输入菜品名称"
android:imeOptions="actionSearch" android:imeOptions="actionSearch"
android:maxLines="1" android:maxLines="1"
android:inputType="text"
android:textColor="#333333" android:textColor="#333333"
android:textColorHint="#ffc8c8c8" android:textColorHint="#ffc8c8c8"
android:textSize="36sp" /> android:textSize="36sp" />
@@ -16,7 +16,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:paddingTop="1dp" android:paddingTop="1dp"
android:visibility="gone"> android:visibility="visible">
<TextureView <TextureView
android:id="@+id/dual_camera_texture_preview_rgb" android:id="@+id/dual_camera_texture_preview_rgb"
@@ -29,8 +29,25 @@
android:id="@+id/dual_camera_face_rect_view" android:id="@+id/dual_camera_face_rect_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout
android:id="@+id/fl_recognize_ir"
android:layout_width="150dp"
android:layout_height="200dp"
android:layout_gravity="bottom">
<TextureView
android:id="@+id/dual_camera_texture_preview_ir"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.sw.plate.utils.arcface.FaceRectView
android:id="@+id/dual_camera_face_rect_view_ir"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</FrameLayout>
<ImageView <ImageView
android:id="@+id/iv_face_bg" android:id="@+id/iv_face_bg"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -38,7 +55,7 @@
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/bg_face" android:background="@drawable/bg_face"
android:contentDescription="相机遮盖层" android:contentDescription="相机遮盖层"
android:visibility="visible" /> android:visibility="gone" />
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -55,7 +72,7 @@
android:visibility="visible"> android:visibility="visible">
<TextView <TextView
android:id="@+id/tv_food_info" android:id="@+id/tv_food_name"
style="@style/text_title_blob_2" style="@style/text_title_blob_2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
+1
View File
@@ -2,6 +2,7 @@
<string name="app_name">档口双屏</string> <string name="app_name">档口双屏</string>
<string name="specific_engine_init_failed">%s 初始化失败,错误码:%d\n错误码常量名:%s</string> <string name="specific_engine_init_failed">%s 初始化失败,错误码:%d\n错误码常量名:%s</string>
<string name="permission_denied">权限被拒绝!</string> <string name="permission_denied">权限被拒绝!</string>
<string name="camera_error_notice">\n可能的原因:该设备不支持同时打开两个摄像头</string>
<array name="detection_method"> <array name="detection_method">
<item>即放即取</item> <item>即放即取</item>
@@ -129,7 +129,8 @@ public class DualCameraHelper implements Camera.PreviewCallback {
//相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置 //相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置
mCameraId = Camera.getNumberOfCameras() - 1; mCameraId = Camera.getNumberOfCameras() - 1;
//若指定了相机ID且该相机存在,则打开指定的相机 //若指定了相机ID且该相机存在,则打开指定的相机
if (specificCameraId != null && specificCameraId <= mCameraId) { // if (specificCameraId != null && specificCameraId <= mCameraId) {
if (specificCameraId != null) {
mCameraId = specificCameraId; mCameraId = specificCameraId;
} }
@@ -158,7 +158,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
private MutableLiveData<String> drawRectInfoText = new MutableLiveData<>(); private MutableLiveData<String> drawRectInfoText = new MutableLiveData<>();
private MutableLiveData<String> recognizeUserId = new MutableLiveData<>(); private MutableLiveData<CompareResult> recognizeUserId = new MutableLiveData<>();
/** /**
* 检测ir活体前,是否需要更新faceData * 检测ir活体前,是否需要更新faceData
@@ -475,7 +475,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
Disposable disposable = Observable.just(true).observeOn(AndroidSchedulers.mainThread()).subscribe(aBoolean -> { Disposable disposable = Observable.just(true).observeOn(AndroidSchedulers.mainThread()).subscribe(aBoolean -> {
if (similarPass) { if (similarPass) {
if (recognizeUserId != null) { if (recognizeUserId != null) {
recognizeUserId.postValue(compareResult.getFaceEntity().getUserName()); recognizeUserId.postValue(compareResult);
} }
boolean isAdded = false; boolean isAdded = false;
List<CompareResult> compareResults = compareResultList.getValue(); List<CompareResult> compareResults = compareResultList.getValue();
@@ -541,7 +541,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback {
return recognizeNotice; return recognizeNotice;
} }
public MutableLiveData<String> getRecognizeUserId() { public MutableLiveData<CompareResult> getRecognizeUserId() {
return recognizeUserId; return recognizeUserId;
} }