This commit is contained in:
2026-01-08 19:36:31 +08:00
parent fce721bfc3
commit a79f7db867
8 changed files with 184 additions and 147 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ class MyApp : App() {
CrashHandler.init(this)
SensorScaleUtils.startScale()
//addActivityLifecycleListener()
addActivityLifecycleListener()
}
private fun addActivityLifecycleListener() {
@@ -106,6 +106,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
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<ActivityMainBinding>() {
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<ActivityMainBinding>() {
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<ActivityMainBinding>() {
//即放即取-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<ActivityMainBinding>() {
*/
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<ActivityMainBinding>() {
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<ActivityMainBinding>() {
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<ActivityMainBinding>() {
// 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<ActivityMainBinding>() {
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
@@ -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<FragmentCollectBinding>() {
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<Food>?=null
private var box: Box<Food>? = null
fun initBox() {
if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class)
}
}
private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply {
repeat(MAX_COUNT) {
add(FoodCollectionBean(isShowCamera = true))
@@ -70,10 +74,9 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
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<FragmentCollectBinding>() {
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<FragmentCollectBinding>() {
}
}
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<FragmentCollectBinding>() {
}
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<FragmentCollectBinding>() {
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()
}
@@ -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 // 超时时间(秒)
@@ -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()
@@ -398,6 +398,9 @@ class MainScreenPresentation(
* 餐品识别成功,不计费模式
*/
fun loadUnbilledMode() {
if (currentUserId.isNullOrBlank()) {
return
}
isLoadUnbilledMode = true
Timber.tag(TAG).d("loadUnbilledMode")
binding.calorieInclude.root.gone()
@@ -1,6 +1,7 @@
package com.sw.dualscreen.utils
import android.graphics.Bitmap
object BitmapCropper {
/**
* 裁剪Bitmap中心区域为指定尺寸
@@ -9,13 +10,13 @@ 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)
@@ -23,16 +24,19 @@ object BitmapCropper {
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)
//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}")
}
// // 裁剪中心500x500区域
// val croppedBitmap = BitmapCropper.cropCenter(originalBitmap, 500, 500)
// println("裁剪后尺寸:${croppedBitmap.width}x${croppedBitmap.height}")
//}
@@ -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)