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 import android.os.Handler import android.os.Looper import android.text.TextUtils import android.view.Display import android.view.View import android.view.ViewOutlineProvider import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture 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.MainFoodListAdapter import com.sw.dualscreen.databinding.ActivityMainBinding import com.sw.dualscreen.dialog.RemindDialog import com.sw.dualscreen.ext.dp import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.load import com.sw.dualscreen.ext.visible import com.sw.dualscreen.model.response.ChargeModeEvent import com.sw.dualscreen.model.response.ClickBackEvent import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodOrder import com.sw.dualscreen.model.response.PaySuccessEvent import com.sw.dualscreen.model.response.UpdateRefreshEvent import com.sw.dualscreen.model.response.UserFaceModel import com.sw.dualscreen.objbox.Food import com.sw.dualscreen.objbox.FoodModule import com.sw.dualscreen.objbox.FoodModule.IdNameScore import com.sw.dualscreen.objbox.ObjectBox import com.sw.dualscreen.presentation.MainScreenPresentation import com.sw.dualscreen.sdk.SensorScaleUtils import com.sw.dualscreen.socket.TcpClient import com.sw.dualscreen.utils.ActivityManager import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.GsonUtils import com.sw.dualscreen.utils.ImageUtil import com.sw.dualscreen.utils.IntervalExecutor import com.sw.dualscreen.utils.SPUtil import com.sw.dualscreen.utils.SpTool import com.sw.dualscreen.view.CustomBottomSheetDialog import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.plate.utils.Base64 import com.sw.plate.utils.LightManager 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 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 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 /** * 服务员显示界面 */ @SuppressLint("NotifyDataSetChanged") class MainActivity : BaseActivity() { companion object { private const val TAG = "MainActivity" const val TIME_OUT = 5 * 60 * 1000L } private val viewModel by viewModels() private var imageCapture: ImageCapture? = null var isAnalyzing = true // 控制是否进行图像分析 // private var presentation: SecondaryScreenPresentation? = null private var presentation: MainScreenPresentation? = null private val executor = Executors.newSingleThreadExecutor() private val handler by lazy { Handler(Looper.getMainLooper()) } private val searchFoodList = mutableListOf() private val adapter by lazy { MainFoodListAdapter(searchFoodList).apply { setOnItemClickListener { adapter, view, position -> if (searchFoodList[position].isChecked) { return@setOnItemClickListener } searchFoodList.forEachIndexed { index, info -> searchFoodList[index].isChecked = index == position } val item = searchFoodList[position] log("itemClick ${item.foodName}, position = $position") item.photoUri = null checkedItem = item notifyDataSetChanged() updateCurrentFood(item) val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) //不计费模式切换菜品,重新加载数据 if (mode == 1) { presentation?.isAgainLoadData = false presentation?.loadUnbilledMode() } } } } var checkedItem: FoodInfo? = null private var imageAnalysis: ImageAnalysis? = null private var cameraProviderFuture: ListenableFuture? = null private var canIdentify: Boolean = false private var bottomSheetDialog: CustomBottomSheetDialog? = null private var lastWeight: Double = 0.0 private var lastPhotoUri: Uri? = null // 最后拍照的图片 private var debouncer = Debouncer(2000) private var isRecognitionFood = true private var isFirstOpen = true private var isStartRecognize = false override fun getViewModel(): BaseViewModel { return viewModel } override fun inflateViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) } override fun initialize() { super.initialize() isPageVisible = true addBackEventListener() initView() if (SpTool.firstGetFace) { viewModel.getUserFaceCache(pageNo = 1) } setupCamera() initData() registerKeyEvent() } private fun initData() { startFaceTask() } private fun initView() { viewModel.getDeviceConfig { deviceConfig -> runOnUiThread { if (deviceConfig == null) { ToastUtils.showToast("获取设备配置数据失败") return@runOnUiThread } GlobalData.appId = deviceConfig.arcsoftAppId ?: "" GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: "" GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: "" viewModel.activeEngine() } } updateDateTime() binding.ivSetting.setOnClickListener { isRefreshPage = false startActivity(Intent(this, SettingActivity::class.java)) } binding.previewView.outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setRoundRect(0, 0, view.width, view.height, 12f.dp) } } binding.previewView.clipToOutline = true binding.tvToSearch.setOnClickListener { bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) { checkedItem = it it.photoUri = null updateCurrentFood(it) searchFoodList.forEachIndexed { index, info -> searchFoodList[index].isChecked = false } adapter.notifyDataSetChanged() } bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog") } binding.recyclerview.let { it.layoutManager = GridLayoutManager(this, 2) it.adapter = adapter } binding.tvFoodName.setOnClickListener { SensorScaleUtils.zero() } binding.btnPay.setOnClickListener { clickPayButton() } } private var recognizeWeight = 0.0 fun updateCurrentFood(foodInfo: FoodInfo?) { if (foodInfo == null) { binding.tvFoodName.text = "-" binding.previewView.visibility = View.VISIBLE binding.ivImg.visibility = View.GONE resumeAnalysis() return } pauseAnalysis() recognizeWeight = lastWeight log("registerDataChange,副屏updateFood调用前耗时:${System.currentTimeMillis() - startTime}") startTime = System.currentTimeMillis() presentation?.updateFood(foodInfo) binding.tvFoodName.text = foodInfo.foodName binding.previewView.gone() binding.ivImg.visible() binding.ivImg.load(foodInfo.foodImg) //pauseAnalysis() } fun updateImage(imgUrl: Any?) { binding.ivImg.load(imgUrl) } override fun registerDataChange() { super.registerDataChange() SensorScaleUtils.addWeightListener { weight -> log("registerDataChange weight = $weight") //runOnUiThread { binding.tvShowWeight.text="${weight*1000}克" } if (lastWeight - weight > 0.005) { //及放及取,从秤上拿走超过5克 if (presentation?.mealPickupMode == 0) { presentation?.updateWeight(weight) lastWeight = weight return@addWeightListener } } if (isPageVisible.not() || isStartRecognize.not()) { if (ActivityManager.currentActivity() is MainActivity) { isPageVisible = true isStartRecognize = true } return@addWeightListener } //0.05 // val isWeightChange = abs(weight - lastWeight) > 0.02 val isWeightChange = weight - lastWeight > 0.02 log("recognizeByWeight---00001,weight=$weight,lastWeight=$lastWeight,isWeightChange=$isWeightChange") recognizeByWeight(weight, isWeightChange) } } private fun recognizeByWeight(weight: Double, isWeightChange: Boolean, block: () -> Unit = {}) { 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() if (presentation?.mealPickupMode == 0) { lastWeight = weight } } } else { block() } if (presentation?.mealPickupMode == 1) { lastWeight = weight } } private var startTime = 0L fun recognizeFood() { startTime = System.currentTimeMillis() // LightManager.openRedLight() LightManager.closeRedLight() LightManager.openGreenLight() showWaitingDialog("识别中,请稍后……") takePhoto(successCallback = { photoUri -> Thread { onTakePhotoSuccess(photoUri) }.start() failCount = 0 }, failureCallback = { //ToastUtils.showToast("拍照异常,请重试") failCount++ hideWaitingDialog() shutdownCamera() setupCamera() binding.root.postDelayed({ recognizeFood() }, 1000) }) } /** * 识别相机异常次数 */ private var failCount = 0 @SuppressLint("NotifyDataSetChanged") private fun updateFoodInfo(list: MutableList, scoreList: List) { val queryData = GsonUtils.toJson(list) val recData = GsonUtils.toJson(scoreList) log("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData") if (list.isEmpty()) { binding.tvToSearch.let { it.text = "未查询到,手动搜索" it.visible() } return } list.forEach { foodInfo -> val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName } val score = scoreItem?.score ?: 0.0 foodInfo.score = ((1 - score) * 10000).roundToInt() } //接口已处理排序 // val list2 = try { // val orderList = scoreList.map { it.name.trim() } // list.sortedBy { orderList.indexOf(it.foodName) } // } catch (e: Exception) { // e.printStackTrace() // list // } searchFoodList.clear() searchFoodList.addAll(list) adapter.notifyDataSetChanged() checkedItem = list[0].also { it.photoUri = lastPhotoUri it.isChecked = true } adapter.notifyItemChanged(0) updateCurrentFood(checkedItem) // lastPhotoUri = null LightManager.closeRedLight() binding.tvToSearch.let { it.text = "以上都不是,手动搜索" it.visible() } } private fun 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) } // 2. 创建图像分析用例(用于副屏显示) imageAnalysis = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // .setTargetResolution(Size(640, 480)) // 降低分辨率减轻负担 .build().also { analysis -> analysis.setAnalyzer(executor) { imageProxy -> // 仅在步骤1时传递 if (isAnalyzing) { val bitmap = imageProxy.toBitmap() presentation?.updateImage(bitmap) // 更新副屏 isStartRecognize = true } imageProxy.close() // 必须关闭以释放资源 } } // 3. 创建拍照用例 imageCapture = ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // .setTargetResolution(Size(1920, 1080)) // 设置拍照分辨率 .build() // 4. 选择摄像头 val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // 解绑所有用例 cameraProvider.unbindAll() // 绑定三个用例到生命周期 cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture ) } catch (e: Exception) { log("setupCamera方法异常:${e.message}") } }, ContextCompat.getMainExecutor(this)) } fun pauseAnalysis() { log("pauseAnalysis") isAnalyzing = false imageAnalysis?.clearAnalyzer() } fun resumeAnalysis() { log("resumeAnalysis isAnalyzing = $isAnalyzing") if (isAnalyzing) return // isAnalyzing = true imageAnalysis?.setAnalyzer(executor) { imageProxy -> if (presentation?.currentStep == 1) { val bitmap = imageProxy.toBitmap() presentation?.updateImage(bitmap) } imageProxy.close() } } /** * 停止相机识别 */ private fun shutdownCamera() { log("shutdownCamera") try { // 1. 首先停止图像分析 imageAnalysis?.clearAnalyzer() // 2. 解除所有绑定 cameraProviderFuture?.get()?.unbindAll() // 3. 关闭相关资源 imageCapture = null imageAnalysis = null // 4. 停止副屏更新 isAnalyzing = false } catch (e: Exception) { log("Error shutting down camera:${e.message}") } } // 拍照功能 private fun takePhoto(successCallback: (Uri) -> Unit, failureCallback: () -> Unit) { log("takePhoto") // 临时停止分析以避免干扰 isAnalyzing = false // 创建输出选项 val executor = ContextCompat.getMainExecutor(this) val cacheDir = cacheDir val photoFile = File.createTempFile( "IMG_${System.currentTimeMillis()}", ".jpg", cacheDir ) val cacheOutputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() // 执行拍照 imageCapture?.takePicture( cacheOutputOptions, executor, object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { val savedUri = outputFileResults.savedUri log("takePhoto savedUri = $savedUri") if (savedUri != null) { successCallback(savedUri) } // 恢复图像分析 isAnalyzing = true } override fun onError(exception: ImageCaptureException) { log("takePhoto拍照失败: ${exception.message}") failureCallback() //ToastUtils.showToast("拍照失败: ${exception.message}") // 恢复图像分析 isAnalyzing = true } }) } private fun getSecondaryDisplay(): Display? { val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val displays = displayManager.displays // 查找副屏(通常索引为1) if (displays.size > 1) return displays[1] return null } private val recognizeViewModel by lazy { viewModels().value } private fun setupSecondaryDisplay() { val secondaryDisplay = getSecondaryDisplay() if (secondaryDisplay == null) { ToastUtils.showToast("获取副屏失败") return } // 创建副屏的Presentation presentation = MainScreenPresentation( activity = this, display = secondaryDisplay, userViewModel = viewModel, recognizeViewModel = recognizeViewModel ).apply { setStepChangeCallback { step -> if (step == 1) { log("setStepChangeCallback${step}") updateCurrentFood(null) bottomSheetDialog?.dismiss() } } show() } } @SuppressLint("NotifyDataSetChanged") fun clearFoodList() { //adapter.updateData(listOf()) searchFoodList.clear() adapter.notifyDataSetChanged() } private fun updateDateTime() { val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 E", Locale.CHINA) val dateTime = formatter.format(LocalDateTime.now(ZoneId.of("Asia/Shanghai"))) binding.tvTitleTime.text = dateTime } public var isPageVisible = true override fun onResume() { super.onResume() isPageVisible = true // 启动定时器 handler.postDelayed(timeoutRunnable, TIME_OUT) // ToastUtils.showToast("isPageVisible=$isPageVisible") if (isFirstOpen) { //首次 setupSecondaryDisplay() presentation?.step1FoodRecognizing() isFirstOpen = false } else { //副屏未显示重新加载 isRecognitionFood = true log("onResume-00001,isRefreshPage=$isRefreshPage") 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 presentation?.hideRecImage() presentation?.step1FoodRecognizing() binding.root.postDelayed({ recognizeByWeight(weight, true) }, 500) } } } //根据付费模式确认是否显示按钮 binding.btnPay.run { val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) if (mode == 0) visible() else gone() } } override fun onPause() { super.onPause() isPageVisible = false // 取消定时器 handler.removeCallbacks(timeoutRunnable) } private var lastTouchTime = 0L private val timeoutRunnable = Runnable { //显示待机页面 presentation?.loadStandbyScreen() } override fun onUserInteraction() { super.onUserInteraction() if (System.currentTimeMillis() - lastTouchTime >= TIME_OUT) { //隐藏待机页面 presentation?.hideStandbyScreen() } // 重置定时器 handler.removeCallbacks(timeoutRunnable) handler.postDelayed(timeoutRunnable, TIME_OUT) lastTouchTime = System.currentTimeMillis() } fun resetTouchTime() { handler.removeCallbacks(timeoutRunnable) handler.postDelayed(timeoutRunnable, TIME_OUT) lastTouchTime = System.currentTimeMillis() } fun createOrder( foodInfo: FoodInfo, foodWeight: Int, eatWeight: Int, eatNum: Int, userId: String?, notEnoughOneBlock: () -> Unit = {}, block: () -> Unit ) { log("foodWeight=$foodWeight,eatWeight=$eatWeight") val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 // if (pickupMode == 0) { // if (foodWeight <= 5) { // ToastUtils.showToast("餐品重量不足") // return // } // if (eatWeight <= 5) { // ToastUtils.showToast("取餐重量过小") // return // } // } if (eatNum <= 0) { ToastUtils.showToast("您好,当前重量不足一份") notEnoughOneBlock() return } showWaitingDialog("加载中,请稍候……") val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) val order = FoodOrder( deviceId = GlobalData.deviceId, foodId = foodInfo.foodId, foodName = foodInfo.foodName ?: "", foodMaterialId = foodInfo.foodMaterialId ?: "", specId = foodInfo.specId ?: "", foodWeight = foodWeight, eatWeight = eatWeight, //根据specId对应规格重量计算 eatNum = eatNum, userId = userId, notPay = chargeMode != 0, //即放即取-1,称重-2 mode = if (pickupMode == 1) 1 else 2 ) submitOrder(order, block) } private fun submitOrder(order: FoodOrder, block: () -> Unit) { viewModel.createOrder(order) { orderId -> runOnUiThread { binding.root.postDelayed({ hideWaitingDialog() if (orderId.isBlank()) { //ToastUtils.showToast("订单id为空") return@postDelayed } foodOrderId = orderId block() if (checkedItem!!.isFromSearch == true) { //当前菜名为手动搜索选择,非识别结果,保存向量数据 Thread { saveFoodVector(checkedItem!!) }.start() } }, 300) } } } fun getEatNum(realWeight: Double?, specWeight: Double?): Int { if (realWeight == null || realWeight <= 0.0 || specWeight == null || specWeight <= 0.0) return 0 return (realWeight / specWeight).roundToInt() } private var foodOrderId: String = "" private var isExist = false fun addBackEventListener() { onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { if (isExist.not()) { ToastUtils.showToast("再按一次退出") isExist = true binding.root.postDelayed({ isExist = false }, 2000) return } finish() } }) } @Subscribe(threadMode = ThreadMode.MAIN) fun onUpdateRefreshEvent(event: UpdateRefreshEvent) { isRefreshPage = true } @Subscribe(threadMode = ThreadMode.MAIN) fun onPaySuccessEvent(event: PaySuccessEvent) { isRefreshPage = true val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) ?: 0 presentation?.hideNutritionView(chargeMode) presentation?.cancel() } @Subscribe(threadMode = ThreadMode.MAIN) fun onChargeModeEvent(event: ChargeModeEvent) { isRefreshPage = true isStartRecognize = false presentation?.hideNutritionView(event.chargeMode) // presentation?.cancel() if (event.chargeMode == 1) { presentation?.let { it.onStop() it.setupArcCamera() } } } private var isRefreshPage = false // @Subscribe(threadMode = ThreadMode.MAIN) // fun onClickBackEvent(event: ClickBackEvent) { // isRefreshPage = false // } private fun clickPayButton() { if (checkedItem == null) { ToastUtils.showToast("暂无识别数据,请搜索选择") return } //val realWeight = (lastWeight * 1000).roundToInt() val mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) ?: 0 var foodWeight = 0.0 var eatWeight = 0.0 if (chargeMode == 0) { //计费 if (mealPickupMode == 0) { //即放即取 eatWeight = lastWeight * 1000 foodWeight = lastWeight * 1000 } else { //余量计量 eatWeight = (recognizeWeight - lastWeight) * 1000 foodWeight = recognizeWeight * 1000 log("eatWeight=$eatWeight, recognizeWeight=$recognizeWeight, lastWeight=$lastWeight") } } else { //不计费 foodWeight = recognizeWeight * 1000 eatWeight = if (mealPickupMode == 0) { //即放即取 recognizeWeight * 1000 } else { //余量计量 (recognizeWeight - lastWeight) * 1000 } } val eatNum = getEatNum(eatWeight, checkedItem!!.specWeight) createOrder( foodInfo = checkedItem!!, foodWeight = foodWeight.roundToInt(), eatWeight = eatWeight.roundToInt(), eatNum = eatNum, userId = null, ) { isRefreshPage = false //presentation?.dismiss() startActivity(Intent(this, PayActivity::class.java).apply { putExtra(PayActivity.FOOD_INFO, checkedItem!!) putExtra(PayActivity.FOOD_EAT_NUM, eatNum) putExtra(PayActivity.FOOD_ORDER_ID, foodOrderId) }) } } /** * 将uri转为文件和向量数据并提交 */ private fun saveFoodVector(foodInfo: FoodInfo) { try { if (lastPhotoUri == null) { return } uri2File(lastPhotoUri!!) { imageFile, imageVector -> runBlocking { uploadCollectFoodPics(foodInfo, imageFile, imageVector) } } } catch (e: Exception) { e.printStackTrace() } } /** * 上传菜品信息 */ private suspend fun uploadCollectFoodPics( foodInfo: FoodInfo, imageFile: File?, imageVector: FloatArray? ) { if (imageFile == null || imageVector == null) { log("是否null判断,imageFile == null:${imageFile == null},imageVector == null:${imageVector == null}") return } val foodId = foodInfo.foodId val foodName = foodInfo.foodName ?: "" val foodModelVersion = GlobalData.foodModelVersion val params = HashMap() params["foodId"] = foodId.toRequestBody() params["foodName"] = foodName.toRequestBody() params["version"] = foodModelVersion.toRequestBody() val files = listOf(imageFile) val vectors = listOf(imageVector) val foodVectorList = vectors.map { it.joinToString( separator = ",", prefix = "[", postfix = "]" ) } val foodVectorJson = foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]") log("json=$foodVectorJson") params["foodVector"] = foodVectorJson.toRequestBody() val idList = viewModel.uploadCollectFoodPics(files, params) if (idList.isNullOrEmpty()) { loadRemindDialog("上传失败,未返回向量数据id") log("idList为空") return } 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 } ObjectBox.put( Food( collectId = idList[0], foodId = foodId, foodName = foodName, foodVector = imageVector, version = foodModelVersion ) ) } private fun loadRemindDialog(msg: String) { runOnUiThread { RemindDialog( context = context, content = msg, confirmBlock = {}).show() } } private fun uri2File(uri: Uri, block: (File?, FloatArray?) -> Unit) { ImageUtil.uriToBitmap(this, uri)?.let { bitmap -> val imageVector = try { FoodModule.bitmap2FloatArray(bitmap, false) } catch (e: Exception) { e.printStackTrace() return@let } val imageFile = BitmapSaver.saveToAppFilesDir( bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" ) log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${imageFile?.absolutePath}") block(imageFile, imageVector) if (bitmap.isRecycled.not()) { bitmap.recycle() } } } override fun onDestroy() { //SensorScaleUtils.closeScale() faceTaskJob?.cancel() presentation?.cancel() shutdownCamera() super.onDestroy() } private val intervalExecutor by lazy { IntervalExecutor() } private var faceTaskJob: Job? = null // private val initialDelay = 5 * 60 * 1000L // private val dealyMillis = 10 * 60 * 1000L private val initialDelay = 30 * 1000L private val dealyMillis = 3 * 60 * 1000L // private var taskPageNo = 1 fun startFaceTask() { faceTaskJob = intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) { val timestamp = SpTool.lastFaceTimestamp //if (timestamp == 0L) { // return@startIntervalTaskWithInitialDelay //} viewModel.getFaceIncrementList( // pageNo = taskPageNo, timestamp = timestamp, onAllQueryFinished = { // taskPageNo = 1 }, onPageQueryFinished = { list -> runOnUiThread { if (list.isEmpty()) { return@runOnUiThread } updateFaceData(list) } }) } } private fun updateFaceData(list: List) { Thread { val faceList = mutableListOf() try { list.forEach { model -> if (model.faceDeleted == true) { //删除数据 FaceDatabase.getInstance(this).faceDao().deleteFaceById(model.userId) } else { //保存数据 val faceEntity = FaceEntity( model.userId, null, Base64.decode(model.faceFeatureStr) ).also { //1-会员、2-临时用户 it.userType = if (model.member) "1" else "2" it.cardNo = model.cardNo } faceList.add(faceEntity) } } } catch (e: Exception) { e.printStackTrace() } try { if (faceList.isNotEmpty()) { FaceDatabase.getInstance(this).faceDao().insert(faceList) } recognizeViewModel.refreshFaceList(); } catch (e: Exception) { e.printStackTrace() } }.start() } private fun sendFaceData(faceData: String) { // 给某个客户端发送 val jsonObject = JSONObject().also { it.put("type", "faceFeature") it.put("content", faceData) } tcpClient?.send(jsonObject) } private var tcpClient: TcpClient? = null private fun onTakePhotoSuccess(photoUri: Uri) { lastPhotoUri = photoUri log("registerDataChange main,takePhoto耗时:${System.currentTimeMillis() - startTime}") startTime = System.currentTimeMillis() log("registerDataChange photoUri = ${photoUri.path}") // viewModel.getIdentifiedFoodList() 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) 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) log("registerDataChange main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}") startTime = System.currentTimeMillis() log("registerDataChange photoUri 拿到识别数据:$recDataJson") val nameList = scoreList.map { it.name } 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 runOnUiThread { binding.tvToSearch.let { it.text = "未识别到,手动搜索" it.visible() } } } else { //binding.layoutRescan.visibility = View.GONE showWaitingDialog("正在查询菜品信息,请稍后……") viewModel.getFoodInfo(foodName) { list -> runOnUiThread { hideWaitingDialog() log("registerDataChange main,getFoodInfo耗时:${System.currentTimeMillis() - startTime}") startTime = System.currentTimeMillis() updateFoodInfo(list.toMutableList(), scoreList) } } } } // @Subscribe(threadMode = ThreadMode.MAIN) // public fun onResetBox(event: ResetBoxEvent) { // ObjectBox.boxStore.close() // ObjectBox.init(this) // } }