添加菜品采集

This commit is contained in:
mazengfei
2025-10-31 09:53:51 +08:00
parent ed6c5e83be
commit e47b362883
16 changed files with 426 additions and 68 deletions
@@ -1,7 +1,14 @@
package com.sw.dualscreen.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.net.Uri
import android.util.Log
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
@@ -13,17 +20,28 @@ import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.example.utils.FloatBase64Utils
import com.google.common.util.concurrent.ListenableFuture
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.FoodCollectionAdapter
import com.sw.dualscreen.adapter.GenericItemAdapter
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding.inflate
import com.sw.dualscreen.model.response.FoodInfo
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.presentation.SecondaryScreenPresentation
import com.sw.dualscreen.sdk.SensorScaleUtils
import com.sw.dualscreen.utils.CameraHelper
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.GlideUtils
import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.utils.StorageUtils
import com.sw.dualscreen.view.CustomBottomSheetDialog
@@ -32,6 +50,7 @@ import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.launch
import org.pytorch.IValue
import org.pytorch.Module
import org.pytorch.torchvision.TensorImageUtils
@@ -50,6 +69,7 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
private val executor = Executors.newSingleThreadExecutor()
private val viewModel by viewModels<UserViewModel>()
private var selectedFoodId: String? = ""
private var selectedFoodName: String? = ""
private var bottomSheetDialog: CustomBottomSheetDialog? = null
@@ -60,11 +80,17 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
private var box: Box<Food>? = null
private val list: MutableList<FoodCollectionBean> = mutableListOf()
private lateinit var cameraHelper: CameraHelper
private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
private val debouncer = Debouncer(2000)
private val adapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(list).apply {
setOnItemClickListener { _, _, position ->
if (list[position].isShowCamera) {
takePhoto()
// takePhoto()
cameraHelper.openCamera()
}
}
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
@@ -84,7 +110,28 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
}
override fun initialize() {
binding.ivBack.setOnClickListener { finish() }
// 初始化 CameraHelper
cameraHelper = CameraHelper(
context = this,
caller = this,
authority = "${packageName}.fileprovider"
) { uri, path ->
if (list.size < 6) {
val insertIndex = if (list.isEmpty()) 0 else list.size - 1
list.add(insertIndex, FoodCollectionBean(imageUri = uri))
} else {
list[list.size - 1] = FoodCollectionBean(imageUri = uri)
}
adapter.notifyDataSetChanged()
}
binding.ivBack.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
list.add(FoodCollectionBean(isShowCamera = true))
binding.rvFoodCollection.let {
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
@@ -92,11 +139,7 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
}
binding.btnFoodSearch.setOnClickListener {
bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
selectedFoodName = it.foodName
binding.tvSelectedFood.text = selectedFoodName
}
bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
searchInfo()
}
binding.btnSave.setOnClickListener {
@@ -110,39 +153,41 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
}
vectorThread()
}
}
private fun takePhoto() {
StorageUtils.requestCameraPermission(this) {
val outputFile = File(this.filesDir, "IMG_${System.currentTimeMillis()}.jpg")
outputFileUri =
FileProvider.getUriForFile(this, "${this.packageName}.fileprovider", outputFile)
takePictureLauncher.launch(outputFileUri!!)
}
}
private var outputFileUri: Uri? = null
@SuppressLint("NotifyDataSetChanged")
private val takePictureLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
//Loading.show(this)
if (list.size < 6) {
list.add(list.size - 1, FoodCollectionBean(imageUri = outputFileUri))
} else {
list[list.size - 1] = FoodCollectionBean(imageUri = outputFileUri)
}
adapter.notifyDataSetChanged()
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchInfo()
val imm =
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
true
} else {
false
}
}
foodAdapter = createAdapter()
binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
// 添加间距装饰(12dp)
binding.recyclerview.addItemDecoration(
GridSpacingItemDecoration(
spanCount = 2,
spacing = dpToPx(30),
includeEdge = false // 包含边缘间距
)
)
binding.recyclerview.adapter = foodAdapter
}
private fun vectorThread() {
//Loading.show(this)
showWaitingDialog("加载中……")
Thread {
list.forEachIndexed { index, it ->
image2VectorTask(it.imageUri!!, index)
if (!it.isShowCamera)
image2VectorTask(it.imageUri!!, index)
}
runOnUiThread {
window.decorView.postDelayed({
@@ -169,17 +214,76 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
val outputTensor = module!!.forward(IValue.from(inputTensor)).toTensor()
val base64Str = FloatBase64Utils.floatArrayToBase64(outputTensor.dataAsFloatArray)
Timber.tag("mzf1").e(base64Str)
val imageVector = outputTensor.dataAsFloatArray
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector))
list[position].let {
it.imageVector = imageVector
it.isFinish = true
Timber
viewModel.postImageData(
context,
foodId = selectedFoodId.toString(),
foodName = selectedFoodName.toString(),
foodVector = base64Str,
uri = imageUri
)
}
runOnUiThread {
adapter.notifyItemChanged(position)
}
Timber.tag("registerDataChange").e("box.all.size=${box?.all?.size}")
}
}
private fun searchInfo() {
debouncer.debounce {
viewModel.searchByFoodName(binding.editFoodName.text.toString())
}
}
override fun registerDataChange() {
super.registerDataChange()
lifecycleScope.launch {
viewModel.searchFoodInfoList.collect {
foodAdapter.updateData(it)
}
}
}
private var checkedItem: FoodInfo? = null
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
return GenericItemAdapter(
items = emptyList(),
bindingInflater = ItemSearchFoodInfoBinding::inflate,
bindCallback = { item, position ->
this.tvName.text = item.foodName
if (item.id != checkedItem?.id) {
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
this.tvName.setTextColor(resources.getColor(R.color.search_normal))
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal)
} else {
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
this.tvName.setTextColor(resources.getColor(R.color.search_checked))
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked)
}
this.llRoot.setOnClickListener {
Timber.d("itemClick ${item.foodName}, position = $position")
checkedItem = item
// viewModel.updateCurrentItem(item)
// itemClickCallback(item)
selectedFoodName = item.foodName
selectedFoodId = item.id
foodAdapter.notifyDataSetChanged()
}
}
)
}
}
@@ -1,6 +1,7 @@
package com.sw.dualscreen.activity
import android.content.Context
import android.content.Intent
import android.graphics.Outline
import android.graphics.Typeface
import android.hardware.display.DisplayManager
@@ -43,6 +44,7 @@ import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.view.CustomBottomSheetDialog
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.LightManager
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.launch
@@ -93,6 +95,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun initView() {
viewModel.activeEngine()
binding.foodDisplayLayout.setOnClickListener {
val intent = Intent(this, FoodCollectionActivity::class.java)
startActivity(intent)
finish()
}
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
@@ -202,6 +209,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
if (weight - lastWeight > 0.1 && presentation?.mealPickupMode == 0) { // 大于100g
debouncer.debounce {
LightManager.openRedLight()
LightManager.openGreenLight()
takePhoto { photoUri ->
lastPhotoUri = photoUri
Timber.d("registerDataChange photoUri = ${photoUri.path}")
@@ -211,7 +220,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
Timber.d("registerDataChange photoUri 拿到bitmap")
val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
Timber.d("registerDataChange photoUri bitmap裁剪完成")
val file = BitmapSaver.saveToAppFilesDir(bmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg")
val file = BitmapSaver.saveToAppFilesDir(
bmp,
this,
"IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
val resultList = FoodModule.queryFood(bmp)
Timber.d("registerDataChange photoUri 拿到识别数据")