This commit is contained in:
2026-02-03 15:00:19 +08:00
parent 6e7eb80715
commit 7596924d75
24 changed files with 1216 additions and 342 deletions
@@ -3,6 +3,7 @@ package com.sw.dualscreen.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Outline
import android.hardware.display.DisplayManager
import android.net.Uri
@@ -21,11 +22,11 @@ import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.common.util.concurrent.ListenableFuture
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.adapter.DialogSearchFoodAdapter
import com.sw.dualscreen.adapter.MainFoodListAdapter
import com.sw.dualscreen.databinding.ActivityMainBinding
import com.sw.dualscreen.dialog.RemindDialog
@@ -63,22 +64,21 @@ import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.json.JSONObject
import timber.log.Timber
import java.io.File
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.roundToInt
/**
@@ -111,7 +111,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
searchFoodList[index].isChecked = index == position
}
val item = searchFoodList[position]
Timber.d("itemClick ${item.foodName}, position = $position")
log("itemClick ${item.foodName}, position = $position")
item.photoUri = null
checkedItem = item
notifyDataSetChanged()
@@ -149,7 +149,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initialize() {
super.initialize()
box = ObjectBox.boxStore.boxFor(Food::class)
isPageVisible = true
addBackEventListener()
initView()
@@ -177,6 +176,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
updateDateTime()
binding.ivSetting.setOnClickListener {
isRefreshPage = false
startActivity(Intent(this, SettingActivity::class.java))
}
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
@@ -221,8 +221,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
pauseAnalysis()
recognizeWeight = lastWeight
Timber.tag(TAG)
.d("registerDataChange,副屏updateFood调用前耗时:${System.currentTimeMillis() - startTime}")
log("registerDataChange,副屏updateFood调用前耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
presentation?.updateFood(foodInfo)
binding.tvFoodName.text = foodInfo.foodName
@@ -239,7 +238,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun registerDataChange() {
super.registerDataChange()
SensorScaleUtils.addWeightListener { weight ->
Timber.d("registerDataChange weight = $weight")
log("registerDataChange weight = $weight")
if (isPageVisible.not() || isStartRecognize.not()) {
if (ActivityManager.currentActivity() is MainActivity) {
isPageVisible = true
@@ -247,7 +246,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
return@addWeightListener
}
val isWeightChange = weight - lastWeight > 0.05
val isWeightChange = abs(weight - lastWeight) > 0.05
log("recognizeByWeight---00001,weight=$weight,lastWeight=$lastWeight,isWeightChange=$isWeightChange")
recognizeByWeight(weight, isWeightChange)
}
}
@@ -256,16 +256,19 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
presentation?.updateWeight(weight)
if (weight <= 0.005) {
log("recognizeByWeight---00002,未超过5克")
////余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
isRecognitionFood = true
return
}
if (isWeightChange && isRecognitionFood) { // 大于50g
log("recognizeByWeight---00003,mealPickupMode=${presentation?.mealPickupMode}")
if (presentation?.mealPickupMode == 1) {
isRecognitionFood = false
}
debouncer.debounce {
log("recognizeByWeight---00004")
recognizeFood()
}
} else {
@@ -282,31 +285,30 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
LightManager.closeRedLight()
LightManager.openGreenLight()
showWaitingDialog("识别中,请稍后……")
takePhoto(
successCallback = { photoUri ->
Thread {
onTakePhotoSuccess(photoUri)
}.start()
failCount = 0
}, failureCallback = {
runOnUiThread {
if (failCount >= 10) {
//ToastUtils.showToast("相机异常,请稍后重试")
shutdownCamera()
setupCamera()
return@runOnUiThread
}
takePhoto(successCallback = { photoUri ->
Thread {
onTakePhotoSuccess(photoUri)
}.start()
failCount = 0
}, failureCallback = {
runOnUiThread {
if (failCount >= 10) {
ToastUtils.showToast("相机异常,请稍后重试")
shutdownCamera()
setupCamera()
return@runOnUiThread
}
failCount++
hideWaitingDialog()
}
failCount++
hideWaitingDialog()
shutdownCamera()
setupCamera()
shutdownCamera()
setupCamera()
binding.root.postDelayed({
recognizeFood()
}, 1000)
})
binding.root.postDelayed({
recognizeFood()
}, 1000)
})
}
/**
@@ -318,7 +320,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun updateFoodInfo(list: MutableList<FoodInfo>, scoreList: List<IdNameScore>) {
val queryData = GsonUtils.toJson(list)
val recData = GsonUtils.toJson(scoreList)
Timber.d("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData")
log("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData")
if (list.isEmpty()) {
binding.tvToSearch.let {
it.text = "未查询到,手动搜索"
@@ -359,24 +361,21 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
private fun setupCamera() {
Timber.d("setupCamera")
log("setupCamera")
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture!!.addListener({
val cameraProvider = cameraProviderFuture!!.get()
// 1. 创建预览用例
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(binding.previewView.surfaceProvider)
}
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(binding.previewView.surfaceProvider)
}
// 2. 创建图像分析用例(用于副屏显示)
imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
// .setTargetResolution(Size(640, 480)) // 降低分辨率减轻负担
.build()
.also { analysis ->
.build().also { analysis ->
analysis.setAnalyzer(executor) { imageProxy ->
// 仅在步骤1时传递
if (isAnalyzing) {
@@ -389,10 +388,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
// 3. 创建拍照用例
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
imageCapture =
ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
// .setTargetResolution(Size(1920, 1080)) // 设置拍照分辨率
.build()
.build()
// 4. 选择摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
@@ -405,20 +404,20 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalysis, imageCapture
)
} catch (exc: Exception) {
Timber.e(exc.message)
} catch (e: Exception) {
log("setupCamera方法异常:${e.message}")
}
}, ContextCompat.getMainExecutor(this))
}
fun pauseAnalysis() {
Timber.d("pauseAnalysis")
log("pauseAnalysis")
isAnalyzing = false
imageAnalysis?.clearAnalyzer()
}
fun resumeAnalysis() {
Timber.d("resumeAnalysis isAnalyzing = $isAnalyzing")
log("resumeAnalysis isAnalyzing = $isAnalyzing")
if (isAnalyzing) return
// isAnalyzing = true
imageAnalysis?.setAnalyzer(executor) { imageProxy ->
@@ -434,7 +433,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
* 停止相机识别
*/
private fun shutdownCamera() {
Timber.d("shutdownCamera")
log("shutdownCamera")
try {
// 1. 首先停止图像分析
imageAnalysis?.clearAnalyzer()
@@ -449,13 +448,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// 4. 停止副屏更新
isAnalyzing = false
} catch (e: Exception) {
Timber.e(e, "Error shutting down camera")
log("Error shutting down camera:${e.message}")
}
}
// 拍照功能
private fun takePhoto(successCallback: (Uri) -> Unit, failureCallback: () -> Unit) {
Timber.d("takePhoto")
log("takePhoto")
// 临时停止分析以避免干扰
isAnalyzing = false
@@ -463,21 +462,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val executor = ContextCompat.getMainExecutor(this)
val cacheDir = cacheDir
val photoFile = File.createTempFile(
"IMG_${System.currentTimeMillis()}",
".jpg",
cacheDir
"IMG_${System.currentTimeMillis()}", ".jpg", cacheDir
)
val cacheOutputOptions =
ImageCapture.OutputFileOptions.Builder(photoFile).build()
val cacheOutputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
// 执行拍照
imageCapture?.takePicture(
cacheOutputOptions,
executor,
object : ImageCapture.OnImageSavedCallback {
cacheOutputOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = outputFileResults.savedUri
Timber.d("takePhoto savedUri = $savedUri")
log("takePhoto savedUri = $savedUri")
if (savedUri != null) {
successCallback(savedUri)
}
@@ -486,14 +480,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
override fun onError(exception: ImageCaptureException) {
Timber.e("拍照失败: ${exception.message}")
log("takePhoto拍照失败: ${exception.message}")
failureCallback()
//ToastUtils.showToast("拍照失败: ${exception.message}")
// 恢复图像分析
isAnalyzing = true
}
}
)
})
}
private fun getSecondaryDisplay(): Display? {
@@ -522,7 +515,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
).apply {
setStepChangeCallback { step ->
if (step == 1) {
Timber.d("setStepChangeCallback${step}")
log("setStepChangeCallback${step}")
updateCurrentFood(null)
bottomSheetDialog?.dismiss()
}
@@ -560,12 +553,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} else {
//副屏未显示重新加载
isRecognitionFood = true
log("onResume-00001")
if (presentation == null || !presentation!!.isShowing) {
log("onResume-00002,副屏未显示,加载副屏")
lastWeight = 0.0
setupSecondaryDisplay()
presentation?.step1FoodRecognizing()
} else {
log("onResume-00003,副屏显示,更新副屏, isRefreshPage=$isRefreshPage")
if (isRefreshPage) {
isRefreshPage = false
//已显示副屏,则进行更新
val weight = lastWeight
lastWeight = 0.0
@@ -576,7 +573,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
recognizeByWeight(weight, true)
}, 500)
}
isRefreshPage = !isRefreshPage
}
}
@@ -627,7 +623,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
notEnoughOneBlock: () -> Unit = {},
block: () -> Unit
) {
Timber.tag(TAG).d("foodWeight=$foodWeight,eatWeight=$eatWeight")
log("foodWeight=$foodWeight,eatWeight=$eatWeight")
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
// if (pickupMode == 0) {
// if (foodWeight <= 5) {
@@ -735,10 +731,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private var isRefreshPage = false
@Subscribe(threadMode = ThreadMode.MAIN)
fun onClickBackEvent(event: ClickBackEvent) {
isRefreshPage = false
}
// @Subscribe(threadMode = ThreadMode.MAIN)
// fun onClickBackEvent(event: ClickBackEvent) {
// isRefreshPage = false
// }
private fun clickPayButton() {
if (checkedItem == null) {
@@ -761,7 +757,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
//余量计量
eatWeight = (recognizeWeight - lastWeight) * 1000
foodWeight = recognizeWeight * 1000
Timber.d("eatWeight=$eatWeight, recognizeWeight=$recognizeWeight, lastWeight=$lastWeight")
log("eatWeight=$eatWeight, recognizeWeight=$recognizeWeight, lastWeight=$lastWeight")
}
} else {
//不计费
@@ -782,7 +778,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
eatNum = eatNum,
userId = null,
) {
// TODO: 暂不关闭首页副屏
isRefreshPage = false
//presentation?.dismiss()
startActivity(Intent(this, PayActivity::class.java).apply {
putExtra(PayActivity.FOOD_INFO, checkedItem!!)
@@ -814,13 +810,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
* 上传菜品信息
*/
private suspend fun uploadCollectFoodPics(
foodInfo: FoodInfo,
imageFile: File?,
imageVector: FloatArray?
foodInfo: FoodInfo, imageFile: File?, imageVector: FloatArray?
) {
if (imageFile == null || imageVector == null) {
Timber.tag(TAG)
.d("是否null判断,imageFile == null:${imageFile == null},imageVector == null:${imageVector == null}")
log("是否null判断,imageFile == null:${imageFile == null},imageVector == null:${imageVector == null}")
return
}
val foodId = foodInfo.foodId
@@ -834,53 +827,45 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val vectors = listOf(imageVector)
val foodVectorList = vectors.map {
it.joinToString(
separator = ",",
prefix = "[",
postfix = "]"
separator = ",", prefix = "[", postfix = "]"
)
}
val foodVectorJson =
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
Timber.tag(TAG).d("json=$foodVectorJson")
log("json=$foodVectorJson")
params["foodVector"] = foodVectorJson.toRequestBody()
val idList = viewModel.uploadCollectFoodPics(files, params)
if (idList.isNullOrEmpty()) {
loadRemindDialog("上传失败,未返回向量数据id")
Timber.tag(TAG).d("idList为空")
log("idList为空")
return
}
Timber.tag(TAG)
.d("uploadCollectFoodPics-已采集向量总数:${box.all.filter { it.isDel.not() }.size}条")
val filterList = box.all.filter { it.collectId == idList[0] }
val all = ObjectBox.getAll()
val size = all.filter { it.isDel.not() }.size
log("uploadCollectFoodPics-已采集向量总数:${size}")
val filterList = all.filter { it.collectId == idList[0] }
if (filterList.isNotEmpty()) {
loadRemindDialog("返回的id${idList[0]}已存在")
return
}
Thread {
box.put(
Food(
collectId = idList[0],
foodId = foodId,
foodName = foodName,
foodVector = imageVector,
version = foodModelVersion
)
ObjectBox.put(
Food(
collectId = idList[0],
foodId = foodId,
foodName = foodName,
foodVector = imageVector,
version = foodModelVersion
)
}.start()
)
}
private fun loadRemindDialog(msg: String) {
runOnUiThread {
RemindDialog(
context = context,
content = msg,
confirmBlock = {}
).show()
context = context, content = msg, confirmBlock = {}).show()
}
}
private lateinit var box: Box<Food>
private fun uri2File(uri: Uri, block: (File?, FloatArray?) -> Unit) {
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
val imageVector = try {
@@ -892,7 +877,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val imageFile = BitmapSaver.saveToAppFilesDir(
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${imageFile?.absolutePath}")
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${imageFile?.absolutePath}")
block(imageFile, imageVector)
if (bitmap.isRecycled.not()) {
bitmap.recycle()
@@ -913,8 +898,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// private val initialDelay = 5 * 60 * 1000L
// private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 60 * 1000L
private val dealyMillis = 30 * 1000L
private val initialDelay = 30 * 1000L
private val dealyMillis = 60 * 1000L
private var taskPageNo = 1
fun startFaceTask() {
faceTaskJob =
@@ -936,8 +921,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
updateFaceData(list)
}
}
)
})
}
}
@@ -952,9 +936,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} else {
//保存数据
val faceEntity = FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
model.userId, null, Base64.decode(model.faceFeatureStr)
).also {
it.userType = "1"
}
@@ -988,66 +970,71 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun onTakePhotoSuccess(photoUri: Uri) {
lastPhotoUri = photoUri
Timber.tag(TAG).d("main,takePhoto耗时:${System.currentTimeMillis() - startTime}")
log("registerDataChange main,takePhoto耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri = ${photoUri.path}")
log("registerDataChange photoUri = ${photoUri.path}")
// viewModel.getIdentifiedFoodList()
ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap ->
Timber.tag(TAG).d("main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri 拿到bitmap")
lifecycleScope.launch {
ImageUtil.uriToBitmap(this@MainActivity, photoUri)?.let { bitmap ->
queryFoodData(bitmap)
}
}
}
private suspend fun queryFoodData(bitmap: Bitmap) {
log("registerDataChange main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
log("registerDataChange photoUri 拿到bitmap")
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
Timber.d("registerDataChange photoUri bitmap裁剪完成")
val file = BitmapSaver.saveToAppFilesDir(
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.tag(TAG)
.d("main,saveToAppFilesDir耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
//val nameList = FoodModule.queryFood(bitmap)
val scoreList = FoodModule.getFoodScoreList(bitmap)
log("registerDataChange registerDataChange photoUri bitmap裁剪完成")
val file = BitmapSaver.saveToAppFilesDir(
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
log("registerDataChange main,saveToAppFilesDir耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
log("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
//val nameList = FoodModule.queryFood(bitmap)
val scoreList = FoodModule.getFoodScoreList(bitmap)
val foodName = if (scoreList.isNotEmpty()) {
val recDataJson = GsonUtils.toJson(scoreList)
Timber.tag(TAG).d("main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
log("registerDataChange main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri 拿到识别数据:$recDataJson")
log("registerDataChange photoUri 拿到识别数据:$recDataJson")
val nameList = scoreList.map { it.name }
val foodName = nameList.joinToString(separator = ",")
nameList.joinToString(separator = ",")
} else ""
hideWaitingDialog()
log("registerDataChange photoUri 识别数据名称:$foodName")
runOnUiThread {
binding.flPay.run {
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
//0-计费,1-不计费
if (chargeMode == 0) visible() else gone()
}
}
if (TextUtils.isEmpty(foodName)) {
LightManager.closeGreenLight()
LightManager.closeRedLight()
//binding.layoutRescan.visibility = View.VISIBLE
hideWaitingDialog()
Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
runOnUiThread {
binding.flPay.run {
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
//0-计费,1-不计费
if (chargeMode == 0) visible() else gone()
binding.tvToSearch.let {
it.text = "未识别到,手动搜索"
it.visible()
}
}
if (TextUtils.isEmpty(foodName)) {
LightManager.closeGreenLight()
LightManager.closeRedLight()
//binding.layoutRescan.visibility = View.VISIBLE
} else {
//binding.layoutRescan.visibility = View.GONE
showWaitingDialog("正在查询菜品信息,请稍后……")
viewModel.getFoodInfo(foodName) { list ->
runOnUiThread {
binding.tvToSearch.let {
it.text = "未识别到,手动搜索"
it.visible()
}
}
} else {
//binding.layoutRescan.visibility = View.GONE
showWaitingDialog("正在查询菜品信息,请稍后……")
viewModel.getFoodInfo(foodName) { list ->
runOnUiThread {
hideWaitingDialog()
Timber.tag(TAG)
.d("main,getFoodInfo耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
updateFoodInfo(list.toMutableList(), scoreList)
}
hideWaitingDialog()
log("registerDataChange main,getFoodInfo耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
updateFoodInfo(list.toMutableList(), scoreList)
}
}
}
}