1443 lines
55 KiB
Kotlin
1443 lines
55 KiB
Kotlin
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.Point
|
||
import android.hardware.Camera
|
||
import android.os.Bundle
|
||
import android.view.Display
|
||
import android.view.View
|
||
import android.view.ViewGroup
|
||
import android.view.ViewTreeObserver
|
||
import android.widget.FrameLayout
|
||
import androidx.core.content.ContextCompat
|
||
import androidx.core.view.isVisible
|
||
import androidx.core.view.updateLayoutParams
|
||
import androidx.lifecycle.Observer
|
||
import androidx.lifecycle.lifecycleScope
|
||
import androidx.recyclerview.widget.LinearLayoutManager
|
||
import com.arcsoft.face.ErrorInfo
|
||
import com.sw.dualscreen.BuildConfig
|
||
import com.sw.dualscreen.GlobalKey
|
||
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.load
|
||
import com.sw.dualscreen.ext.maskName
|
||
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.plate.utils.LightManager
|
||
import com.sw.plate.utils.ToastUtils
|
||
import com.sw.plate.utils.arcface.ConfigUtil
|
||
import com.sw.plate.utils.arcface.ErrorCodeUtil
|
||
import com.sw.plate.utils.arcface.FaceRectTransformer
|
||
import com.sw.plate.utils.arcface.FaceRectView
|
||
import com.sw.plate.utils.arcface.FaceRectView.DrawInfo
|
||
import com.sw.plate.utils.arcface.PreviewConfig
|
||
import com.sw.plate.utils.arcface.camera.CameraListener
|
||
import com.sw.plate.utils.arcface.camera.DualCameraHelper
|
||
import com.sw.plate.utils.arcface.face.constants.LivenessType
|
||
import com.sw.plate.utils.arcface.face.model.CompareResult
|
||
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
|
||
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
|
||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||
import kotlinx.coroutines.flow.drop
|
||
import kotlinx.coroutines.launch
|
||
import timber.log.Timber
|
||
import java.text.DecimalFormat
|
||
import kotlin.math.max
|
||
import kotlin.math.roundToInt
|
||
|
||
class MainScreenPresentation(
|
||
val activity: MainActivity,
|
||
display: Display,
|
||
val userViewModel: UserViewModel,
|
||
val recognizeViewModel: RecognizeViewModel,
|
||
private val onDismissListener: () -> Unit = {}
|
||
) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener {
|
||
|
||
companion object {
|
||
const val TAG = "SubScreenPresentation"
|
||
}
|
||
|
||
private lateinit var binding: PresentationMainScreenBinding
|
||
private lateinit var stepChangeCallback: (Int) -> Unit
|
||
|
||
// 当前步骤
|
||
var currentStep: Int = 0
|
||
|
||
private var isGoStep1 = false;//防止持续调用
|
||
|
||
// 虹软人脸配置 ⬇
|
||
private var isRecognition = false
|
||
private var rgbCameraHelper: DualCameraHelper? = null
|
||
private var rgbFaceRectTransformer: FaceRectTransformer? = null
|
||
private val livenessType = LivenessType.IR
|
||
private var openRectInfoDraw = false
|
||
|
||
private var irCameraHelper: DualCameraHelper? = null
|
||
private var irFaceRectTransformer: FaceRectTransformer? = null
|
||
|
||
private var detectWeight = 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 var recognitionTime: Long = 0L // 人脸识别时的时间
|
||
var recognitionWeight: Int = 0 // 人脸识别时的重量
|
||
|
||
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
|
||
var mealPickupMode: Int = 0 // 取餐模式 0 即放即取 1 余量取餐
|
||
|
||
// var isKeepFaceState = true
|
||
var isKeepFaceState = false
|
||
|
||
var currentUserId: String? = null
|
||
var isMember: Boolean = false
|
||
|
||
var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
|
||
|
||
override fun onCreate(savedInstanceState: Bundle?) {
|
||
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 (activity.settlementMode == 0 || chargeMode == 1) {
|
||
setupArcCamera()
|
||
}
|
||
registerDataChange()
|
||
startTime = System.currentTimeMillis()
|
||
}
|
||
|
||
private var isFirstGetWeight = true
|
||
|
||
fun updateWeight(weight: Int) {
|
||
lastWeight = weight
|
||
Timber.tag(TAG)
|
||
.d("updateWeight lastWeight = $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 <= MainActivity.WEIGHT_RESET_RECOGNIZE && isGoStep1) {
|
||
//人脸识别过程中,秤上物品拿走
|
||
isGoStep1 = false
|
||
activity.runOnUiThread {
|
||
Timber.tag(TAG).e("切回step1")
|
||
step1FoodRecognizing()
|
||
}
|
||
} else if (lastWeight <= MainActivity.WEIGHT_RESET_RECOGNIZE) {
|
||
isGoStep1 = false
|
||
activity.runOnUiThread {
|
||
Timber.tag(TAG).e("切回step1")
|
||
if (mealPickupMode == 0) {
|
||
//即放即取
|
||
step1FoodRecognizing()
|
||
} else {
|
||
//秤上菜品不足,请补充
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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 (mealPickupMode == 0) {
|
||
//即放即取
|
||
calculateNutrition(lastWeight)
|
||
} else {
|
||
//余量计量
|
||
calculateNutrition(recognitionWeight - lastWeight)
|
||
}
|
||
}
|
||
}
|
||
|
||
fun setStepChangeCallback(callback: (Int) -> Unit) {
|
||
stepChangeCallback = callback
|
||
}
|
||
|
||
override fun onGlobalLayout() {
|
||
Timber.tag(TAG).d("onGlobalLayout")
|
||
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this)
|
||
openCamera()
|
||
}
|
||
|
||
private fun openCamera() {
|
||
Timber.tag(TAG).d("openCamera")
|
||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
|
||
PackageManager.PERMISSION_GRANTED
|
||
) {
|
||
ToastUtils.showToast("无摄像头权限")
|
||
return
|
||
}
|
||
try {
|
||
val cameraCount = Camera.getNumberOfCameras()
|
||
if (cameraCount < 3) {
|
||
ToastUtils.showToast("摄像头数量异常")
|
||
return
|
||
}
|
||
recognizeViewModel.init(
|
||
PreviewConfig(
|
||
2, 1, 90, 90
|
||
)
|
||
)
|
||
initRgbCamera()
|
||
if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) {
|
||
initIrCamera()
|
||
}
|
||
} catch (e: Exception) {
|
||
Timber.tag(TAG).e(e, "打开摄像头失败")
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 启用虹软人脸失败
|
||
*/
|
||
fun setupArcCamera() {
|
||
initArcViewModel()
|
||
initArcView()
|
||
openRectInfoDraw = true
|
||
}
|
||
|
||
private fun registerDataChange() {
|
||
activity.lifecycleScope.launch {
|
||
userViewModel.loadFaceResult.collect { needUpdate ->
|
||
if (needUpdate) {
|
||
recognizeViewModel.refreshFaceList()
|
||
}
|
||
}
|
||
}
|
||
// activity.lifecycleScope.launch {
|
||
// userViewModel.nutritionData.collect {
|
||
// Timber.tag(TAG).d("registerDataChange nutritionData = $it")
|
||
// userNutritionData = it
|
||
// if (it == null) return@collect
|
||
//// step3ShowRecognizeResult()
|
||
// //binding.nutritionInclude.tvUserName.text = it.userName.maskName()
|
||
// //binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}"
|
||
//// //updateWeight(lastWeight / 1000)
|
||
// }
|
||
// }
|
||
activity.lifecycleScope.launch {
|
||
userViewModel.dinnerTypeInfo.drop(1).collect {
|
||
Timber.tag(TAG).d("registerDataChange dinnerTypeInfo = $it")
|
||
dinnerTypeInfo = it
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun setTopInfoVisible() {
|
||
binding.llTopInfo.visible()
|
||
binding.flFoodDetail.gone()
|
||
binding.llTopInfo.updateLayoutParams {
|
||
height = activity.screenSize[1] / 2 + 120.dp
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 显示识别出的菜品信息
|
||
*/
|
||
fun step3ShowRecognizeResult(userId: String) {
|
||
Timber.tag(TAG).d("main,step3,耗时:${System.currentTimeMillis() - startTime}")
|
||
LightManager.closeRedLight()
|
||
LightManager.openGreenLight()
|
||
currentStep = 3
|
||
stepChangeCallback(currentStep)
|
||
|
||
setTopInfoVisible()
|
||
//根据接口数据更新热量数据--------------------------
|
||
userViewModel.getUserNutritionData(userId = userId) { nutrition ->
|
||
activity.runOnUiThread {
|
||
if (nutrition == null) {
|
||
activity.hideWaitingDialog()
|
||
ToastUtils.showToast("未查询到营养数据")
|
||
return@runOnUiThread
|
||
}
|
||
this@MainScreenPresentation.userNutrition = nutrition
|
||
loadUnbilledMode()
|
||
|
||
val imageUrl =
|
||
if (currentFood!!.foodImg.isNullOrBlank()) currentFood!!.photoUri else currentFood!!.foodImg
|
||
binding.ivRecImage.let {
|
||
it.visible()
|
||
it.load(imageUrl)
|
||
}
|
||
activity.updateImage(imageUrl)
|
||
activity.hideWaitingDialog()
|
||
}
|
||
}
|
||
|
||
// TODO: 判断是联合支付调用接口查询就餐信息------------------------
|
||
if (activity.settlementMode == 0) {
|
||
loadOrderList(userId)
|
||
}
|
||
}
|
||
|
||
private fun initView() {
|
||
// binding.ivRecImage.let {
|
||
// it.outlineProvider = object : ViewOutlineProvider() {
|
||
// override fun getOutline(view: View, outline: Outline) {
|
||
// outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
|
||
// }
|
||
// }
|
||
// it.clipToOutline = true
|
||
// }
|
||
binding.ivPreviewImage.visible()
|
||
}
|
||
|
||
override fun onDisplayRemoved() {
|
||
super.onDisplayRemoved()
|
||
onDismissListener()
|
||
}
|
||
|
||
|
||
/**
|
||
* 加载待机页面
|
||
*/
|
||
fun loadStandbyScreen() {
|
||
binding.llStandbyPage.visible()
|
||
}
|
||
|
||
fun hideStandbyScreen() {
|
||
binding.llStandbyPage.gone()
|
||
}
|
||
|
||
/**
|
||
* 餐品识别成功,计费模式
|
||
*/
|
||
fun loadBilledMode() {
|
||
binding.calorieInclude.root.visible()
|
||
binding.nutritionInclude.root.gone()
|
||
|
||
binding.flCameraView.visible()
|
||
binding.ivFaceRecMask.gone()
|
||
binding.llCircleArea.gone()
|
||
binding.tvFaceTip.gone()
|
||
binding.tvFoodRecPrompt.gone()
|
||
binding.llPriceInfo.visible()
|
||
//binding.ivRecImage.gone()
|
||
|
||
//updateFoodInfo(foodInfo)
|
||
|
||
// userNutritionData?.let {
|
||
// Timber.tag(TAG).d("loadBilledMode:${GsonUtils.toJson(it)}")
|
||
// }
|
||
currentFood?.let {
|
||
val specPrice = it.specPrice ?: 0.0
|
||
val vipPrice = it.vipPrice ?: 0.0
|
||
binding.tvNormalPrice.text = "${specPrice.format2String(2)} 元/份"
|
||
binding.tvVipPrice.text = "${vipPrice.format2String(2)} 元/份"
|
||
|
||
var calorie = getIntNutritionValue(it.calorie)
|
||
calorie = max(calorie, 0)
|
||
val fat = getIntNutritionValue(it.fat)
|
||
val protein = getIntNutritionValue(it.protein)
|
||
val carbohydrate = getIntNutritionValue(it.carbohydrate)
|
||
|
||
val fatRate = getNutritionRate(fat, calorie)
|
||
val proteinRate = getNutritionRate(protein, calorie)
|
||
val carbohydrateRate = getNutritionRate(carbohydrate, calorie)
|
||
binding.calorieInclude.run {
|
||
tvCalorie.text = "$calorie"
|
||
tvFatRate.text = "$fatRate%"
|
||
tvProteinRate.text = "$proteinRate%"
|
||
tvCarbohydrateRate.text = "$carbohydrateRate%"
|
||
|
||
viewFatLine.updateLayoutParams {
|
||
height = (fatRate / 100.0 * 84).roundToInt() * 1.dp
|
||
}
|
||
viewProteinLine.updateLayoutParams {
|
||
height = (proteinRate / 100.0 * 84).roundToInt() * 1.dp
|
||
}
|
||
viewCarbohydrateLine.updateLayoutParams {
|
||
height = (carbohydrateRate / 100.0 * 84).roundToInt() * 1.dp
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
private fun getNutritionRate(value: Int, total: Int): Int {
|
||
if (value == 0 || total == 0) return 0
|
||
return (100.0 * value / total).roundToInt()
|
||
}
|
||
|
||
private fun getIntNutritionValue(num: Double?): Int {
|
||
return num?.roundToInt() ?: 0
|
||
}
|
||
|
||
private var isLoadUnbilledMode = false
|
||
private val df by lazy { DecimalFormat("#.##") }
|
||
|
||
/**
|
||
* 餐品识别成功,不计费模式
|
||
*/
|
||
fun loadUnbilledMode() {
|
||
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")
|
||
binding.calorieInclude.root.gone()
|
||
binding.nutritionInclude.root.visible()
|
||
|
||
binding.ivPreviewImage.gone()
|
||
binding.flCameraView.visible()
|
||
binding.ivFaceRecMask.gone()
|
||
binding.tvFaceTip.gone()
|
||
binding.llCircleArea.gone()
|
||
binding.tvFoodRecPrompt.gone()
|
||
binding.llPriceInfo.gone()
|
||
//binding.ivRecImage.gone()
|
||
|
||
//updateFoodInfo(foodInfo)
|
||
binding.nutritionInclude.tvUserName.text = userNutrition?.name.maskName()
|
||
val recommendCalorie = userNutrition?.recommendCalorie ?: 0.0
|
||
|
||
binding.nutritionInclude.tvRecommendHeat.text =
|
||
"推荐热量:${df.format(recommendCalorie)}kcal"
|
||
// binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${recommendCalorie.removeTrailingZeros()}kcal"
|
||
//updateWeight(lastWeight / 1000)
|
||
// -------------------------------------------------
|
||
// 删除userNutritionData,改为userNutrition
|
||
calculateNutrition(recognitionWeight - lastWeight)
|
||
binding.root.postDelayed({
|
||
isLoadUnbilledMode = false
|
||
}, 10000)
|
||
|
||
if (isAgainLoadData.not()) {
|
||
//上次已经识别过人脸
|
||
step3ShowRecognizeResult(currentUserId!!)
|
||
isAgainLoadData = true
|
||
}
|
||
}
|
||
|
||
public var isAgainLoadData = false
|
||
|
||
private var isFirstOpen = true
|
||
var startTime = 0L
|
||
|
||
var jointPaymentQueryFinish = false
|
||
|
||
/**
|
||
* 餐品识别中
|
||
*/
|
||
fun step1FoodRecognizing() {
|
||
if (currentStep == 1) {
|
||
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
|
||
//}
|
||
LightManager.closeGreenLight()
|
||
LightManager.closeRedLight()
|
||
|
||
dinnerTypeInfo = null
|
||
currentFood = null
|
||
// userNutritionData = null
|
||
userNutrition = null
|
||
recognitionWeight = 0
|
||
|
||
setTopInfoVisible()
|
||
|
||
binding.calorieInclude.root.gone()
|
||
binding.nutritionInclude.root.gone()
|
||
|
||
binding.tvFoodName.text = "餐品识别中..."
|
||
binding.tvFoodRecPrompt.visible()
|
||
binding.flCameraView.visible()
|
||
binding.ivRecImage.gone()
|
||
binding.ivPreviewImage.visible()
|
||
binding.ivFaceRecMask.gone()
|
||
binding.tvFaceTip.gone()
|
||
binding.llCircleArea.gone()
|
||
binding.llPriceInfo.gone()
|
||
binding.flFace.gone()
|
||
binding.ivFrame.gone()
|
||
binding.flRecognizeIr.gone()
|
||
|
||
pauseCamera()
|
||
activity.clearRecognizeFood()
|
||
|
||
currentStep = 1
|
||
stepChangeCallback(currentStep)
|
||
}
|
||
|
||
// 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()
|
||
// }
|
||
// }
|
||
|
||
/**
|
||
* 用户人脸识别中
|
||
*/
|
||
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()
|
||
|
||
binding.calorieInclude.root.gone()
|
||
binding.nutritionInclude.root.gone()
|
||
|
||
binding.tvFoodName.text = foodInfo.foodName
|
||
binding.flFace.visible()
|
||
binding.flRecognizeIr.visible()
|
||
binding.flCameraView.visible()
|
||
binding.ivFaceRecMask.visible()
|
||
binding.tvFaceTip.visible()
|
||
binding.llCircleArea.visible()
|
||
binding.tvFoodRecPrompt.visible()
|
||
binding.llPriceInfo.gone()
|
||
|
||
binding.ivRecImage.gone()
|
||
binding.ivPreviewImage.gone()
|
||
binding.ivFrame.gone()
|
||
dinnerTypeInfo = null
|
||
// userNutritionData = null
|
||
userNutrition = null
|
||
recognitionWeight = 0
|
||
|
||
stepChangeCallback(currentStep)
|
||
currentFood = foodInfo
|
||
|
||
// userViewModel.getDinnerType()
|
||
|
||
resumeCamera()
|
||
}
|
||
|
||
fun hideRecImage() {
|
||
binding.ivRecImage.gone()
|
||
}
|
||
|
||
// 当前食物信息
|
||
var currentFood: FoodInfo? = null
|
||
fun updateFood(foodInfo: FoodInfo?) {
|
||
//if (isLoadUnbilledMode) return
|
||
Timber.tag(TAG).d("main,updateFood,耗时:${System.currentTimeMillis() - startTime}")
|
||
startTime = System.currentTimeMillis()
|
||
currentFood = foodInfo
|
||
if (foodInfo == null) {
|
||
return
|
||
}
|
||
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 {
|
||
it.visible()
|
||
val imageUrl =
|
||
if (foodInfo.foodImg.isNullOrBlank()) foodInfo.photoUri else foodInfo.foodImg
|
||
it.load(imageUrl)
|
||
}
|
||
} else {
|
||
binding.ivRecImage.gone()
|
||
}
|
||
//独立支付+计费模式,使用人脸识别
|
||
if (activity.settlementMode == 1 && chargeMode == 0) {
|
||
//计费,查询营养信息、价格信息------------------------
|
||
loadBilledMode()
|
||
} else {
|
||
//联合支付,进行人脸识别
|
||
if (activity.settlementMode == 0) {
|
||
step2FaceRecognizing(foodInfo)
|
||
} else {
|
||
//不计费,开启人脸识别
|
||
if (currentStep == 1 || binding.tvFaceTip.isVisible.not()) {
|
||
step2FaceRecognizing(foodInfo)
|
||
}
|
||
}
|
||
}
|
||
//if (currentStep == 1) {
|
||
// step2FaceRecognizing(foodInfo)
|
||
//} else {
|
||
// updateFoodInfo(foodInfo)
|
||
//}
|
||
}
|
||
|
||
private fun updateFoodInfo(foodInfo: FoodInfo) {
|
||
// binding.tvFoodName.text = foodInfo.foodName
|
||
// binding.ivPreviewImage.gone()
|
||
//binding.ivRecImage.visible()
|
||
// if (foodInfo.photoUri != null) {
|
||
// GlideUtils.loadRoundCornerImage(
|
||
// context,
|
||
// url = foodInfo.photoUri,
|
||
// imageView = //binding.ivRecImage,
|
||
// radius = 12
|
||
// )
|
||
// } else {
|
||
// GlideUtils.loadRoundCornerImage(
|
||
// context,
|
||
// url = foodInfo.imgUrl,
|
||
// imageView = //binding.ivRecImage,
|
||
// radius = 12
|
||
// )
|
||
// }
|
||
}
|
||
|
||
private var lastAddWeight = 0
|
||
private fun addFoodToOrder(weight: Int, foodInfo: FoodInfo) {
|
||
if (weight == lastAddWeight) return
|
||
lastAddWeight = weight
|
||
if (weight <= 10) {
|
||
if (foodOrderList.isNotEmpty()) {
|
||
val last = foodOrderList.last()
|
||
if (last.isLocalData) {
|
||
foodOrderList.remove(last)
|
||
foodOrderAdapter.submitList(foodOrderList)
|
||
//foodOrderAdapter.notifyDataSetChanged()
|
||
}
|
||
}
|
||
return
|
||
}
|
||
val foodPrice = if (isMember) foodInfo.vipPrice else foodInfo.specPrice
|
||
val eatNum = activity.getEatNum(weight.toDouble(), foodInfo.specWeight)
|
||
val price = eatNum * (foodPrice?:0.0)
|
||
if (foodOrderList.isNotEmpty()) {
|
||
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()
|
||
}
|
||
|
||
private fun calculateNutrition(value: Int) {
|
||
if (currentFood == null || userNutrition == null) {
|
||
return
|
||
}
|
||
var weight = value
|
||
if (weight < 0) weight = 0
|
||
if (activity.settlementMode == 0) {
|
||
activity.runOnUiThread {
|
||
addFoodToOrder(weight, currentFood!!)
|
||
}
|
||
}
|
||
debouncer.debounce {
|
||
Timber.tag(TAG)
|
||
.d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight")
|
||
val dinnerType = dinnerTypeInfo?.dinnerType
|
||
// Timber.tag(TAG)
|
||
// .d("calculateNutrition foodName = ${currentFood!!.foodName}, dinnerType = $dinnerType, userId = ${userNutritionData!!.userId}")
|
||
val energy: UserEnergy = UserNutritionUtils.calculateNutrition2(
|
||
currentFood!!,
|
||
// userNutritionData!!,
|
||
userNutrition!!,
|
||
weight.toDouble(),
|
||
dinnerType = dinnerType ?: ""
|
||
)
|
||
// Timber.tag(TAG).d("calculateNutrition calcResultInfo = $calcResultInfo")
|
||
// val columnMax = UserNutritionUtils.getMaxInt(calcResultInfo)
|
||
var columnMax = max(energy.grain, energy.fruitsVegetables)
|
||
columnMax = max(columnMax, energy.meatEggs)
|
||
|
||
activity.runOnUiThread {
|
||
val totalKcal = energy.calorie
|
||
val grain = energy.grain
|
||
val fruitsVegetables = energy.fruitsVegetables
|
||
val meatEggs = energy.meatEggs
|
||
|
||
val include = binding.nutritionInclude
|
||
|
||
// 设置热量---------------------------------------------------------------------
|
||
val calorieArray = doubleArrayOf(
|
||
0.0,
|
||
userNutrition?.minCalorie ?: 0.0,
|
||
userNutrition?.recommendCalorie ?: 0.0,
|
||
((userNutrition?.recommendCalorie ?: 0.0) + (userNutrition?.maxCalorie
|
||
?: 0.0)) / 2,
|
||
userNutrition?.maxCalorie ?: 0.0,
|
||
)
|
||
val calorieIndex = UserNutritionUtils.findCalorieIndex(energy.calorie, calorieArray)
|
||
include.customKcalColumn.setImageDrawable(true, calorieIndex)
|
||
var maxKcal = userNutrition?.maxCalorie ?: 1.0
|
||
Timber.tag(TAG).d("maxKcal = $maxKcal")
|
||
// 避免0作为被除数
|
||
maxKcal = if (maxKcal <= 0.0) 1.0 else maxKcal
|
||
Timber.tag(TAG).d("maxKcal 1 = $maxKcal")
|
||
include.totalKcalTv.text = totalKcal.format2String(2)
|
||
var totalKcalHeight = (totalKcal / maxKcal).toFloat()
|
||
if (totalKcalHeight == 0F) {
|
||
include.divKcalView.visible()
|
||
} else {
|
||
include.divKcalView.gone()
|
||
totalKcalHeight += 0.1F
|
||
}
|
||
include.customKcalColumn.setCustomHeightPercent(totalKcalHeight, true)
|
||
|
||
// 设置主食-----------------------------------------------------------
|
||
val grainArray = UserNutritionUtils.getCalorieArray(
|
||
left = userNutrition?.stapleFoodRecommend,
|
||
mid = userNutrition?.stapleFoodNearExcess,
|
||
right = userNutrition?.stapleFoodExcess
|
||
)
|
||
val grainIndex = UserNutritionUtils.findCalorieIndex(energy.grain, grainArray)
|
||
include.customFoodColumn.setImageDrawable(false, grainIndex)
|
||
include.totalFoodTv.text = grain.format2String(2)
|
||
columnMax = if (columnMax <= 0.0) 1.0 else columnMax
|
||
var grainHeight = (grain / columnMax).toFloat()
|
||
if (grainHeight == 0F) {
|
||
include.divTotalFoodView.visible()
|
||
} else {
|
||
include.divTotalFoodView.gone()
|
||
grainHeight += 0.1F
|
||
}
|
||
include.customFoodColumn.setCustomHeightPercent(grainHeight, true)
|
||
|
||
// 设置果蔬-------------------------------------------------------------
|
||
val fruitsArray = UserNutritionUtils.getCalorieArray(
|
||
left = userNutrition?.fruitsVegetablesRecommend,
|
||
mid = userNutrition?.fruitsVegetablesNearExcess,
|
||
right = userNutrition?.fruitsVegetablesExcess
|
||
)
|
||
val fruitsIndex =
|
||
UserNutritionUtils.findCalorieIndex(energy.fruitsVegetables, fruitsArray)
|
||
include.customVegetableColumn.setImageDrawable(false, fruitsIndex)
|
||
include.totalVegetableTv.text = fruitsVegetables.format2String(2)
|
||
var fruitsHeight = (fruitsVegetables / columnMax).toFloat()
|
||
if (fruitsHeight == 0F) {
|
||
include.divVegetableView.visible()
|
||
} else {
|
||
include.divVegetableView.gone()
|
||
fruitsHeight += 0.1F
|
||
}
|
||
include.customVegetableColumn.setCustomHeightPercent(fruitsHeight, true)
|
||
|
||
// 设置肉蛋-----------------------------------------------------------
|
||
val meatEggsArray = UserNutritionUtils.getCalorieArray(
|
||
left = userNutrition?.meatEggsRecommend,
|
||
mid = userNutrition?.meatEggsNearExcess,
|
||
right = userNutrition?.meatEggsExcess
|
||
)
|
||
val meatEggsIndex =
|
||
UserNutritionUtils.findCalorieIndex(energy.meatEggs, meatEggsArray)
|
||
include.customMeatColumn.setImageDrawable(false, meatEggsIndex)
|
||
include.totalMeatTv.text = meatEggs.format2String(2)
|
||
var meatHeight = (meatEggs / columnMax).toFloat()
|
||
if (meatHeight == 0F) {
|
||
include.divMeatView.visible()
|
||
} else {
|
||
include.divMeatView.gone()
|
||
meatHeight += 0.1F
|
||
}
|
||
include.customMeatColumn.setCustomHeightPercent(meatHeight, true)
|
||
}
|
||
}
|
||
}
|
||
|
||
fun updateImage(bitmap: Bitmap) {
|
||
activity.runOnUiThread {
|
||
binding.ivPreviewImage.setImageBitmap(bitmap)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 余量取餐
|
||
*/
|
||
fun updateMealPickupMode(mode: Int) {
|
||
Timber.tag(TAG).d("updateMealPickupMode mode = $mode")
|
||
mealPickupMode = mode
|
||
step1FoodRecognizing()
|
||
}
|
||
|
||
fun stopCamera() {
|
||
rgbCameraHelper?.release()
|
||
rgbCameraHelper = null
|
||
irCameraHelper?.release()
|
||
irCameraHelper = null
|
||
recognizeViewModel.destroy()
|
||
}
|
||
|
||
public override fun onStop() {
|
||
stopCamera()
|
||
super.onStop()
|
||
}
|
||
|
||
fun resumeCamera() {
|
||
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
|
||
isRecognition = true
|
||
if (rgbCameraHelper == null) {
|
||
setupArcCamera()
|
||
rgbCameraHelper!!.start()
|
||
} else if (rgbCameraHelper!!.isStopped) {
|
||
rgbCameraHelper!!.start()
|
||
}
|
||
}
|
||
|
||
fun pauseCamera() {
|
||
Timber.tag(TAG).d("pauseCamera isRecognition = $isRecognition")
|
||
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
|
||
private fun initArcViewModel() {
|
||
recognizeViewModel.setLiveType(livenessType)
|
||
recognizeViewModel.ftInitCode.observe(activity, Observer { ftInitCode: Int? ->
|
||
if (ftInitCode != ErrorInfo.MOK) {
|
||
val error: String? = context.getString(
|
||
R.string.specific_engine_init_failed, "ftEngine",
|
||
ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!)
|
||
)
|
||
Timber.tag(TAG).e("ftInitCode observe = $error")
|
||
ToastUtils.showToast(error)
|
||
}
|
||
})
|
||
recognizeViewModel.frInitCode.observe(activity, Observer { frInitCode: Int? ->
|
||
if (frInitCode != ErrorInfo.MOK) {
|
||
val error: String? = context.getString(
|
||
R.string.specific_engine_init_failed, "frEngine",
|
||
frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!)
|
||
)
|
||
Timber.tag(TAG).e("frInitCode observe = $error")
|
||
ToastUtils.showToast(error)
|
||
}
|
||
})
|
||
recognizeViewModel.flInitCode.observe(activity, Observer { flInitCode: Int? ->
|
||
if (flInitCode != ErrorInfo.MOK) {
|
||
val error: String? = context.getString(
|
||
R.string.specific_engine_init_failed, "flEngine",
|
||
flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!)
|
||
)
|
||
Timber.tag(TAG).e("flInitCode observe = $error")
|
||
ToastUtils.showToast(error)
|
||
}
|
||
})
|
||
|
||
recognizeViewModel.recognizeConfiguration
|
||
.observe(activity, Observer { recognizeConfiguration: RecognizeConfiguration? ->
|
||
Timber.tag(TAG)
|
||
.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}")
|
||
})
|
||
recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? ->
|
||
Timber.tag(TAG).i("recognizeNotice observe notice = $notice")
|
||
})
|
||
|
||
recognizeViewModel.recognizeUserId.observe(
|
||
activity,
|
||
Observer { compareResult: CompareResult ->
|
||
if (currentStep != 2 || System.currentTimeMillis() - recognizeTime <= 2000) {
|
||
//忽略非识别中及两秒内的再次识别
|
||
return@Observer
|
||
}
|
||
recognizeTime = System.currentTimeMillis()
|
||
activity.runOnUiThread {
|
||
activity.showWaitingDialog("加载中,请稍后……")
|
||
}
|
||
Timber.tag(TAG)
|
||
.i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}, step = $currentStep")
|
||
recognitionTime = System.currentTimeMillis()
|
||
recognitionWeight = lastWeight
|
||
lastFaceTrackId = compareResult.trackId
|
||
val faceEntity = compareResult.faceEntity
|
||
val userId = faceEntity.userName
|
||
currentUserId = userId
|
||
isMember = faceEntity.userType == "1"
|
||
isAgainLoadData = false
|
||
if (userId == null) return@Observer
|
||
//ToastUtils.showToast("人脸识别成功,userId = $userId")
|
||
if (currentFood == null) {
|
||
currentFood = activity.checkedItem
|
||
}
|
||
if (currentFood == null) {
|
||
activity.runOnUiThread {
|
||
activity.hideWaitingDialog()
|
||
ToastUtils.showToast("请选择菜品")
|
||
return@runOnUiThread
|
||
}
|
||
}
|
||
step3ShowRecognizeResult(userId)
|
||
})
|
||
|
||
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
|
||
Timber.tag(TAG).i("drawRectInfoText observe info = $info")
|
||
})
|
||
}
|
||
|
||
private fun initArcView() {
|
||
//在布局结束后才做初始化操作
|
||
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
|
||
recognizeViewModel.getCompareResultList().getValue()
|
||
}
|
||
|
||
/**
|
||
* 调整View的宽高,使预览显示正常且采集框固定为
|
||
*
|
||
* @param rgbPreview RGB预览View
|
||
* @param previewView 显示预览数据的view
|
||
* @param faceRectView 画框的view
|
||
* @param previewSize 预览大小
|
||
* @param displayOrientation 相机旋转角度
|
||
* @param scale 缩放比例
|
||
* @return 调整后的LayoutParams
|
||
*/
|
||
private fun adjustPreviewViewSize(
|
||
rgbPreview: View,
|
||
previewView: View,
|
||
faceRectView: FaceRectView,
|
||
previewSize: Camera.Size,
|
||
displayOrientation: Int,
|
||
scale: Float
|
||
): ViewGroup.LayoutParams {
|
||
val w = 600.dp
|
||
val h = 1068.dp
|
||
val layoutParams = FrameLayout.LayoutParams(w, h);
|
||
previewView.setLayoutParams(layoutParams)
|
||
faceRectView.setLayoutParams(layoutParams)
|
||
return layoutParams
|
||
}
|
||
|
||
private fun initRgbCamera() {
|
||
val cameraListener: CameraListener = object : CameraListener {
|
||
override fun onCameraOpened(
|
||
camera: Camera,
|
||
cameraId: Int,
|
||
displayOrientation: Int,
|
||
isMirror: Boolean
|
||
) {
|
||
Timber.tag(TAG)
|
||
.d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
|
||
activity.runOnUiThread({
|
||
val previewSizeRgb = camera.getParameters().getPreviewSize()
|
||
val layoutParams = adjustPreviewViewSize(
|
||
binding.dualCameraTexturePreviewRgb,
|
||
binding.dualCameraTexturePreviewRgb,
|
||
binding.dualCameraFaceRectView,
|
||
previewSizeRgb, displayOrientation,
|
||
0.5F
|
||
//0.6F
|
||
)
|
||
|
||
Timber.tag(TAG)
|
||
.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
|
||
Timber.tag(TAG)
|
||
.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
|
||
Timber.tag(TAG).d(
|
||
"initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${
|
||
ConfigUtil.isDrawRgbRectHorizontalMirror(
|
||
context
|
||
)
|
||
}, isDrawRgbRectVerticalMirror = ${
|
||
ConfigUtil.isDrawRgbRectVerticalMirror(
|
||
context
|
||
)
|
||
}"
|
||
)
|
||
// 调整识别窗口位置
|
||
rgbFaceRectTransformer = FaceRectTransformer(
|
||
previewSizeRgb.width,
|
||
previewSizeRgb.height,
|
||
layoutParams.width,
|
||
layoutParams.height,
|
||
90,
|
||
cameraId,
|
||
isMirror,
|
||
true,
|
||
true
|
||
)
|
||
|
||
recognizeViewModel.onRgbCameraOpened(camera)
|
||
recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer)
|
||
})
|
||
}
|
||
|
||
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
|
||
if (!isRecognition) {
|
||
return
|
||
}
|
||
binding.dualCameraFaceRectView.clearFaceInfo()
|
||
facePreviewInfoList = recognizeViewModel.onPreviewFrame(nv21, true)
|
||
if (facePreviewInfoList != null && rgbFaceRectTransformer != null) {
|
||
drawPreviewInfo(facePreviewInfoList!!)
|
||
}
|
||
if (facePreviewInfoList.isNullOrEmpty()) {
|
||
currentUserId = null
|
||
isMember = false
|
||
}
|
||
//recognizeViewModel.clearLeftFace(facePreviewInfoList)
|
||
clearLeftFaceData()
|
||
}
|
||
|
||
override fun onCameraClosed() {
|
||
Timber.tag(TAG).i("initRgbCamera onCameraClosed: ")
|
||
}
|
||
|
||
override fun onCameraError(e: java.lang.Exception) {
|
||
Timber.tag(TAG).i("initRgbCamera onCameraError: %s", e.message)
|
||
e.printStackTrace()
|
||
}
|
||
|
||
override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
|
||
Timber.tag(TAG)
|
||
.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}")
|
||
if (rgbFaceRectTransformer != null) {
|
||
rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
|
||
}
|
||
Timber.tag(TAG)
|
||
.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation")
|
||
}
|
||
}
|
||
val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth
|
||
val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight
|
||
Timber.tag(TAG)
|
||
.i("initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight")
|
||
|
||
val previewConfig: PreviewConfig = recognizeViewModel.previewConfig
|
||
rgbCameraHelper = DualCameraHelper.Builder()
|
||
.previewViewSize(Point(measuredWidth, measuredHeight))
|
||
// .previewViewSize(Point(608.dp, 456.dp))
|
||
// .previewViewSize(Point(1024, 768))
|
||
.rotation(activity.windowManager.defaultDisplay.rotation)
|
||
.additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度
|
||
.previewSize(recognizeViewModel.loadPreviewSize())
|
||
// .previewSize(Point(1080, 720))
|
||
.specificCameraId(previewConfig.rgbCameraId)
|
||
.isMirror(true)
|
||
.previewOn(binding.dualCameraTexturePreviewRgb)
|
||
.cameraListener(cameraListener)
|
||
.build()
|
||
rgbCameraHelper!!.init()
|
||
// rgbCameraHelper!!.start()
|
||
}
|
||
|
||
/**
|
||
* 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用
|
||
*/
|
||
private fun initIrCamera() {
|
||
Timber.tag(TAG).d("initIrCamera: livenessType = $livenessType")
|
||
if (livenessType === LivenessType.RGB) {
|
||
return
|
||
}
|
||
val irCameraListener: CameraListener = object : CameraListener {
|
||
override fun onCameraOpened(
|
||
camera: Camera,
|
||
cameraId: Int,
|
||
displayOrientation: Int,
|
||
isMirror: Boolean
|
||
) {
|
||
Timber.tag(TAG)
|
||
.d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
|
||
val previewSizeIr = camera.getParameters().getPreviewSize()
|
||
val layoutParams = adjustPreviewViewSize(
|
||
binding.dualCameraTexturePreviewRgb,
|
||
binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr,
|
||
previewSizeIr, displayOrientation, 0.25f
|
||
)
|
||
|
||
irFaceRectTransformer = FaceRectTransformer(
|
||
previewSizeIr.width, previewSizeIr.height,
|
||
// layoutParams.width, layoutParams.height,
|
||
layoutParams.width, layoutParams.height,
|
||
displayOrientation, cameraId, isMirror,
|
||
ConfigUtil.isDrawIrRectHorizontalMirror(context),
|
||
ConfigUtil.isDrawIrRectVerticalMirror(context)
|
||
)
|
||
|
||
recognizeViewModel.onIrCameraOpened(camera)
|
||
recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer)
|
||
}
|
||
|
||
|
||
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
|
||
recognizeViewModel.refreshIrPreviewData(nv21)
|
||
}
|
||
|
||
override fun onCameraClosed() {
|
||
Timber.tag(TAG).i("initIrCamera onCameraClosed: ")
|
||
}
|
||
|
||
override fun onCameraError(e: java.lang.Exception) {
|
||
Timber.tag(TAG).i("initIrCamera onCameraError: ${e.message}")
|
||
e.printStackTrace()
|
||
}
|
||
|
||
override fun onCameraConfigurationChanged(
|
||
cameraID: Int,
|
||
displayOrientation: Int
|
||
) {
|
||
if (irFaceRectTransformer != null) {
|
||
irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
|
||
}
|
||
Timber.tag(TAG)
|
||
.i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation")
|
||
}
|
||
}
|
||
|
||
val previewConfig = recognizeViewModel.previewConfig
|
||
irCameraHelper = DualCameraHelper.Builder()
|
||
.previewViewSize(
|
||
Point(
|
||
binding.dualCameraTexturePreviewIr.measuredWidth,
|
||
binding.dualCameraTexturePreviewIr.measuredHeight
|
||
)
|
||
)
|
||
// .previewViewSize(Point(133,100))
|
||
.rotation(activity.windowManager.defaultDisplay.rotation)
|
||
.specificCameraId(previewConfig.irCameraId)
|
||
.previewOn(binding.dualCameraTexturePreviewIr)
|
||
.cameraListener(irCameraListener)
|
||
.isMirror(true)
|
||
.previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小
|
||
.additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度
|
||
.build()
|
||
irCameraHelper!!.init()
|
||
try {
|
||
irCameraHelper!!.start()
|
||
} catch (e: RuntimeException) {
|
||
ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice))
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 绘制RGB、IR画面的实时人脸信息
|
||
*
|
||
* @param facePreviewInfoList RGB画面的实时人脸信息
|
||
*/
|
||
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo>) {
|
||
// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
|
||
if (rgbFaceRectTransformer != null) {
|
||
val rgbDrawInfoList: MutableList<DrawInfo>? = recognizeViewModel.getDrawInfo(
|
||
facePreviewInfoList,
|
||
LivenessType.RGB,
|
||
openRectInfoDraw
|
||
)
|
||
// 识别成功
|
||
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList)
|
||
}
|
||
|
||
//binding.dualCameraFaceRectView.clearFaceInfo()
|
||
//clearLeftFaceData()
|
||
|
||
val listIsEmpty = facePreviewInfoList.isEmpty()
|
||
val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0].trackId else null
|
||
Timber.tag(TAG)
|
||
.d("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 用户离开")
|
||
lastFaceTrackId = -1
|
||
Timber.tag(TAG)
|
||
.d("postUserData userNutritionData是否null: ${userNutrition == null}, currentFood是否null: ${currentFood == null},currentUserId是否null:${currentUserId == null}")
|
||
if (userNutrition == null || currentFood == null || currentUserId == null) {
|
||
return
|
||
}
|
||
createOrder(notEnoughOneBlock = {
|
||
if (activity.settlementMode == 0) {
|
||
setTopInfoVisible()
|
||
}
|
||
// if (mealPickupMode == 0) {
|
||
currentStep = 2
|
||
step2FaceRecognizing(currentFood!!)
|
||
// }
|
||
}, successBlock = {
|
||
// TODO: 测试人脸识别问题--------------------
|
||
//recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
|
||
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 {
|
||
if (mealPickupMode == 1) {
|
||
step2FaceRecognizing(currentFood!!)
|
||
isKeepFaceState = false
|
||
if (activity.settlementMode == 0) {
|
||
setTopInfoVisible()
|
||
}
|
||
} else {
|
||
activity.isAnalyzing = false
|
||
step1FoodRecognizing()
|
||
}
|
||
// }
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 人脸离开后调用创建订单接口
|
||
*/
|
||
fun createOrder(notEnoughOneBlock: () -> Unit = {}, successBlock: () -> Unit) {
|
||
//0-即放即取,1-余量计量
|
||
mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||
//if (settlementMode == 0) {
|
||
// //使用联合支付方式
|
||
// ToastUtils.showToast("使用联合支付方式")
|
||
// return
|
||
//}
|
||
val eatWeight = if (mealPickupMode == 0) {
|
||
//即放即取
|
||
recognitionWeight
|
||
} else {
|
||
recognitionWeight - lastWeight
|
||
}
|
||
//val userNutritionParam = UserNutritionParam(
|
||
// userId = userNutritionData?.userId!!,
|
||
// foodId = currentFood?.foodId!!,
|
||
// faceTime = recognitionTime,
|
||
// faceEndTime = System.currentTimeMillis(),
|
||
// eatWeight = eatWeight,//lastWeight
|
||
// 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,
|
||
eatWeight = eatWeight,
|
||
eatNum = eatNum,
|
||
userId = currentUserId,
|
||
isMember = isMember,
|
||
notEnoughOneBlock = notEnoughOneBlock,
|
||
successBlock = {
|
||
activity.runOnUiThread {
|
||
successBlock()
|
||
}
|
||
}
|
||
)
|
||
}
|
||
|
||
public fun clearLeftFaceData() {
|
||
facePreviewInfoList?.let {
|
||
recognizeViewModel.clearLeftFace(facePreviewInfoList!!)
|
||
}
|
||
facePreviewInfoList = null
|
||
}
|
||
|
||
fun hideNutritionView(chargeMode: Int) {
|
||
binding.nutritionInclude.root.gone()
|
||
binding.calorieInclude.root.gone()
|
||
binding.tvFoodRecPrompt.visible()
|
||
|
||
binding.ivRecImage.gone()
|
||
binding.llPriceInfo.gone()
|
||
binding.ivPreviewImage.visible()
|
||
binding.ivFrame.visible()
|
||
|
||
// if (chargeMode == 1) {
|
||
// //不计费
|
||
//// binding.flFace.visible()
|
||
// binding.ivRecImage.gone()
|
||
// binding.llPriceInfo.gone()
|
||
// binding.ivPreviewImage.gone()
|
||
// binding.ivFrame.gone()
|
||
// } else {
|
||
// //计费
|
||
// binding.flFace.gone()
|
||
// binding.llPriceInfo.gone()
|
||
//// val imageUrl = if (currentFood!!.foodImg.isNullOrBlank()) currentFood!!.photoUri else currentFood!!.foodImg
|
||
//// binding.ivRecImage.let {
|
||
//// it.visible()
|
||
//// it.load(imageUrl)
|
||
//// }
|
||
// binding.ivRecImage.gone()
|
||
// binding.ivPreviewImage.visible()
|
||
// binding.ivFrame.visible()
|
||
// }
|
||
}
|
||
|
||
private var foodOrderList = mutableListOf<FoodItem>()
|
||
private val foodOrderAdapter by lazy {
|
||
FoodOrderAdapter(foodOrderList)
|
||
}
|
||
|
||
/**
|
||
* 加载订单列表
|
||
*/
|
||
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
|
||
private fun loadOrderList(userId: String) {
|
||
binding.llTopInfo.gone()
|
||
binding.flFoodDetail.visible()
|
||
binding.detailInclude.let {
|
||
it.root.updateLayoutParams {
|
||
height = activity.screenSize[1] / 2 + 120.dp
|
||
}
|
||
it.rvFoodList.run {
|
||
if (adapter == null) {
|
||
layoutManager =
|
||
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
|
||
adapter = foodOrderAdapter
|
||
}
|
||
}
|
||
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}克"
|
||
binding.detailInclude.tvTotalCalorie.text =
|
||
"总热量:${model.calorie.roundToInt()}千卡"
|
||
jointPaymentQueryFinish = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构造符合分辨率的 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)
|
||
|
||
} |