采集向量优化调试

This commit is contained in:
2026-01-08 15:02:44 +08:00
parent 378e6088e7
commit 2a55ddb617
35 changed files with 663 additions and 1112 deletions
+1
View File
@@ -114,6 +114,7 @@ dependencies {
val objectboxVersion = "5.0.1"
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
// implementation("io.objectbox:objectbox-fulltext:5.0.1")
implementation(libs.androidx.recyclerview)
+11 -1
View File
@@ -5,7 +5,7 @@
"entities": [
{
"id": "1:594331511073099531",
"lastPropertyId": "9:4394472487596452023",
"lastPropertyId": "11:7949771231314213889",
"name": "Food",
"properties": [
{
@@ -45,6 +45,16 @@
"id": "9:4394472487596452023",
"name": "otherField",
"type": 9
},
{
"id": "10:5704890122356491756",
"name": "createTime",
"type": 9
},
{
"id": "11:7949771231314213889",
"name": "isDel",
"type": 1
}
],
"relations": []
+2 -1
View File
@@ -50,7 +50,8 @@
</activity>
<activity
android:name=".activity.MainActivity"
android:exported="true">
android:exported="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>
<!-- <activity-->
<!-- android:name=".activity.FoodCollectionActivity"-->
@@ -1,134 +0,0 @@
//package com.sw.dualscreen.activity
//
//import android.annotation.SuppressLint
//import androidx.activity.viewModels
//import androidx.core.widget.addTextChangedListener
//import androidx.recyclerview.widget.LinearLayoutManager
//import com.sw.dualscreen.R
//import com.sw.dualscreen.adapter.CollectedFoodAdapter
//import com.sw.dualscreen.databinding.ActivityCollectedDataBinding
//import com.sw.dualscreen.databinding.LayoutEmptySearchBinding
//import com.sw.dualscreen.dialog.WarnDialog
//import com.sw.dualscreen.ext.addOnActionSearchListener
//import com.sw.dualscreen.ext.hideKeyboard
//import com.sw.dualscreen.objbox.CollectedFoodInfo
//import com.sw.dualscreen.objbox.Food
//import com.sw.dualscreen.objbox.ObjectBox
//import com.sw.dualscreen.viewmodel.BaseViewModel
//import com.sw.dualscreen.viewmodel.UserViewModel
//import com.sw.plate.utils.ToastUtils
//import io.objectbox.Box
//import io.objectbox.kotlin.boxFor
//import kotlin.getValue
//
//class CollectedDataActivity : BaseActivity<ActivityCollectedDataBinding>() {
// private val viewModel by viewModels<UserViewModel>()
// override fun getViewModel(): BaseViewModel {
// return viewModel
// }
//
// override fun inflateViewBinding(): ActivityCollectedDataBinding {
// return ActivityCollectedDataBinding.inflate(layoutInflater)
// }
//
// private val list: MutableList<CollectedFoodInfo> = mutableListOf()
// private val adapter by lazy {
// CollectedFoodAdapter(list).apply {
// isStateViewEnable = true
// addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position ->
// deleteGoods(position)
// }
// }
// }
//
// override fun initialize() {
// super.initialize()
// binding.rvFoodList.let {
// it.layoutManager = LinearLayoutManager(this)
// it.adapter = adapter
// }
// binding.ivBack.setOnClickListener { finish() }
// binding.ivGoodsSearch.setOnClickListener {
// val searchName = binding.etInputGoods.text.toString().trim()
// if (searchName.isBlank()) {
// ToastUtils.showToast("请输入物品名称")
// return@setOnClickListener
// }
// getCollectGoods(searchName)
// }
// binding.etInputGoods.let { v ->
// v.addOnActionSearchListener {
// val searchName = binding.etInputGoods.text.toString().trim()
// if (searchName.isBlank()) {
// ToastUtils.showToast("请输入物品名称")
// return@addOnActionSearchListener
// }
// getCollectGoods(searchName)
// }
// v.addTextChangedListener {
// if (it.isNullOrBlank()) {
// getCollectGoods()
// }
// }
// }
// getCollectGoods()
// }
//
//
// private var box: Box<Food>? = null
//
// @SuppressLint("NotifyDataSetChanged")
// private fun getCollectGoods(searchName:String?=null) {
// if (box == null) {
// box = ObjectBox.boxStore.boxFor(Food::class)
// }
// list.clear()
// var queryList = box?.all?.distinctBy { it.name }
// if (searchName.isNullOrBlank().not()) {
// queryList = queryList?.filter { it.name?.contains(searchName) == true }
// }
// queryList?.forEach {
// list.add(CollectedFoodInfo(foodName = it.name))
// }
// adapter.notifyDataSetChanged()
// if (list.isEmpty()) {
// loadEmptyView()
// }
// binding.root.hideKeyboard()
// }
//
// private var emptyBinding: LayoutEmptySearchBinding? = null
// private fun loadEmptyView() {
// if (emptyBinding == null) {
// emptyBinding = LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvFoodList, false)
// }
// emptyBinding?.root?.let { layout ->
// layout.setOnClickListener { layout.hideKeyboard() }
// adapter.stateView = layout
// }
// }
//
// private fun deleteGoods(position: Int) {
// WarnDialog(
// context = context,
// content = "请确认是否删除菜品:${list[position].foodName}",
// confirmBlock = {
// Thread {
// runOnUiThread {
// showWaitingDialog("加载中……")
// }
// val name = list[position].foodName
// val filterIdList = box?.all?.filter { it.name == name }?.map { it.id }
// box?.removeByIds(filterIdList)
// runOnUiThread {
// hideWaitingDialog()
// adapter.removeAt(position)
// ToastUtils.showToast("删除成功")
// if (list.isEmpty()) {
// loadEmptyView()
// }
// }
// }.start()
// }).show()
// }
//}
@@ -8,17 +8,21 @@ import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.CollectedFoodNewAdapter
import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding
import com.sw.dualscreen.dialog.WarnDialog
import com.sw.dualscreen.dialog.RemindDialog
import com.sw.dualscreen.ext.addOnActionSearchListener
import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.ResetBoxEvent
import com.sw.dualscreen.objbox.CollectedFoodInfo
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import org.greenrobot.eventbus.EventBus
class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
@@ -47,6 +51,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
override fun initialize() {
super.initialize()
box = ObjectBox.boxStore.boxFor(Food::class)
//binding.root.setOnClickListener { it.hideKeyboard() }
binding.rvFoodList.let {
it.layoutManager = LinearLayoutManager(this)
@@ -99,7 +104,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
}
private var box: Box<Food>? = null
private lateinit var box: Box<Food>
@SuppressLint("NotifyDataSetChanged")
private fun getCollectGoods(searchName: String? = null) {
@@ -181,7 +186,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
}
private fun loadDeleteDialog(position: Int) {
WarnDialog(
RemindDialog(
context = context,
content = "请确认是否删除菜品:${list[position].foodName}",
confirmBlock = {
@@ -195,8 +200,16 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess ->
if (deleteSuccess) {
Thread {
val filterIdList = box?.all?.filter { it.foodName == food.foodName }?.map { it.id }
box?.removeByIds(filterIdList)
val filterFoodList = box.all.filter { it.foodName == food.foodName }
filterFoodList.forEach { it.isDel = true }
box.put(filterFoodList)
// val filterIdList = filterFoodList.map { it.id }
////// ObjectBox.boxStore.runInTx {
//// box.removeByIds(filterIdList)
////// EventBus.getDefault().post(ResetBoxEvent())
////// }
runOnUiThread {
hideWaitingDialog()
adapter.removeAt(position)
@@ -1,370 +0,0 @@
//package com.sw.dualscreen.activity
//
//import android.annotation.SuppressLint
//import android.content.Context
//import android.content.Intent
//import android.graphics.Bitmap
//import android.graphics.Typeface
//import android.net.Uri
//import android.view.View
//import android.view.inputmethod.EditorInfo
//import android.view.inputmethod.InputMethodManager
//import android.widget.Toast
//import androidx.activity.viewModels
//import androidx.camera.view.PreviewView
//import androidx.core.net.toUri
//import androidx.core.view.updateLayoutParams
//import androidx.lifecycle.lifecycleScope
//import androidx.recyclerview.widget.GridLayoutManager
//import com.example.utils.FloatBase64Utils
//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.LayoutCameraPreviewBinding
//import com.sw.dualscreen.ext.clickWithDebounce
//import com.sw.dualscreen.ext.dp
//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.utils.BitmapCropper
//import com.sw.dualscreen.utils.BitmapSaver
//import com.sw.dualscreen.utils.CameraHelper
//import com.sw.dualscreen.utils.CameraUtils
//import com.sw.dualscreen.utils.Debouncer
//import com.sw.dualscreen.utils.ImageUtil
//import com.sw.dualscreen.viewmodel.BaseViewModel
//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
//import timber.log.Timber
//import kotlin.math.max
//
//class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
// companion object {
// val MAX_COUNT = 100
// }
// private val viewModel by viewModels<UserViewModel>()
// private var selectedFoodId: String? = ""
// private var selectedFoodName: String? = ""
//
// private var box: Box<Food>? = null
// private val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf()
//
//// private lateinit var cameraHelper: CameraHelper
//
// private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
// private val foodList = mutableListOf<FoodInfo>() // 适配器内部维护的数据列表
// private val debouncer = Debouncer(2000)
// private lateinit var previewView: PreviewView
//
// private val cameraUtils: CameraUtils by lazy {
// CameraUtils(this)
// }
//
// private val collectionAdapter: FoodCollectionAdapter by lazy {
// FoodCollectionAdapter(foodCollectionList).apply {
//// setOnItemClickListener { _, _, position ->
//// if (list[position].isShowCamera) {
////// takePhoto()
//// cameraHelper.openCamera()
//// }
//// }
// addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
//// list.removeAt(position)
//// collectionAdapter.notifyItemRemoved(position)
//// collectionAdapter.notifyItemRangeChanged(position, list.size)
// foodCollectionList[position].let {
// it.bitmap = null
// it.isShowCamera = true
// it.isFinish = false
// }
// notifyItemChanged(position)
// }
// }
// }
//
// override fun getViewModel(): BaseViewModel {
// return viewModel
// }
//
// override fun inflateViewBinding(): ActivityFoodCollectionBinding {
// return ActivityFoodCollectionBinding.inflate(layoutInflater)
// }
//
// private val cameraCallback: (Uri) -> Unit = { uri ->
// val index = foodCollectionList.indexOfFirst { it.bitmap == null }
// if (index == -1) {
// ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
// rerurn@ cameraCallback
// }
// ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
//// val cropBitmap = BitmapCropper.cropCenter(
//// original = bitmap,
//// targetWidth = 1300, targetHeight = 900,
//// //offsetX = 30, offsetY = 100
//// )
// val file = BitmapSaver.saveToAppFilesDir(
// bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
// )
// Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
//
// foodCollectionList[index].let {
// it.bitmap = bitmap
// it.isShowCamera = false
// it.imageUri = file?.toUri()
// }
// collectionAdapter.notifyItemChanged(index)
// }
// }
//
// @SuppressLint("NotifyDataSetChanged")
// private fun takePhoto() {
// val count = foodCollectionList.count { it.bitmap != null }
// if (count >= MAX_COUNT) {
// ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
// return
// }
// cameraUtils.takePhoto(cameraCallback)
// }
//
// override fun initialize() {
// cameraUtils.initCamera()
// val previewBinding =
// LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
// previewView = previewBinding.previewView.also {
// it.updateLayoutParams {
// width = 180.dp
// height = 180.dp
// }
// }
// cameraUtils.setPreviewController(previewView)
// // 初始化 CameraHelper
//// cameraHelper = CameraHelper(
//// context = this,
//// caller = this,
//// authority = "${packageName}.fileprovider"
//// ) { uri, path ->
//// //if (foodCollectionList.size < 6) {
//// // val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
//// // foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
//// //} else {
//// // foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
//// //}
//// //collectionAdapter.notifyDataSetChanged()
//// cameraCallback(uri)
//// }
//
// binding.ivBack.setOnClickListener {
// val intent = Intent(this, MainActivity::class.java)
// startActivity(intent)
// finish()
// }
// repeat(MAX_COUNT) {
// foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
// }
// binding.rvFoodCollection.let {
// it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
// it.adapter = collectionAdapter
// }
//
// binding.btnFoodSearch.setOnClickListener {
// searchInfo()
// }
//
// binding.btnSave.setOnClickListener {
// if (selectedFoodName.isNullOrBlank()) {
// Toast.makeText(this, "请选择菜品名称", Toast.LENGTH_SHORT).show()
// return@setOnClickListener
// }
// if (foodCollectionList.size <= 1) {
// Toast.makeText(this, "请拍摄菜品照片", Toast.LENGTH_SHORT).show()
// return@setOnClickListener
// }
// vectorThread()
// }
//
// 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
// }
// }
//
// binding.btnCollectedGoods.setOnClickListener {
// startActivity(Intent(this, CollectedDataActivity::class.java))
// }
// binding.btnTakePhoto.clickWithDebounce {
// binding.btnTakePhoto.text = "拍照"
// val count = foodCollectionList.count { it.bitmap != null }
// if (count >= MAX_COUNT) {
// ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
// return@clickWithDebounce
// }
//// cameraHelper.openCamera()
// takePhoto()
// }
// binding.btnClearData.setOnClickListener { clearData() }
// 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 {
// foodCollectionList
// .filter { it.bitmap != null }
// .forEachIndexed { index, it ->
// image2VectorTask(it, index)
// }
// runOnUiThread {
// window.decorView.postDelayed({
// //Loading.dismiss()
// hideWaitingDialog()
// }, 1000)
// }
// }.start()
// }
//
// private fun initBox() {
// if (box == null) {
// box = ObjectBox.boxStore.boxFor(Food::class)
// }
// }
//
// private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
// initBox()
// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
//
// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
// Timber.tag("mzf1").e(base64Str)
//// viewModel.postImageData(
//// context,
//// foodId = selectedFoodId.toString(),
//// foodName = selectedFoodName.toString(),
//// foodVector = base64Str,
//// uri = item.imageUri!!
//// )
// box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector))
// foodCollectionList[position].let {
// it.imageVector = imageVector
// it.isFinish = true
// }
//
// runOnUiThread {
// collectionAdapter.notifyItemChanged(position)
// }
// }
//
//
// private fun searchInfo() {
// debouncer.debounce {
// viewModel.searchByFoodName(binding.editFoodName.text.toString())
// }
// }
//
// override fun registerDataChange() {
// super.registerDataChange()
// lifecycleScope.launch {
// viewModel.searchFoodInfoList.collect {
//// foodList.clear()
//// foodList.addAll(it)
// 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()
// }
// }
// )
// }
//
// private var clickIndex = -1
//
// @SuppressLint("NotifyDataSetChanged")
// private fun clearData() {
// foodCollectionList.forEach {
// it.bitmap = null
// it.isShowCamera = true
// it.isFinish = false
// }
// collectionAdapter.notifyDataSetChanged()
//
// clickIndex = -1
// binding.editFoodName.setText("")
//// foodList.clear()
//// foodAdapter.updateData(mutableListOf())
//// foodAdapter.notifyDataSetChanged()
// //loadEmptyView()
// }
//
// override fun onResume() {
// super.onResume()
// cameraUtils.bind()
// binding.llCameraFlag.run {
// visibility = View.VISIBLE
// postDelayed({
// visibility = View.GONE
// }, 3000)
// }
// }
//
// override fun onPause() {
// super.onPause()
// cameraUtils.unbind()
// binding.llCameraFlag.visibility = View.VISIBLE
// }
//
//}
@@ -51,7 +51,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
}
}
}
private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
// private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
private fun saveFoodVector(list: List<FoodVector>) {
val vectorList = list.map { vt ->
val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")?.map { it.toFloatOrNull()?:0.0f }?.toFloatArray()
@@ -63,6 +63,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
foodVector = foodVector
)
}
val box = ObjectBox.boxStore.boxFor(Food::class)
box.put(vectorList)
}
@@ -25,8 +25,10 @@ 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.activity.fragment.CollectFragment
import com.sw.dualscreen.adapter.SearchFoodAdapter
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
@@ -36,12 +38,16 @@ 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.ResetBoxEvent
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
@@ -58,7 +64,12 @@ 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.runBlocking
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.json.JSONObject
@@ -69,6 +80,7 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.concurrent.Executors
import kotlin.collections.get
import kotlin.math.roundToInt
/**
@@ -135,6 +147,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initialize() {
super.initialize()
box = ObjectBox.boxStore.boxFor(Food::class)
isPageVisible = true
addBackEventListener()
//FoodModule.init(this)
@@ -262,7 +275,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.previewView.visibility = View.VISIBLE
binding.ivImg.visibility = View.GONE
resumeAnalysis()
viewModel.cleanIdentifiedFoodInfoList()
// viewModel.cleanIdentifiedFoodInfoList()
return
}
pauseAnalysis()
@@ -318,6 +331,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
SensorScaleUtils.addWeightListener { weight ->
Timber.d("registerDataChange weight = $weight")
if (isPageVisible.not() || isStartRecognize.not()) {
if (ActivityManager.currentActivity() is MainActivity) {
isPageVisible = true
}
return@addWeightListener
}
val isWeightChange = weight - lastWeight > 0.05
@@ -328,8 +344,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun recognizeByWeight(weight: Double, isWeightChange: Boolean, block: () -> Unit = {}) {
presentation?.updateWeight(weight)
if (weight <= 0.005) {//余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
if (weight <= 0.005) {
////余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
isRecognitionFood = true
return
}
if (isWeightChange && isRecognitionFood) { // 大于50g
@@ -368,7 +386,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
failCount++
hideWaitingDialog()
// ToastUtils.showToast("拍照异常,请手动放置后重新识别")
shutdownCamera()
setupCamera()
@@ -389,40 +406,43 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val queryData = GsonUtils.toJson(list)
val recData = GsonUtils.toJson(scoreList)
Timber.d("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
}
//接口已处理排序
// 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(list2)
searchFoodList.addAll(list)
adapter.notifyDataSetChanged()
//adapter.updateData(list)
if (list2.isNotEmpty()) {
checkedItem = list2[0]
checkedItem!!.photoUri = lastPhotoUri
checkedItem!!.isChecked = true
checkedItem = list[0].also {
it.photoUri = lastPhotoUri
it.isChecked = true
}
adapter.notifyItemChanged(0)
updateCurrentFood(checkedItem)
lastPhotoUri = null
// lastPhotoUri = null
LightManager.closeRedLight()
binding.flPay.visible()
binding.tvToSearch.let {
it.text = "以上都不是,手动搜索"
it.visible()
}
} else {
binding.flPay.gone()
binding.tvToSearch.gone()
}
}
// private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> {
@@ -653,14 +673,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.tvTitleTime.text = dateTime
}
private var isPageVisible = true
public var isPageVisible = true
override fun onResume() {
super.onResume()
isPageVisible = true
// 启动定时器
handler.postDelayed(timeoutRunnable, TIME_OUT)
// ToastUtils.showToast("isPageVisible=$isPageVisible")
if (isFirstOpen) {
//首次
setupSecondaryDisplay()
@@ -778,6 +798,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
foodOrderId = orderId
block()
if (checkedItem!!.isFromSearch == true) {
//当前菜名为手动搜索选择,非识别结果,保存向量数据
saveFoodVector(checkedItem!!)
}
}
}
}
@@ -886,6 +911,111 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
/**
* 将uri转为文件和向量数据并提交
*/
private fun saveFoodVector(foodInfo: FoodInfo) {
try {
lastPhotoUri?.let { uri ->
uri2File(uri) { 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) {
Timber.tag(TAG)
.d("是否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<String, RequestBody>()
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 = "]")
Timber.tag(TAG).d("json=$foodVectorJson")
params["foodVector"] = foodVectorJson.toRequestBody()
val idList = viewModel.uploadCollectFoodPics(files, params)
if (idList.isNullOrEmpty()) {
loadRemindDialog("未返回id")
Timber.tag(TAG).d("idList为空")
return
}
Timber.tag(TAG)
.d("uploadCollectFoodPics-已采集向量总数:${box.all.filter { it.isDel.not() }.size}条")
val filterList = box.all.filter { it.collectId == idList[0] }
if (filterList.isNotEmpty()) {
loadRemindDialog("返回的id${idList[0]}已存在")
return
}
box.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 lateinit var box: Box<Food>
private fun uri2File(uri: Uri, block: (File?, FloatArray?) -> Unit) {
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
val imageVector = try {
FoodModule.bitmap2FloatArray(bitmap)
} catch (e: Exception) {
e.printStackTrace()
return@let
}
val imageFile = BitmapSaver.saveToAppFilesDir(
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${imageFile?.absolutePath}")
block(imageFile, imageVector)
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
}
}
override fun onDestroy() {
//SensorScaleUtils.closeScale()
faceTaskJob?.cancel()
@@ -1091,13 +1221,18 @@ 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()
}
if (TextUtils.isEmpty(foodName)) {
LightManager.closeGreenLight()
LightManager.closeRedLight()
//binding.layoutRescan.visibility = View.VISIBLE
binding.flPay.visible()
binding.tvToSearch.let {
it.text = "未识别到,手动搜索"
it.text = "未识别到,手动搜索"
it.visible()
}
} else {
@@ -1117,4 +1252,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
// @Subscribe(threadMode = ThreadMode.MAIN)
// public fun onResetBox(event: ResetBoxEvent) {
// ObjectBox.boxStore.close()
// ObjectBox.init(this)
// }
}
@@ -36,6 +36,7 @@ import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import timber.log.Timber
@@ -51,7 +52,12 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
private var selectedFoodId: String? = ""
private var selectedFoodName: String? = ""
private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
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))
@@ -192,7 +198,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
}
binding.btnFoodSearch.setOnClickListener {
searchInfo()
searchFood()
}
binding.btnSave.setOnClickListener {
@@ -209,7 +215,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchInfo()
searchFood()
val imm =
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
@@ -236,15 +242,14 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
it.adapter = searchFoodAdapter
}
searchInfo()
searchFood()
}
@SuppressLint("NotifyDataSetChanged")
private fun upload() {
lifecycleScope.launch {
val totalFileCount = foodCollectionList.count { it.imageFile != null }
settingActivity?.showWaitingDialog("图片上传中0/$totalFileCount")
settingActivity?.showWaitingDialog2("图片上传中0/$totalFileCount")
val params = HashMap<String, RequestBody>()
//params["placeId"] = restId.toRequestBody()
params["foodId"] = checkedItem!!.foodId.toRequestBody()
@@ -256,7 +261,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
uploadImage = { batch ->
val files = batch.map { it.imageFile }
val foodVectorList =
batch.filter { it.imageVector != null }.map { it.imageVector!!.joinToString(separator = ",", prefix = "[", postfix = "]")}
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")
@@ -264,12 +275,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
settingActivity?.viewModel?.uploadCollectFoodPics(files, params)
},
onProgress = { count, batch, idList ->
activity?.runOnUiThread {
settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount")
//batch.forEach {
// it.uploadSuccess = true
//}
Thread {
runBlocking {
activity?.runOnUiThread {
settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount")
}
val foodList = batch.mapIndexed { index, it ->
Food(
collectId = if (index < idList.size) idList[index] else null,
@@ -279,23 +291,28 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
version = GlobalData.foodModelVersion
)
}
box.put(foodList)
initBox()
box?.put(foodList)
activity?.runOnUiThread {
batch.forEach { it.uploadSuccess = true }
batch.forEach { it.isFinish = true }
collectionAdapter.notifyDataSetChanged()
}
}.start()
}
},
onError = {
settingActivity?.hideWaitingDialog()
activity?.runOnUiThread {
binding.root.postDelayed({
settingActivity?.hideWaitingDialog()
ToastUtils.showToast("上传失败,请稍后重试")
}, 1000)
}
},
onComplete = {
//vectorThread()
binding.root.postDelayed({
settingActivity?.hideWaitingDialog()
ToastUtils.showToast("上传成功")
}, 1000)
}
).processUploads()
}
@@ -320,59 +337,59 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
// }
}
private fun vectorThread() {
settingActivity?.showWaitingDialog("加载中……")
Thread {
foodCollectionList
// .filter { it.bitmap != null }
// .filter { it.imageVector != null }
.forEachIndexed { index, it ->
if (it.imageVector == null) {
return@forEachIndexed
}
image2VectorTask(imageVector = it.imageVector!!, index)
}
activity?.runOnUiThread {
binding.root.postDelayed({
settingActivity?.hideWaitingDialog()
}, 1000)
}
}.start()
}
private fun image2VectorTask(imageVector: FloatArray?, position: Int) {
if (imageVector == null) return
//// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
////
////// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
////// Timber.tag("mzf1").e(base64Str)
//////// viewModel.postImageData(
//////// context,
//////// foodId = selectedFoodId.toString(),
//////// foodName = selectedFoodName.toString(),
//////// foodVector = base64Str,
//////// uri = item.imageUri!!
//////// )
// box.put(
// Food(
// collectId = null,
// foodId = checkedItem!!.foodId,
// foodName = checkedItem!!.foodName,
// foodVector = imageVector,
// version = "1.0.0"
// )
// )
// if (position > -1) {
// foodCollectionList[position].isFinish = true
// private fun vectorThread() {
// settingActivity?.showWaitingDialog("加载中……")
// Thread {
// foodCollectionList
//// .filter { it.bitmap != null }
//// .filter { it.imageVector != null }
// .forEachIndexed { index, it ->
// if (it.imageVector == null) {
// return@forEachIndexed
// }
// image2VectorTask(imageVector = it.imageVector!!, index)
// }
// activity?.runOnUiThread {
// collectionAdapter.notifyItemChanged(position)
// binding.root.postDelayed({
// settingActivity?.hideWaitingDialog()
// }, 1000)
// }
// }.start()
// }
// private fun image2VectorTask(imageVector: FloatArray?, position: Int) {
// if (imageVector == null) return
////// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
//////
//////// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
//////// Timber.tag("mzf1").e(base64Str)
////////// viewModel.postImageData(
////////// context,
////////// foodId = selectedFoodId.toString(),
////////// foodName = selectedFoodName.toString(),
////////// foodVector = base64Str,
////////// uri = item.imageUri!!
////////// )
//// box.put(
//// Food(
//// collectId = null,
//// foodId = checkedItem!!.foodId,
//// foodName = checkedItem!!.foodName,
//// foodVector = imageVector,
//// version = "1.0.0"
//// )
//// )
//// if (position > -1) {
//// foodCollectionList[position].isFinish = true
//// activity?.runOnUiThread {
//// collectionAdapter.notifyItemChanged(position)
//// }
//// }
// }
}
@SuppressLint("NotifyDataSetChanged")
private fun searchInfo() {
private fun searchFood() {
debouncer.debounce {
settingActivity?.searchByFoodName(binding.editFoodName.text.toString()) {
searchFoodList.clear()
@@ -399,8 +416,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
clickIndex = -1
binding.editFoodName.setText("")
searchFoodList.clear()
searchFoodAdapter.notifyDataSetChanged()
searchFood()
}
override fun onResume() {
@@ -5,7 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import com.sw.dualscreen.databinding.DialogWarnBinding
class WarnDialog(
class RemindDialog(
context: Context,
var content:String,
var cancelBlock: () -> Unit = {},
@@ -31,5 +31,4 @@ class WarnDialog(
}
}
}
@@ -145,3 +145,7 @@ data class FoodVector(
val foodVector: String? = null,
val version: String? = null
)
data class ResetBoxEvent(
var num: Int = 0
)
@@ -48,6 +48,8 @@ data class FoodInfo(
var score: Int = 0,
var isChecked: Boolean = false,
var photoUri: Uri? = null,
//true-搜索结果数据,false-识别查询数据
var isFromSearch: Boolean? = null
// @SerializedName("foodTypeAndRealIntakeVoList")
// val foodTypeAndRealIntakeVoList: List<FoodTypeAndRealIntakeVo>? = listOf(),
@@ -25,7 +25,35 @@ data class FoodCollectionBean(
var isShowCamera: Boolean = false,
var isFinish:Boolean = false,
var uploadSuccess:Boolean = false
)
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FoodCollectionBean
if (isShowCamera != other.isShowCamera) return false
if (isFinish != other.isFinish) return false
if (uploadSuccess != other.uploadSuccess) return false
if (imageFile != other.imageFile) return false
if (imageUri != other.imageUri) return false
if (bitmap != other.bitmap) return false
if (!imageVector.contentEquals(other.imageVector)) return false
return true
}
override fun hashCode(): Int {
var result = isShowCamera.hashCode()
result = 31 * result + isFinish.hashCode()
result = 31 * result + uploadSuccess.hashCode()
result = 31 * result + (imageFile?.hashCode() ?: 0)
result = 31 * result + (imageUri?.hashCode() ?: 0)
result = 31 * result + (bitmap?.hashCode() ?: 0)
result = 31 * result + (imageVector?.contentHashCode() ?: 0)
return result
}
}
data class CollectedFoodInfo(
// var id: String? = null,
@@ -1,9 +1,11 @@
package com.sw.dualscreen.objbox
import com.sw.dualscreen.utils.DateTimeUtil
import io.objectbox.annotation.Entity
import io.objectbox.annotation.HnswIndex
import io.objectbox.annotation.Id
import io.objectbox.annotation.VectorDistanceType
import java.time.LocalDateTime
@Entity
data class Food(
@@ -12,6 +14,8 @@ data class Food(
var foodId: String? = null,
var foodName: String? = null,
var version: String? = null,
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now()),
var isDel: Boolean = false,
var otherField: String? = null,
@HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT)
var foodVector: FloatArray? = null
@@ -23,10 +27,12 @@ data class Food(
other as Food
if (id != other.id) return false
if (isDel != other.isDel) return false
if (collectId != other.collectId) return false
if (foodId != other.foodId) return false
if (foodName != other.foodName) return false
if (version != other.version) return false
if (createTime != other.createTime) return false
if (otherField != other.otherField) return false
if (!foodVector.contentEquals(other.foodVector)) return false
@@ -35,14 +41,15 @@ data class Food(
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + isDel.hashCode()
result = 31 * result + (collectId?.hashCode() ?: 0)
result = 31 * result + (foodId?.hashCode() ?: 0)
result = 31 * result + (foodName?.hashCode() ?: 0)
result = 31 * result + (version?.hashCode() ?: 0)
result = 31 * result + createTime.hashCode()
result = 31 * result + (otherField?.hashCode() ?: 0)
result = 31 * result + (foodVector?.contentHashCode() ?: 0)
return result
}
}
@@ -1,18 +1,14 @@
package com.sw.dualscreen.objbox
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.sw.dualscreen.MyApp
import com.sw.dualscreen.utils.AssetsTool
import com.sw.dualscreen.utils.GsonUtils
import com.sw.dualscreen.utils.ImageUtil
import com.sw.plate.App
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import io.objectbox.query.Query
import org.pytorch.IValue
import org.pytorch.Module
import org.pytorch.torchvision.TensorImageUtils
@@ -23,22 +19,27 @@ import java.io.IOException
import java.io.InputStream
object FoodModule {
private const val TAG = "FoodModule"
private const val THRESHOLD = 0.8
private var module_mobile: Module? = null
// private const val THRESHOLD = 0.0
private lateinit var module: Module
private lateinit var box: Box<Food>
private lateinit var embeddingsList: List<List<Float>>
private lateinit var labelsList: IntArray
private lateinit var classInfo: FoodClassInfo
// private lateinit var embeddingsList: List<List<Float>>
// private lateinit var labelsList: IntArray
// private lateinit var classInfo: FoodClassInfo
val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
val DEFAULT_FOOD_INDEX = -1
// val DEFAULT_FOOD_INDEX = -1
@SuppressLint("SuspiciousIndentation")
fun init(context: Context, block: () -> Unit = {}) {
Thread {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
module = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
box = ObjectBox.boxStore.boxFor(Food::class)
//初始化默认重新拉取数据,先清空本地数据
// ObjectBox.boxStore.runInTx {
if (box.all.isNotEmpty()) {
box.removeAll()
}
@@ -58,68 +59,79 @@ object FoodModule {
}
fun bitmap2FloatArray(bitmap: Bitmap): FloatArray {
var tensorStartTime = System.currentTimeMillis()
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
bitmap,
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
if (module_mobile == null) {
init(App.getContext())
}
val outputTensor = module_mobile!!.forward(IValue.from(inputTensor)).toTensor()
Timber.tag(TAG).d("bitmap2FloatArray-inputTensor耗时:${System.currentTimeMillis() - tensorStartTime}")
// if (module_mobile == null) {
// init(App.getContext())
// }
tensorStartTime = System.currentTimeMillis()
val outputTensor = module.forward(IValue.from(inputTensor)).toTensor()
Timber.tag(TAG).d("bitmap2FloatArray-toTensor耗时:${System.currentTimeMillis() - tensorStartTime}")
return outputTensor.dataAsFloatArray
}
fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
return uri2FloatArray(uri)?.let {
queryFood(it, queryCount)
}
}
// fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
// return uri2FloatArray(uri)?.let {
// queryFood(it, queryCount)
// }
// }
/**
* 返回识别物品名称列表
*/
fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> {
val floatArray = bitmap2FloatArray(bitmap)
return queryFood(floatArray, queryCount)
}
// fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> {
// val floatArray = bitmap2FloatArray(bitmap)
// return queryFood(floatArray, queryCount)
// }
/**
* 返回识别物品IdNameScore对象列表
*/
fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val floatArray = bitmap2FloatArray(bitmap)
return queryFoodNameScore(floatArray, queryCount)
}
// fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
// val floatArray = bitmap2FloatArray(bitmap)
// return queryFoodNameScore(floatArray, queryCount)
// }
fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List<IdNameScore> {
val startTime = System.currentTimeMillis()
val query: Query<Food> =
box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
Timber.tag(TAG).d("queryFoodNameScore-已采集向量总数:${box.all.filter { it.isDel.not() }.size}")
box.store.runInTx { }
val query = box.query()
.equal(Food_.isDel, false)
.and()
.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
.build()
//查询比较分数
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
val idScoreList = query.findIdsWithScores()
// val idScoreList = query.findIdsWithScores()
val objScoreList = query.findWithScores()
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}")
val nameScoreList = mutableListOf<IdNameScore>()
idScoreList.forEach {
objScoreList.forEach {
val food = it.get()
nameScoreList.add(
IdNameScore(
id = it.id,
name = box.get(it.id).foodName ?: "",
id = food.id,
name = food.foodName ?: "",
score = it.score
)
)
}
val nameScoreData = GsonUtils.toJson(nameScoreList)
Timber.tag("FoodModule")
.d("registerDataChange,queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
query.close()
return nameScoreList
}
fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val startTime = System.currentTimeMillis()
val floatArray = bitmap2FloatArray(bitmap)
Timber.tag("FoodModule")
.d("registerDataChange,bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
if (nameScoreList.isEmpty()) {
return emptyList()
@@ -147,35 +159,40 @@ object FoodModule {
return sortedScoreList
}
fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
val query: Query<Food> =
box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
//查询比较分数
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
val map = mutableMapOf<String, Int>()
query.findIdsWithScores().forEach {
val food = box.get(it.id)
Timber.d("${food.foodName}|${food.foodId}|${food.collectId}|${food.version}|${food.otherField}|${it.score}")
if (1 - it.score >= THRESHOLD) {
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
food.foodName?.let { key ->
val count = map[key] ?: 0
map.put(key, count + 1)
}
}
}
val list = map.entries.sortedByDescending { it.value }.map { it.key }
return list
// fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
// val query = box.query()
// .equal(Food_.isDel, false)
// .and()
// .nearestNeighbors(Food_.foodVector, floatArray, queryCount)
// .build()
//// val query: Query<Food> =
//// box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
// //查询比较分数
//// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
// val map = mutableMapOf<String, Int>()
// val nameScoreList = queryFoodNameScore(floatArray, queryCount)
// nameScoreList.filter { it.score < 0.05 }.forEach {
// val count = map[it.name] ?: 0
// map[it.name] = count + 1
// query.findIdsWithScores().forEach {
// val food = box.get(it.id)
// Timber.d("${food.foodName}|${food.foodId}|${food.collectId}|${food.version}|${food.otherField}|${it.score}")
// if (1 - it.score >= THRESHOLD) {
// //FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
// food.foodName?.let { key ->
// val count = map[key] ?: 0
// map.put(key, count + 1)
// }
// }
// }
// val list = map.entries.sortedByDescending { it.value }.map { it.key }
// return list
}
//
//// val map = mutableMapOf<String, Int>()
//// val nameScoreList = queryFoodNameScore(floatArray, queryCount)
//// nameScoreList.filter { it.score < 0.05 }.forEach {
//// val count = map[it.name] ?: 0
//// map[it.name] = count + 1
//// }
//// val list = map.entries.sortedByDescending { it.value }.map { it.key }
//// return list
// }
data class IdNameScore(
val id: Long,
@@ -183,36 +200,36 @@ object FoodModule {
val score: Double
)
fun initDefFoodData(context: Context, action: () -> Unit = {}) {
//val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX }
//if (count > 0) {
// return
// fun initDefFoodData(context: Context, action: () -> Unit = {}) {
// //val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX }
// //if (count > 0) {
// // return
// //}
// val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
// val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
// val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
//
// embeddingsList =
// Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
// labelsList = Gson().fromJson(labelsJson, IntArray::class.java)
// classInfo =
// Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
//
// val foodMap = classInfo.idx_to_class
// embeddingsList.forEachIndexed { index, floatList ->
// val classIdx = labelsList[index]
// val foodName = foodMap["$classIdx"]
// val array = floatList.toFloatArray()
// box.put(
// Food(
// foodName = foodName,
// foodVector = array,
// //foodIdx = DEFAULT_FOOD_INDEX
// )
// )
// }
// action()
// }
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
embeddingsList =
Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
labelsList = Gson().fromJson(labelsJson, IntArray::class.java)
classInfo =
Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
val foodMap = classInfo.idx_to_class
embeddingsList.forEachIndexed { index, floatList ->
val classIdx = labelsList[index]
val foodName = foodMap["$classIdx"]
val array = floatList.toFloatArray()
box.put(
Food(
foodName = foodName,
foodVector = array,
//foodIdx = DEFAULT_FOOD_INDEX
)
)
}
action()
}
/**
@@ -225,7 +242,7 @@ object FoodModule {
*/
fun copyAssetToCache(context: Context, fileName: String): String? {
// 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦
val cacheDir = context.getCacheDir()
val cacheDir = context.cacheDir
if (!cacheDir.exists()) {
cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建
}
@@ -239,7 +256,7 @@ object FoodModule {
// 创建文件,如果创建成功,就返回true
val res = outPath.createNewFile()
if (res) {
`is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节
`is` = context.assets.open(fileName) // 拿到main/assets目录的输入流,用于读取字节
fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath
val buf = ByteArray(`is`.available()) // 缓存区
var byteCount: Int
@@ -248,7 +265,7 @@ object FoodModule {
while ((`is`.read(buf).also { byteCount = it }) != -1) {
fos.write(buf, 0, byteCount)
}
return outPath.getAbsolutePath()
return outPath.absolutePath
}
} catch (e: IOException) {
e.printStackTrace()
@@ -18,10 +18,12 @@ package com.sw.dualscreen.objbox
import android.content.Context
import android.util.Log
import io.objectbox.Box
import io.objectbox.BoxStore
import io.objectbox.BoxStoreBuilder
import io.objectbox.android.Admin
import io.objectbox.android.ObjectBoxLiveData
import io.objectbox.config.DebugFlags
import io.objectbox.exception.DbException
import io.objectbox.exception.FileCorruptException
import io.objectbox.sync.Sync
@@ -64,6 +66,7 @@ object ObjectBox {
boxStore = try {
MyObjectBox.builder()
.androidContext(context.applicationContext)
.debugFlags(DebugFlags.LOG_QUERY_PARAMETERS)
.build()
} catch (e: DbException) {
if (e.javaClass == DbException::class.java || e is FileCorruptException) {
@@ -111,4 +114,42 @@ object ObjectBox {
return true
}
// /**
// * 安全获取 Food 实体 foodVector 字段的向量索引(推荐写法)
// */
// fun getFoodVectorIndex(box: Box<Food>): VectorIndex? {
// return try {
// // 核心修复:使用自动生成的 Food_.foodVector 而非字符串,避免拼写错误
// val foodVectorProperty = Food_.foodVector
// // 获取向量索引(适配 ObjectBox 3.5+ 所有版本)
// foodVectorProperty.vectorIndex
// } catch (e: Exception) {
// // 容错处理:打印错误信息,避免崩溃
// e.printStackTrace()
// null
// }
// }
//
// // 刷新索引的调用示例(结合你之前的更新场景)
// fun refreshFoodVectorIndex(box: Box<Food>) {
// val vectorIndex = getFoodVectorIndex(box)
// vectorIndex?.run {
// // 同步刷新索引(解决查询异常问题)
// refresh()
// // 等待索引构建完成(超时 5 秒,避免无限等待)
// waitUntilBuilt(5000)
// }
// }
//
// // 更新 isDel 字段后的完整调用流程
// fun markFoodAsDeleted(box: Box<Food>, foodId: Long) {
// val food = box[foodId]
// if (food != null) {
// food.isDel = true
// box.put(food)
// // 更新后刷新索引
// refreshFoodVectorIndex(box)
// }
// }
}
@@ -0,0 +1,32 @@
package com.sw.dualscreen.utils
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Date
object DateTimeUtil {
const val YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"
fun formatDateTime(dateTime: LocalDateTime, pattern: String = YYYY_MM_DD_HH_MM_SS): String {
val formatter = DateTimeFormatter.ofPattern(pattern)
return dateTime.format(formatter)
}
fun convert(dateStr: String, pattern: String = YYYY_MM_DD_HH_MM_SS): Date {
val formatter = DateTimeFormatter.ofPattern(pattern)
val ldt = LocalDateTime.parse(dateStr, formatter)
val zdt = ldt.atZone(ZoneId.systemDefault())
return Date.from(zdt.toInstant())
}
fun main() {
val now = LocalDateTime.now()
println("默认格式: ${formatDateTime(now)}")
println("自定义格式: ${formatDateTime(now, "yyyy年MM月dd日 HH时mm分ss秒")}")
}
}
@@ -3,8 +3,6 @@ package com.sw.dualscreen.view
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -13,18 +11,15 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.GenericItemAdapter
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
import com.sw.dualscreen.adapter.SearchFoodAdapter
import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.BottomSheetDialogBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.KeyboardUtils
import com.sw.dualscreen.viewmodel.UserViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import androidx.core.graphics.drawable.toDrawable
/**
* 底部搜索结果弹窗
@@ -43,7 +38,7 @@ class CustomBottomSheetDialog(
searchFoodList.forEachIndexed { index, info ->
searchFoodList[index].isChecked = index == position
}
val item = searchFoodList[position]
val item = searchFoodList[position].also { it.isFromSearch = true }
Timber.d("itemClick ${item.foodName}, position = $position")
itemClickCallback(item)
checkedItem = item
@@ -79,7 +74,7 @@ class CustomBottomSheetDialog(
private fun setDialogStyle() {
dialog?.window?.let {
it.decorView.background = ColorDrawable(Color.TRANSPARENT)
it.decorView.background = Color.TRANSPARENT.toDrawable()
it.decorView.setPadding(0, 0, 0, 0)
it.attributes?.apply {
width = ViewGroup.LayoutParams.MATCH_PARENT
@@ -118,15 +113,17 @@ class CustomBottomSheetDialog(
it.adapter = adapter
}
binding.ivSearch.setOnClickListener {
searchInfo()
searchFood()
}
KeyboardUtils.setupEditorAction(binding.etSearch) {
searchInfo()
searchFood()
}
registerDataChange()
searchFood()
}
private fun searchInfo(){
private fun searchFood(){
debouncer.debounce {
viewModel.searchByFoodName(binding.etSearch.text.toString()) {
searchFoodList.clear()
@@ -138,13 +135,13 @@ class CustomBottomSheetDialog(
@SuppressLint("NotifyDataSetChanged")
private fun registerDataChange() {
lifecycleScope.launch {
viewModel.searchFoodInfoList.collect {
searchFoodList.clear()
searchFoodList.addAll(it)
adapter.notifyDataSetChanged()
}
}
// lifecycleScope.launch {
// viewModel.searchFoodInfoList.collect {
// searchFoodList.clear()
// searchFoodList.addAll(it)
// adapter.notifyDataSetChanged()
// }
// }
}
// private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
@@ -176,13 +173,13 @@ class CustomBottomSheetDialog(
override fun onCancel(dialog: DialogInterface) {
Timber.d("onCancel")
viewModel.cleanSearchFoodInfoList()
// viewModel.cleanSearchFoodInfoList()
super.onCancel(dialog)
}
override fun dismiss() {
Timber.d("dismiss")
viewModel.cleanSearchFoodInfoList()
// viewModel.cleanSearchFoodInfoList()
super.dismiss()
}
}
@@ -40,12 +40,12 @@ class UserViewModel : BaseViewModel() {
private val faceApi: FaceApi = FaceApi()
// 识别出的列表
private val _identifiedFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
val identifiedFoodInfoList: StateFlow<List<FoodInfo>> = _identifiedFoodInfoList
// private val _identifiedFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
// val identifiedFoodInfoList: StateFlow<List<FoodInfo>> = _identifiedFoodInfoList
// 搜索出的食物列表
private val _searchFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
val searchFoodInfoList: StateFlow<List<FoodInfo>> = _searchFoodInfoList
// private val _searchFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
// val searchFoodInfoList: StateFlow<List<FoodInfo>> = _searchFoodInfoList
// 就餐数据
// private val _nutritionData = MutableStateFlow<UserNutritionData?>(null)
@@ -225,6 +225,8 @@ class UserViewModel : BaseViewModel() {
val list: List<CollectedFoodInfo> = response.data ?: emptyList()
block(list)
}
} else {
block(emptyList())
}
}
}
@@ -278,10 +280,10 @@ class UserViewModel : BaseViewModel() {
// }
// }
fun cleanIdentifiedFoodInfoList() {
Timber.tag(TAG).d("cleanIdentifiedFoodInfoList")
_identifiedFoodInfoList.value = emptyList<FoodInfo>()
}
// fun cleanIdentifiedFoodInfoList() {
// Timber.tag(TAG).d("cleanIdentifiedFoodInfoList")
// _identifiedFoodInfoList.value = emptyList<FoodInfo>()
// }
/**
* 搜索食物
@@ -293,15 +295,15 @@ class UserViewModel : BaseViewModel() {
if (parseResponse(response)) {
val list = response.data ?: emptyList()
action(list)
_identifiedFoodInfoList.value = response.data ?: emptyList()
// _identifiedFoodInfoList.value = response.data ?: emptyList()
}
}
}
fun cleanSearchFoodInfoList() {
Timber.tag(TAG).d("cleanSearchFoodInfoList")
_searchFoodInfoList.value = emptyList<FoodInfo>()
}
// fun cleanSearchFoodInfoList() {
// Timber.tag(TAG).d("cleanSearchFoodInfoList")
// _searchFoodInfoList.value = emptyList<FoodInfo>()
// }
// /**
// * 获取用户就餐数据
@@ -383,7 +385,7 @@ class UserViewModel : BaseViewModel() {
val response = repository.getFoodInfo(foodName = foodName)
if (parseResponse(response)) {
val list = response.data ?: emptyList()
_identifiedFoodInfoList.value = list
// _identifiedFoodInfoList.value = list
action(list)
} else {
action(emptyList())
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

+1 -1
View File
@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFF3232"/>
<corners android:radius="10dp"/>
<corners android:radius="12dp"/>
</shape>
@@ -3,8 +3,8 @@
android:color="#30000000">
<item>
<shape android:shape="rectangle">
<solid android:color="#FF0032C8" />
<corners android:radius="10dp" />
<solid android:color="#FFFF3232" />
<corners android:radius="12dp" />
</shape>
</item>
</ripple>
@@ -4,8 +4,8 @@
<item>
<shape android:shape="rectangle">
<solid android:color="@color/white"/>
<stroke android:color="#FF0032C8" android:width="1dp"/>
<corners android:radius="10dp"/>
<stroke android:color="#FFFF3232" android:width="1dp"/>
<corners android:radius="12dp"/>
</shape>
</item>
</ripple>
@@ -3,14 +3,14 @@
<item android:state_checked="true">
<shape android:shape="rectangle">
<solid android:color="#FFFF3232"/>
<size android:width="120dp" android:height="5dp"/>
<size android:width="120dp" android:height="3dp"/>
<corners android:radius="5dp"/>
</shape>
</item>
<item android:state_checked="false">
<shape android:shape="rectangle">
<solid android:color="@color/transparent"/>
<size android:width="120dp" android:height="5dp"/>
<size android:width="120dp" android:height="3dp"/>
<corners android:radius="5dp"/>
</shape>
</item>
@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="88dp"
android:layout_marginTop="24dp">
<ImageView
android:id="@+id/ivBack"
android:layout_width="68dp"
android:layout_height="68dp"
android:padding="10dp"
android:src="@drawable/ic_back" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="已采集菜品"
android:textColor="@color/black"
android:textSize="26sp" />
</FrameLayout>
<FrameLayout
android:id="@+id/flSearchBlock"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginHorizontal="20dp"
android:background="@drawable/bg_gray">
<EditText
android:id="@+id/etInputGoods"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00000000"
android:gravity="center"
android:hint="输入菜品名称"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="80dp"
android:textColor="#FF666666"
android:textColorHint="#FFC7C8DC"
android:textSize="30sp"
android:inputType="text"
android:imeOptions="actionSearch" />
<ImageView
android:id="@+id/ivGoodsSearch"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="16dp"
android:padding="14dp"
android:src="@drawable/ic_goods_search" />
</FrameLayout>
<!-- <com.scwang.smart.refresh.layout.SmartRefreshLayout-->
<!-- android:id="@+id/refreshLayout"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="0dp"-->
<!-- android:layout_marginHorizontal="130dp"-->
<!-- android:layout_marginTop="40dp"-->
<!-- android:layout_marginBottom="58dp"-->
<!-- app:layout_constraintBottom_toTopOf="@id/dividerLine"-->
<!-- app:layout_constraintTop_toBottomOf="@id/flSearchBlock">-->
<!-- <com.scwang.smart.refresh.header.ClassicsHeader-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content" />-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:layout_margin="10dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="8"
tools:listitem="@layout/list_item_collected_data" />
<!-- <com.scwang.smart.refresh.footer.ClassicsFooter-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content" />-->
<!-- </com.scwang.smart.refresh.layout.SmartRefreshLayout>-->
</LinearLayout>
@@ -1,182 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="88dp"
android:layout_marginTop="24dp">
<ImageView
android:id="@+id/ivBack"
android:layout_width="68dp"
android:layout_height="68dp"
android:padding="15dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_back" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="菜品采集"
android:textColor="@color/black"
android:textSize="26sp" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="24dp">
<FrameLayout
android:id="@+id/flPreview"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_marginStart="24dp">
<androidx.cardview.widget.CardView
android:id="@+id/flCameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="2dp"
app:cardCornerRadius="10dp"
app:cardElevation="0dp"/>
<LinearLayout
android:id="@+id/llCameraFlag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/white"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:scaleType="fitCenter"
android:src="@drawable/ic_camera256"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="图片采集"
android:textColor="#ffb4b4c8"
android:textSize="16sp" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<Button
android:id="@+id/btnTakePhoto"
android:layout_width="140dp"
android:layout_height="80dp"
android:gravity="center"
android:text="开始拍照"
android:textColor="#ffffff"
android:textSize="18sp" />
<Button
android:id="@+id/btnClearData"
android:layout_width="120dp"
android:layout_height="80dp"
android:gravity="center"
android:text="清除"
android:layout_marginHorizontal="10dp"
android:textColor="#ffffff"
android:textSize="18sp" />
<Button
android:id="@+id/btnCollectedGoods"
android:layout_width="220dp"
android:layout_height="80dp"
android:gravity="center"
android:text="已采集菜品"
android:textColor="#ffffff"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodCollection"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginHorizontal="3dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:itemCount="3"
tools:listitem="@layout/list_item_food_collection" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="66dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="24dp"
android:layout_marginRight="24dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<EditText
android:id="@+id/editFoodName"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/bg_gray"
android:gravity="center_vertical"
android:hint="输入菜品名称"
android:imeOptions="actionSearch"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="26sp" />
<Button
android:id="@+id/btnFoodSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:text="菜品检索"
android:textColor="#ffffff"
android:textSize="26sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="24dp"
android:layout_weight="1" />
<Button
android:id="@+id/btnSave"
android:layout_width="260dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:layout_marginVertical="24dp"
android:text="保存"
android:textColor="#ffffff"
android:textSize="26sp" />
</LinearLayout>
+4 -4
View File
@@ -157,17 +157,17 @@
android:id="@+id/flPay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:visibility="gone">
<!-- android:background="@color/white"-->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnPay"
android:layout_width="match_parent"
android:layout_margin="28dp"
android:layout_height="87dp"
android:layout_margin="32dp"
android:layout_height="100dp"
android:textStyle="bold"
android:text="去结算"
android:textSize="35sp"
android:textSize="40sp"
android:background="@drawable/bg_btn_save"
android:textColor="@color/white"/>
+29 -18
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<include
@@ -16,42 +16,53 @@
android:layout_height="90dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rbBusiness"
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="50dp"
android:layout_marginEnd="25dp"
android:layout_weight="1"
android:layout_weight="1" />
<RadioButton
android:id="@+id/rbBusiness"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:button="@null"
tools:checked="true"
android:drawableBottom="@drawable/line_shape_horizontal"
android:gravity="center_horizontal"
android:maxLines="1"
android:paddingHorizontal="50dp"
android:paddingTop="8dp"
android:paddingBottom="35dp"
android:text="经营设置"
android:paddingTop="6dp"
android:paddingBottom="32dp"
android:textColor="@color/setting_radio"
android:textSize="30sp"
android:drawableBottom="@drawable/line_shape_horizontal"
android:textStyle="bold" />
android:textStyle="bold"
tools:checked="true" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<RadioButton
android:id="@+id/rbCollect"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="25dp"
android:layout_marginEnd="50dp"
android:layout_weight="1"
android:button="@null"
android:drawableBottom="@drawable/line_shape_horizontal"
android:gravity="center_horizontal"
android:maxLines="1"
android:paddingHorizontal="50dp"
android:paddingTop="8dp"
android:paddingBottom="35dp"
android:text="餐品采集"
android:paddingTop="6dp"
android:paddingBottom="32dp"
android:textColor="@color/setting_radio"
android:textSize="30sp"
android:drawableBottom="@drawable/line_shape_horizontal"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</RadioGroup>
<androidx.fragment.app.FragmentContainerView
@@ -69,6 +69,7 @@
android:layout_marginHorizontal="14dp"
app:spanCount="2"
tools:itemCount="12"
android:overScrollMode="never"
tools:listitem="@layout/list_item_search_food"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/>
</LinearLayout>
+19 -15
View File
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="500dp"
android:layout_height="300dp"
android:gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical"
android:background="@drawable/bg_white_radius12"
tools:ignore="HardcodedText">
@@ -11,42 +11,46 @@
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="20dp"
android:src="@drawable/ic_tip_warn"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tvWarnContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginHorizontal="30dp"
android:layout_marginVertical="30dp"
android:layout_marginTop="20dp"
android:gravity="center"
tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存"
tools:text="这里显示弹窗内容?\n这里显示弹窗内容?\n这里显示弹窗内容"
android:textColor="#ff141428"
android:maxLines="3"
android:ellipsize="end"
android:textSize="22sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="90dp"
android:gravity="center"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel"
android:layout_width="130dp"
android:layout_height="60dp"
android:background="@drawable/bg_white_stroke_blue_ripple"
android:layout_width="135dp"
android:layout_height="50dp"
android:background="@drawable/bg_white_stroke_red_ripple"
android:text="取消"
android:gravity="center"
android:textColor="#FF0033CC"
android:textColor="#FFFF3232"
android:textSize="22sp" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="130dp"
android:layout_height="60dp"
android:layout_marginStart="40dp"
android:background="@drawable/bg_blue_ripple"
android:layout_width="135dp"
android:layout_height="50dp"
android:layout_marginStart="50dp"
android:background="@drawable/bg_red_ripple"
android:text="确认"
android:gravity="center"
android:textColor="@color/white"
@@ -187,9 +187,9 @@
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="match_parent"
android:layout_height="87dp"
android:layout_height="100dp"
android:layout_marginHorizontal="32dp"
android:layout_marginBottom="48dp"
android:layout_marginBottom="32dp"
android:text="确定"
android:background="@drawable/bg_btn_save"
android:textColor="@color/white"
+6 -4
View File
@@ -111,7 +111,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_height="382dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
@@ -164,20 +164,22 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="24dp"
android:layout_marginTop="18dp"
android:minHeight="348dp"
android:layout_weight="1"
app:spanCount="2"
tools:itemCount="10"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:listitem="@layout/list_item_search_food"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="87dp"
android:layout_height="100dp"
android:layout_marginHorizontal="32dp"
android:layout_marginBottom="48dp"
android:layout_marginBottom="32dp"
android:layout_marginTop="20dp"
android:text="保存"
android:background="@drawable/bg_btn_save"
android:textColor="@color/white"
+11 -12
View File
@@ -10,7 +10,7 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="80dp"
android:layout_height="100dp"
android:src="@drawable/ic_empty_view"
tools:ignore="ContentDescription" />
@@ -18,18 +18,17 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginBottom="39dp"
android:text="暂无采集记录"
android:textColor="#ff000000"
android:textSize="36sp"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请确认菜品名称,或稍后重试"
android:textColor="#ff999999"
android:gravity="center"
android:textSize="30sp" />
<!-- <TextView-->
<!-- android:id="@+id/tvContent"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="请确认菜品名称,或稍后重试"-->
<!-- android:textColor="#ff999999"-->
<!-- android:gravity="center"-->
<!-- android:textSize="30sp" />-->
</LinearLayout>
@@ -42,7 +42,7 @@
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="75dp"
android:paddingHorizontal="13dp"
android:paddingHorizontal="18dp"
android:src="@drawable/ic_close2"
tools:ignore="ContentDescription"
android:visibility="visible"/>