From a79f7db867a885ca1075e2b7fd172dac24eb22e3 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Thu, 8 Jan 2026 19:36:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/sw/dualscreen/MyApp.kt | 2 +- .../sw/dualscreen/activity/MainActivity.kt | 99 +++++---- .../activity/fragment/CollectFragment.kt | 188 +++++++++--------- .../com/sw/dualscreen/network/ApiClient.kt | 2 +- .../com/sw/dualscreen/objbox/FoodModule.kt | 1 + .../presentation/MainScreenPresentation.kt | 3 + .../com/sw/dualscreen/utils/BitmapCropper.kt | 34 ++-- .../com/sw/dualscreen/utils/ImageUploader.kt | 2 +- 8 files changed, 184 insertions(+), 147 deletions(-) diff --git a/app/src/main/java/com/sw/dualscreen/MyApp.kt b/app/src/main/java/com/sw/dualscreen/MyApp.kt index 4fb52c1..e45e61f 100644 --- a/app/src/main/java/com/sw/dualscreen/MyApp.kt +++ b/app/src/main/java/com/sw/dualscreen/MyApp.kt @@ -29,7 +29,7 @@ class MyApp : App() { CrashHandler.init(this) SensorScaleUtils.startScale() - //addActivityLifecycleListener() + addActivityLifecycleListener() } private fun addActivityLifecycleListener() { diff --git a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt index 7b2fbbc..5602e18 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt @@ -106,6 +106,9 @@ class MainActivity : BaseActivity() { private val adapter by lazy { SearchFoodAdapter(searchFoodList).apply { setOnItemClickListener { adapter, view, position -> + if (searchFoodList[position].isChecked) { + return@setOnItemClickListener + } searchFoodList.forEachIndexed { index, info -> searchFoodList[index].isChecked = index == position } @@ -373,12 +376,14 @@ class MainActivity : BaseActivity() { showWaitingDialog("识别中,请稍后……") takePhoto( successCallback = { photoUri -> - onTakePhotoSuccess(photoUri) + Thread{ + onTakePhotoSuccess(photoUri) + }.start() failCount = 0 }, failureCallback = { runOnUiThread { if (failCount >= 10) { - ToastUtils.showToast("相机异常,请稍后重试") + //ToastUtils.showToast("相机异常,请稍后重试") shutdownCamera() setupCamera() return@runOnUiThread @@ -774,6 +779,7 @@ class MainActivity : BaseActivity() { ToastUtils.showToast("您好,当前重量不足一份") return } + showWaitingDialog("加载中,请稍候……") val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) val order = FoodOrder( deviceId = GlobalData.deviceId, @@ -790,19 +796,30 @@ class MainActivity : BaseActivity() { //即放即取-1,称重-2 mode = if (pickupMode == 1) 1 else 2 ) + debouncer.debounce { + submitOrder(order, block) + } + } + + private fun submitOrder(order: FoodOrder, block: () -> Unit) { viewModel.createOrder(order) { orderId -> runOnUiThread { - if (orderId.isBlank()) { - //ToastUtils.showToast("订单id为空") - return@runOnUiThread - } - foodOrderId = orderId - block() + binding.root.postDelayed({ + hideWaitingDialog() + if (orderId.isBlank()) { + //ToastUtils.showToast("订单id为空") + return@postDelayed + } + foodOrderId = orderId + block() - if (checkedItem!!.isFromSearch == true) { - //当前菜名为手动搜索选择,非识别结果,保存向量数据 - saveFoodVector(checkedItem!!) - } + if (checkedItem!!.isFromSearch == true) { + //当前菜名为手动搜索选择,非识别结果,保存向量数据 + Thread{ + saveFoodVector(checkedItem!!) + }.start() + } + }, 300) } } } @@ -916,11 +933,12 @@ class MainActivity : BaseActivity() { */ private fun saveFoodVector(foodInfo: FoodInfo) { try { - lastPhotoUri?.let { uri -> - uri2File(uri) { imageFile, imageVector -> - runBlocking { - uploadCollectFoodPics(foodInfo, imageFile, imageVector) - } + if (lastPhotoUri == null) { + return + } + uri2File(lastPhotoUri!!) { imageFile, imageVector -> + runBlocking { + uploadCollectFoodPics(foodInfo, imageFile, imageVector) } } } catch (e: Exception) { @@ -974,15 +992,17 @@ class MainActivity : BaseActivity() { loadRemindDialog("返回的id:${idList[0]}已存在") return } - box.put( - Food( - collectId = idList[0], - foodId = foodId, - foodName = foodName, - foodVector = imageVector, - version = foodModelVersion + Thread { + box.put( + Food( + collectId = idList[0], + foodId = foodId, + foodName = foodName, + foodVector = imageVector, + version = foodModelVersion + ) ) - ) + }.start() } private fun loadRemindDialog(msg: String) { @@ -1193,7 +1213,6 @@ class MainActivity : BaseActivity() { startTime = System.currentTimeMillis() Timber.d("registerDataChange photoUri = ${photoUri.path}") // viewModel.getIdentifiedFoodList() - ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap -> Timber.tag(TAG).d("main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}") startTime = System.currentTimeMillis() @@ -1201,19 +1220,15 @@ class MainActivity : BaseActivity() { // val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900) Timber.d("registerDataChange photoUri bitmap裁剪完成") val file = BitmapSaver.saveToAppFilesDir( - bitmap, - this, - "IMG_CROP_${System.currentTimeMillis()}.jpg" + bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" ) - Timber.tag(TAG) - .d("main,saveToAppFilesDir耗时:${System.currentTimeMillis() - startTime}") + 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) val recDataJson = GsonUtils.toJson(scoreList) - Timber.tag(TAG) - .d("main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}") + Timber.tag(TAG).d("main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}") startTime = System.currentTimeMillis() Timber.d("registerDataChange photoUri 拿到识别数据:$recDataJson") val nameList = scoreList.map { it.name } @@ -1221,19 +1236,23 @@ class MainActivity : BaseActivity() { hideWaitingDialog() Timber.d("registerDataChange photoUri 识别数据名称:$foodName") - binding.flPay.run { - val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) - //0-计费,1-不计费 - if (chargeMode == 0) visible() else gone() + 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 - binding.tvToSearch.let { - it.text = "未识别到,手动搜索" - it.visible() + runOnUiThread { + binding.tvToSearch.let { + it.text = "未识别到,手动搜索" + it.visible() + } } } else { //binding.layoutRescan.visibility = View.GONE diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt index 5e9b8cd..bc5a84a 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt @@ -3,6 +3,7 @@ package com.sw.dualscreen.activity.fragment import android.annotation.SuppressLint import android.content.Context import android.content.Intent +import android.graphics.Bitmap import android.net.Uri import android.view.LayoutInflater import android.view.View @@ -27,6 +28,7 @@ import com.sw.dualscreen.objbox.Food import com.sw.dualscreen.objbox.FoodCollectionBean import com.sw.dualscreen.objbox.FoodModule import com.sw.dualscreen.objbox.ObjectBox +import com.sw.dualscreen.utils.BitmapCropper import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.CameraUtils import com.sw.dualscreen.utils.Debouncer @@ -42,22 +44,24 @@ import okhttp3.RequestBody.Companion.toRequestBody import timber.log.Timber import kotlin.getValue +@SuppressLint("NotifyDataSetChanged") class CollectFragment : BaseFragment() { companion object { private const val TAG = "CollectFragment" - const val MAX_COUNT = 15 + const val MAX_COUNT = 9 } private var selectedFoodId: String? = "" private var selectedFoodName: String? = "" - private var box: Box?=null + private var box: Box? = null fun initBox() { if (box == null) { box = ObjectBox.boxStore.boxFor(Food::class) } } + private val foodCollectionList = mutableListOf().apply { repeat(MAX_COUNT) { add(FoodCollectionBean(isShowCamera = true)) @@ -70,10 +74,9 @@ class CollectFragment : BaseFragment() { private val searchFoodAdapter by lazy { SearchFoodAdapter(searchFoodList).apply { setOnItemClickListener { adapter, view, position -> + searchFoodList.forEachIndexed { index, item -> item.isChecked = index == position } checkedItem = searchFoodList[position] - val isChecked = checkedItem!!.isChecked - searchFoodList[position].isChecked = isChecked.not() - notifyItemChanged(position) + notifyDataSetChanged() selectedFoodId = checkedItem!!.foodId selectedFoodName = checkedItem!!.foodName } @@ -126,37 +129,11 @@ class CollectFragment : BaseFragment() { collectionAdapter.notifyItemChanged(index) } // settingActivity?.hideWaitingDialog() - ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap -> - // val cropBitmap = BitmapCropper.cropCenter( - // original = bitmap, - // targetWidth = 1300, targetHeight = 900, - // //offsetX = 30, offsetY = 100 - // ) - val imageVector = try { - FoodModule.bitmap2FloatArray(bitmap) - } catch (e: Exception) { - e.printStackTrace() - ToastUtils.showToast("操作失败") - settingActivity?.hideWaitingDialog() - return@let + Thread { + ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap -> + getImageVector(index, bitmap) } - val file = BitmapSaver.saveToAppFilesDir( - bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg" - ) - Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}") - - foodCollectionList[index].let { - it.imageVector = imageVector - it.bitmap = null - it.isShowCamera = false - it.imageFile = file - } - collectionAdapter.notifyItemChanged(index) - if (bitmap.isRecycled.not()) { - bitmap.recycle() - } - settingActivity?.hideWaitingDialog() - } + }.start() } catch (e: Exception) { e.printStackTrace() settingActivity?.hideWaitingDialog() @@ -164,6 +141,40 @@ class CollectFragment : BaseFragment() { } } + private fun getImageVector(index: Int, bitmap: Bitmap) { +// val bitmap = BitmapCropper.cropCenter( +// original = srcBmp, +// targetWidth = 900, targetHeight = 900, +//// offsetX = 30, offsetY = 100 +// ) + val imageVector = try { + FoodModule.bitmap2FloatArray(bitmap) + } catch (e: Exception) { + e.printStackTrace() + ToastUtils.showToast("操作失败") + settingActivity?.hideWaitingDialog() + return + } + val file = BitmapSaver.saveToAppFilesDir( + bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg" + ) + Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}") + + activity?.runOnUiThread { + foodCollectionList[index].let { + it.imageVector = imageVector + it.bitmap = null + it.isShowCamera = false + it.imageFile = file + } + collectionAdapter.notifyItemChanged(index) + } + if (bitmap.isRecycled.not()) { + bitmap.recycle() + } + settingActivity?.hideWaitingDialog() + } + @SuppressLint("NotifyDataSetChanged") private fun takePhoto() { val count = foodCollectionList.count { it.bitmap != null } @@ -173,11 +184,19 @@ class CollectFragment : BaseFragment() { } settingActivity?.showWaitingDialog("采集中……") cameraUtils.takePhoto(cameraCallback) { errMsg -> - ToastUtils.showToast("拍照异常,请重新操作") settingActivity?.hideWaitingDialog() + if (cameraErrorCount >= 10) { + ToastUtils.showToast("拍照异常,请重新操作") + cameraErrorCount = 0 + return@takePhoto + } + cameraErrorCount++ + takePhoto() } } + private var cameraErrorCount = 0 + override fun initialize() { settingActivity = activity as SettingActivity cameraUtils.initCamera() @@ -256,65 +275,56 @@ class CollectFragment : BaseFragment() { params["foodName"] = checkedItem!!.foodName!!.toRequestBody() params["version"] = GlobalData.foodModelVersion.toRequestBody() - ImageUploader( - totalList = foodCollectionList, - uploadImage = { batch -> - val files = batch.map { it.imageFile } - val foodVectorList = - batch.filter { it.imageVector != null }.map { - it.imageVector!!.joinToString( - separator = ",", - prefix = "[", - postfix = "]" - ) - } - val foodVectorJson = - foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]") - Timber.tag(TAG).d("json=$foodVectorJson") - params["foodVector"] = foodVectorJson.toRequestBody() - settingActivity?.viewModel?.uploadCollectFoodPics(files, params) - }, - onProgress = { count, batch, idList -> - //batch.forEach { - // it.uploadSuccess = true - //} - runBlocking { - activity?.runOnUiThread { - settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount") - } - val foodList = batch.mapIndexed { index, it -> - Food( - collectId = if (index < idList.size) idList[index] else null, - foodId = checkedItem!!.foodId, - foodName = checkedItem!!.foodName, - foodVector = it.imageVector, - version = GlobalData.foodModelVersion - ) - } - initBox() - box?.put(foodList) - activity?.runOnUiThread { - batch.forEach { it.isFinish = true } - collectionAdapter.notifyDataSetChanged() - } - } - }, - onError = { + ImageUploader(totalList = foodCollectionList, uploadImage = { batch -> + val files = batch.map { it.imageFile } + val foodVectorList = batch.filter { it.imageVector != null }.map { + it.imageVector!!.joinToString( + separator = ",", prefix = "[", postfix = "]" + ) + } + val foodVectorJson = + foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]") + Timber.tag(TAG).d("json=$foodVectorJson") + params["foodVector"] = foodVectorJson.toRequestBody() + settingActivity?.viewModel?.uploadCollectFoodPics(files, params) + }, onProgress = { count, batch, idList -> + //batch.forEach { + // it.uploadSuccess = true + //} + runBlocking { activity?.runOnUiThread { - binding.root.postDelayed({ - settingActivity?.hideWaitingDialog() - ToastUtils.showToast("上传失败,请稍后重试") - }, 1000) + settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount") } - }, - onComplete = { - //vectorThread() + val foodList = batch.mapIndexed { index, it -> + Food( + collectId = if (index < idList.size) idList[index] else null, + foodId = checkedItem!!.foodId, + foodName = checkedItem!!.foodName, + foodVector = it.imageVector, + version = GlobalData.foodModelVersion + ) + } + initBox() + box?.put(foodList) + activity?.runOnUiThread { + batch.forEach { it.isFinish = true } + collectionAdapter.notifyDataSetChanged() + } + } + }, onError = { + activity?.runOnUiThread { binding.root.postDelayed({ settingActivity?.hideWaitingDialog() - ToastUtils.showToast("上传成功") + ToastUtils.showToast("上传失败,请稍后重试") }, 1000) } - ).processUploads() + }, onComplete = { + //vectorThread() + binding.root.postDelayed({ + settingActivity?.hideWaitingDialog() + ToastUtils.showToast("上传成功") + }, 1000) + }).processUploads() } diff --git a/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt b/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt index a183342..d67752a 100644 --- a/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt +++ b/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt @@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit object ApiClient { private const val BASE_URL = "http://device.shuziweidao.com:8889/" - private const val TIME_OUT = 60L // 超时时间(秒) + private const val TIME_OUT = 30L // 超时时间(秒) //todo 临时测试改为5秒 // private const val TIME_OUT = 5L // 超时时间(秒) diff --git a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt index 09775e9..a01da07 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt @@ -132,6 +132,7 @@ object FoodModule { val startTime = System.currentTimeMillis() val floatArray = bitmap2FloatArray(bitmap) Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}") + val nameScoreList = queryFoodNameScore(floatArray, queryCount) if (nameScoreList.isEmpty()) { return emptyList() diff --git a/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt index 1679e9a..6c5aa3f 100644 --- a/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt +++ b/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt @@ -398,6 +398,9 @@ class MainScreenPresentation( * 餐品识别成功,不计费模式 */ fun loadUnbilledMode() { + if (currentUserId.isNullOrBlank()) { + return + } isLoadUnbilledMode = true Timber.tag(TAG).d("loadUnbilledMode") binding.calorieInclude.root.gone() diff --git a/app/src/main/java/com/sw/dualscreen/utils/BitmapCropper.kt b/app/src/main/java/com/sw/dualscreen/utils/BitmapCropper.kt index ac6e7b6..0a6dc82 100644 --- a/app/src/main/java/com/sw/dualscreen/utils/BitmapCropper.kt +++ b/app/src/main/java/com/sw/dualscreen/utils/BitmapCropper.kt @@ -1,6 +1,7 @@ package com.sw.dualscreen.utils import android.graphics.Bitmap + object BitmapCropper { /** * 裁剪Bitmap中心区域为指定尺寸 @@ -9,30 +10,33 @@ object BitmapCropper { * @param targetHeight 目标高度 * @return 裁剪后的Bitmap */ - fun cropCenter(original: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap { + fun cropCenter(original: Bitmap, targetWidth: Int, targetHeight: Int, offsetX:Int = 0, offsetY:Int = 0): Bitmap { val originalWidth = original.width val originalHeight = original.height - + // 计算中心点坐标 - var startX = (originalWidth - targetWidth) / 2 - var startY = (originalHeight - targetHeight) / 2 - + var startX = (originalWidth - targetWidth) / 2 + offsetX + var startY = (originalHeight - targetHeight) / 2 + offsetY + // 边界检查 startX = startX.coerceAtLeast(0) startY = startY.coerceAtLeast(0) val actualWidth = minOf(targetWidth, originalWidth - startX) val actualHeight = minOf(targetHeight, originalHeight - startY) - - return Bitmap.createBitmap(original, startX, startY, actualWidth, actualHeight) + + return Bitmap.createBitmap(original, startX, startY, actualWidth, actualHeight).also { +// it.setConfig(Bitmap.Config.RGB_565) +// it.density = DisplayMetrics.DENSITY_LOW + } } } // 使用示例 -fun main() { - // 假设这是从资源加载的Bitmap - val originalBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888) - - // 裁剪中心500x500区域 - val croppedBitmap = BitmapCropper.cropCenter(originalBitmap, 500, 500) - println("裁剪后尺寸:${croppedBitmap.width}x${croppedBitmap.height}") -} +//fun main() { +// // 假设这是从资源加载的Bitmap +// val originalBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888) + +// // 裁剪中心500x500区域 +// val croppedBitmap = BitmapCropper.cropCenter(originalBitmap, 500, 500) +// println("裁剪后尺寸:${croppedBitmap.width}x${croppedBitmap.height}") +//} diff --git a/app/src/main/java/com/sw/dualscreen/utils/ImageUploader.kt b/app/src/main/java/com/sw/dualscreen/utils/ImageUploader.kt index 061873a..89fc0b7 100644 --- a/app/src/main/java/com/sw/dualscreen/utils/ImageUploader.kt +++ b/app/src/main/java/com/sw/dualscreen/utils/ImageUploader.kt @@ -11,7 +11,7 @@ class ImageUploader( private val onComplete:() -> Unit ) { companion object { - private const val BATCH_SIZE = 5 + private const val BATCH_SIZE = 3 } private val uploadedCount = AtomicInteger(0)