采集向量优化调试

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" val objectboxVersion = "5.0.1"
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion") debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion") // releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
// implementation("io.objectbox:objectbox-fulltext:5.0.1")
implementation(libs.androidx.recyclerview) implementation(libs.androidx.recyclerview)
+11 -1
View File
@@ -5,7 +5,7 @@
"entities": [ "entities": [
{ {
"id": "1:594331511073099531", "id": "1:594331511073099531",
"lastPropertyId": "9:4394472487596452023", "lastPropertyId": "11:7949771231314213889",
"name": "Food", "name": "Food",
"properties": [ "properties": [
{ {
@@ -45,6 +45,16 @@
"id": "9:4394472487596452023", "id": "9:4394472487596452023",
"name": "otherField", "name": "otherField",
"type": 9 "type": 9
},
{
"id": "10:5704890122356491756",
"name": "createTime",
"type": 9
},
{
"id": "11:7949771231314213889",
"name": "isDel",
"type": 1
} }
], ],
"relations": [] "relations": []
+2 -1
View File
@@ -50,7 +50,8 @@
</activity> </activity>
<activity <activity
android:name=".activity.MainActivity" android:name=".activity.MainActivity"
android:exported="true"> android:exported="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity> </activity>
<!-- <activity--> <!-- <activity-->
<!-- android:name=".activity.FoodCollectionActivity"--> <!-- 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.R
import com.sw.dualscreen.adapter.CollectedFoodNewAdapter import com.sw.dualscreen.adapter.CollectedFoodNewAdapter
import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding 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.addOnActionSearchListener
import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.hideKeyboard import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.ext.visible import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.ResetBoxEvent
import com.sw.dualscreen.objbox.CollectedFoodInfo import com.sw.dualscreen.objbox.CollectedFoodInfo
import com.sw.dualscreen.objbox.Food import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
import io.objectbox.Box import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import org.greenrobot.eventbus.EventBus
class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() { class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
@@ -47,6 +51,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
box = ObjectBox.boxStore.boxFor(Food::class)
//binding.root.setOnClickListener { it.hideKeyboard() } //binding.root.setOnClickListener { it.hideKeyboard() }
binding.rvFoodList.let { binding.rvFoodList.let {
it.layoutManager = LinearLayoutManager(this) 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") @SuppressLint("NotifyDataSetChanged")
private fun getCollectGoods(searchName: String? = null) { private fun getCollectGoods(searchName: String? = null) {
@@ -181,7 +186,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
} }
private fun loadDeleteDialog(position: Int) { private fun loadDeleteDialog(position: Int) {
WarnDialog( RemindDialog(
context = context, context = context,
content = "请确认是否删除菜品:${list[position].foodName}", content = "请确认是否删除菜品:${list[position].foodName}",
confirmBlock = { confirmBlock = {
@@ -195,8 +200,16 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess -> viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess ->
if (deleteSuccess) { if (deleteSuccess) {
Thread { Thread {
val filterIdList = box?.all?.filter { it.foodName == food.foodName }?.map { it.id } val filterFoodList = box.all.filter { it.foodName == food.foodName }
box?.removeByIds(filterIdList) 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 { runOnUiThread {
hideWaitingDialog() hideWaitingDialog()
adapter.removeAt(position) 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>) { private fun saveFoodVector(list: List<FoodVector>) {
val vectorList = list.map { vt -> val vectorList = list.map { vt ->
val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")?.map { it.toFloatOrNull()?:0.0f }?.toFloatArray() val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")?.map { it.toFloatOrNull()?:0.0f }?.toFloatArray()
@@ -63,6 +63,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
foodVector = foodVector foodVector = foodVector
) )
} }
val box = ObjectBox.boxStore.boxFor(Food::class)
box.put(vectorList) box.put(vectorList)
} }
@@ -25,8 +25,10 @@ import androidx.recyclerview.widget.GridLayoutManager
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.activity.fragment.CollectFragment
import com.sw.dualscreen.adapter.SearchFoodAdapter import com.sw.dualscreen.adapter.SearchFoodAdapter
import com.sw.dualscreen.databinding.ActivityMainBinding import com.sw.dualscreen.databinding.ActivityMainBinding
import com.sw.dualscreen.dialog.RemindDialog
import com.sw.dualscreen.ext.dp import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.load 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.FoodInfo
import com.sw.dualscreen.model.response.FoodOrder import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.PaySuccessEvent 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.model.response.UserFaceModel
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodModule import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.objbox.FoodModule.IdNameScore import com.sw.dualscreen.objbox.FoodModule.IdNameScore
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.presentation.MainScreenPresentation import com.sw.dualscreen.presentation.MainScreenPresentation
import com.sw.dualscreen.sdk.SensorScaleUtils import com.sw.dualscreen.sdk.SensorScaleUtils
import com.sw.dualscreen.socket.TcpClient import com.sw.dualscreen.socket.TcpClient
import com.sw.dualscreen.utils.ActivityManager
import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.BitmapSaver
import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.GsonUtils 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.FaceDatabase
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Job 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.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.json.JSONObject import org.json.JSONObject
@@ -69,6 +80,7 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Locale import java.util.Locale
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.collections.get
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
@@ -135,6 +147,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
box = ObjectBox.boxStore.boxFor(Food::class)
isPageVisible = true isPageVisible = true
addBackEventListener() addBackEventListener()
//FoodModule.init(this) //FoodModule.init(this)
@@ -262,7 +275,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.previewView.visibility = View.VISIBLE binding.previewView.visibility = View.VISIBLE
binding.ivImg.visibility = View.GONE binding.ivImg.visibility = View.GONE
resumeAnalysis() resumeAnalysis()
viewModel.cleanIdentifiedFoodInfoList() // viewModel.cleanIdentifiedFoodInfoList()
return return
} }
pauseAnalysis() pauseAnalysis()
@@ -318,6 +331,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
SensorScaleUtils.addWeightListener { weight -> SensorScaleUtils.addWeightListener { weight ->
Timber.d("registerDataChange weight = $weight") Timber.d("registerDataChange weight = $weight")
if (isPageVisible.not() || isStartRecognize.not()) { if (isPageVisible.not() || isStartRecognize.not()) {
if (ActivityManager.currentActivity() is MainActivity) {
isPageVisible = true
}
return@addWeightListener return@addWeightListener
} }
val isWeightChange = weight - lastWeight > 0.05 val isWeightChange = weight - lastWeight > 0.05
@@ -328,8 +344,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun recognizeByWeight(weight: Double, isWeightChange: Boolean, block: () -> Unit = {}) { private fun recognizeByWeight(weight: Double, isWeightChange: Boolean, block: () -> Unit = {}) {
presentation?.updateWeight(weight) presentation?.updateWeight(weight)
if (weight <= 0.005) {//余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1 if (weight <= 0.005) {
////余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
isRecognitionFood = true isRecognitionFood = true
return
} }
if (isWeightChange && isRecognitionFood) { // 大于50g if (isWeightChange && isRecognitionFood) { // 大于50g
@@ -368,7 +386,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
failCount++ failCount++
hideWaitingDialog() hideWaitingDialog()
// ToastUtils.showToast("拍照异常,请手动放置后重新识别")
shutdownCamera() shutdownCamera()
setupCamera() setupCamera()
@@ -389,40 +406,43 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val queryData = GsonUtils.toJson(list) val queryData = GsonUtils.toJson(list)
val recData = GsonUtils.toJson(scoreList) val recData = GsonUtils.toJson(scoreList)
Timber.d("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData") Timber.d("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData")
if (list.isEmpty()) {
binding.tvToSearch.let {
it.text = "未查询到,手动搜索"
it.visible()
}
return
}
list.forEach { foodInfo -> list.forEach { foodInfo ->
val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName } val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName }
val score = scoreItem?.score ?: 0.0 val score = scoreItem?.score ?: 0.0
foodInfo.score = ((1 - score) * 10000).roundToInt() foodInfo.score = ((1 - score) * 10000).roundToInt()
} }
val list2 = try { //接口已处理排序
val orderList = scoreList.map { it.name.trim() } // val list2 = try {
list.sortedBy { orderList.indexOf(it.foodName) } // val orderList = scoreList.map { it.name.trim() }
} catch (e: Exception) { // list.sortedBy { orderList.indexOf(it.foodName) }
e.printStackTrace() // } catch (e: Exception) {
list // e.printStackTrace()
} // list
// }
searchFoodList.clear() searchFoodList.clear()
searchFoodList.addAll(list2) searchFoodList.addAll(list)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
//adapter.updateData(list)
if (list2.isNotEmpty()) { checkedItem = list[0].also {
checkedItem = list2[0] it.photoUri = lastPhotoUri
checkedItem!!.photoUri = lastPhotoUri it.isChecked = true
checkedItem!!.isChecked = true }
adapter.notifyItemChanged(0) adapter.notifyItemChanged(0)
updateCurrentFood(checkedItem) updateCurrentFood(checkedItem)
lastPhotoUri = null // lastPhotoUri = null
LightManager.closeRedLight() LightManager.closeRedLight()
binding.flPay.visible()
binding.tvToSearch.let { binding.tvToSearch.let {
it.text = "以上都不是,手动搜索" it.text = "以上都不是,手动搜索"
it.visible() it.visible()
} }
} else {
binding.flPay.gone()
binding.tvToSearch.gone()
}
} }
// private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> { // private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> {
@@ -653,14 +673,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.tvTitleTime.text = dateTime binding.tvTitleTime.text = dateTime
} }
private var isPageVisible = true public var isPageVisible = true
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
isPageVisible = true isPageVisible = true
// 启动定时器 // 启动定时器
handler.postDelayed(timeoutRunnable, TIME_OUT) handler.postDelayed(timeoutRunnable, TIME_OUT)
// ToastUtils.showToast("isPageVisible=$isPageVisible")
if (isFirstOpen) { if (isFirstOpen) {
//首次 //首次
setupSecondaryDisplay() setupSecondaryDisplay()
@@ -778,6 +798,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
foodOrderId = orderId foodOrderId = orderId
block() 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() { override fun onDestroy() {
//SensorScaleUtils.closeScale() //SensorScaleUtils.closeScale()
faceTaskJob?.cancel() faceTaskJob?.cancel()
@@ -1091,13 +1221,18 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
hideWaitingDialog() hideWaitingDialog()
Timber.d("registerDataChange photoUri 识别数据名称:$foodName") 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)) { if (TextUtils.isEmpty(foodName)) {
LightManager.closeGreenLight() LightManager.closeGreenLight()
LightManager.closeRedLight() LightManager.closeRedLight()
//binding.layoutRescan.visibility = View.VISIBLE //binding.layoutRescan.visibility = View.VISIBLE
binding.flPay.visible()
binding.tvToSearch.let { binding.tvToSearch.let {
it.text = "未识别到,手动搜索" it.text = "未识别到,手动搜索"
it.visible() it.visible()
} }
} else { } 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.Box
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import timber.log.Timber import timber.log.Timber
@@ -51,7 +52,12 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
private var selectedFoodId: String? = "" private var selectedFoodId: String? = ""
private var selectedFoodName: 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 { private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply {
repeat(MAX_COUNT) { repeat(MAX_COUNT) {
add(FoodCollectionBean(isShowCamera = true)) add(FoodCollectionBean(isShowCamera = true))
@@ -192,7 +198,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
} }
binding.btnFoodSearch.setOnClickListener { binding.btnFoodSearch.setOnClickListener {
searchInfo() searchFood()
} }
binding.btnSave.setOnClickListener { binding.btnSave.setOnClickListener {
@@ -209,7 +215,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
binding.editFoodName.setOnEditorActionListener { v, actionId, event -> binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) { if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchInfo() searchFood()
val imm = val imm =
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0) imm.hideSoftInputFromWindow(v.windowToken, 0)
@@ -236,15 +242,14 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
it.adapter = searchFoodAdapter it.adapter = searchFoodAdapter
} }
searchInfo() searchFood()
} }
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun upload() { private fun upload() {
lifecycleScope.launch { lifecycleScope.launch {
val totalFileCount = foodCollectionList.count { it.imageFile != null } val totalFileCount = foodCollectionList.count { it.imageFile != null }
settingActivity?.showWaitingDialog("图片上传中0/$totalFileCount") settingActivity?.showWaitingDialog2("图片上传中0/$totalFileCount")
val params = HashMap<String, RequestBody>() val params = HashMap<String, RequestBody>()
//params["placeId"] = restId.toRequestBody() //params["placeId"] = restId.toRequestBody()
params["foodId"] = checkedItem!!.foodId.toRequestBody() params["foodId"] = checkedItem!!.foodId.toRequestBody()
@@ -256,7 +261,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
uploadImage = { batch -> uploadImage = { batch ->
val files = batch.map { it.imageFile } val files = batch.map { it.imageFile }
val foodVectorList = 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 = val foodVectorJson =
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]") foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
Timber.tag(TAG).d("json=$foodVectorJson") Timber.tag(TAG).d("json=$foodVectorJson")
@@ -264,12 +275,13 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
settingActivity?.viewModel?.uploadCollectFoodPics(files, params) settingActivity?.viewModel?.uploadCollectFoodPics(files, params)
}, },
onProgress = { count, batch, idList -> onProgress = { count, batch, idList ->
activity?.runOnUiThread {
settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount")
//batch.forEach { //batch.forEach {
// it.uploadSuccess = true // it.uploadSuccess = true
//} //}
Thread { runBlocking {
activity?.runOnUiThread {
settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount")
}
val foodList = batch.mapIndexed { index, it -> val foodList = batch.mapIndexed { index, it ->
Food( Food(
collectId = if (index < idList.size) idList[index] else null, collectId = if (index < idList.size) idList[index] else null,
@@ -279,23 +291,28 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
version = GlobalData.foodModelVersion version = GlobalData.foodModelVersion
) )
} }
box.put(foodList) initBox()
box?.put(foodList)
activity?.runOnUiThread { activity?.runOnUiThread {
batch.forEach { it.uploadSuccess = true } batch.forEach { it.isFinish = true }
collectionAdapter.notifyDataSetChanged() collectionAdapter.notifyDataSetChanged()
} }
}.start()
} }
}, },
onError = { onError = {
settingActivity?.hideWaitingDialog()
activity?.runOnUiThread { activity?.runOnUiThread {
binding.root.postDelayed({
settingActivity?.hideWaitingDialog()
ToastUtils.showToast("上传失败,请稍后重试") ToastUtils.showToast("上传失败,请稍后重试")
}, 1000)
} }
}, },
onComplete = { onComplete = {
//vectorThread() //vectorThread()
binding.root.postDelayed({
settingActivity?.hideWaitingDialog() settingActivity?.hideWaitingDialog()
ToastUtils.showToast("上传成功")
}, 1000)
} }
).processUploads() ).processUploads()
} }
@@ -320,59 +337,59 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
// } // }
} }
private fun vectorThread() { // private fun vectorThread() {
settingActivity?.showWaitingDialog("加载中……") // settingActivity?.showWaitingDialog("加载中……")
Thread { // Thread {
foodCollectionList // foodCollectionList
// .filter { it.bitmap != null } //// .filter { it.bitmap != null }
// .filter { it.imageVector != null } //// .filter { it.imageVector != null }
.forEachIndexed { index, it -> // .forEachIndexed { index, it ->
if (it.imageVector == null) { // if (it.imageVector == null) {
return@forEachIndexed // return@forEachIndexed
} // }
image2VectorTask(imageVector = it.imageVector!!, index) // 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
// activity?.runOnUiThread { // 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") @SuppressLint("NotifyDataSetChanged")
private fun searchInfo() { private fun searchFood() {
debouncer.debounce { debouncer.debounce {
settingActivity?.searchByFoodName(binding.editFoodName.text.toString()) { settingActivity?.searchByFoodName(binding.editFoodName.text.toString()) {
searchFoodList.clear() searchFoodList.clear()
@@ -399,8 +416,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
clickIndex = -1 clickIndex = -1
binding.editFoodName.setText("") binding.editFoodName.setText("")
searchFoodList.clear() searchFood()
searchFoodAdapter.notifyDataSetChanged()
} }
override fun onResume() { override fun onResume() {
@@ -5,7 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import com.sw.dualscreen.databinding.DialogWarnBinding import com.sw.dualscreen.databinding.DialogWarnBinding
class WarnDialog( class RemindDialog(
context: Context, context: Context,
var content:String, var content:String,
var cancelBlock: () -> Unit = {}, var cancelBlock: () -> Unit = {},
@@ -31,5 +31,4 @@ class WarnDialog(
} }
} }
} }
@@ -145,3 +145,7 @@ data class FoodVector(
val foodVector: String? = null, val foodVector: String? = null,
val version: 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 score: Int = 0,
var isChecked: Boolean = false, var isChecked: Boolean = false,
var photoUri: Uri? = null, var photoUri: Uri? = null,
//true-搜索结果数据,false-识别查询数据
var isFromSearch: Boolean? = null
// @SerializedName("foodTypeAndRealIntakeVoList") // @SerializedName("foodTypeAndRealIntakeVoList")
// val foodTypeAndRealIntakeVoList: List<FoodTypeAndRealIntakeVo>? = listOf(), // val foodTypeAndRealIntakeVoList: List<FoodTypeAndRealIntakeVo>? = listOf(),
@@ -25,7 +25,35 @@ data class FoodCollectionBean(
var isShowCamera: Boolean = false, var isShowCamera: Boolean = false,
var isFinish:Boolean = false, var isFinish:Boolean = false,
var uploadSuccess: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( data class CollectedFoodInfo(
// var id: String? = null, // var id: String? = null,
@@ -1,9 +1,11 @@
package com.sw.dualscreen.objbox package com.sw.dualscreen.objbox
import com.sw.dualscreen.utils.DateTimeUtil
import io.objectbox.annotation.Entity import io.objectbox.annotation.Entity
import io.objectbox.annotation.HnswIndex import io.objectbox.annotation.HnswIndex
import io.objectbox.annotation.Id import io.objectbox.annotation.Id
import io.objectbox.annotation.VectorDistanceType import io.objectbox.annotation.VectorDistanceType
import java.time.LocalDateTime
@Entity @Entity
data class Food( data class Food(
@@ -12,6 +14,8 @@ data class Food(
var foodId: String? = null, var foodId: String? = null,
var foodName: String? = null, var foodName: String? = null,
var version: String? = null, var version: String? = null,
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now()),
var isDel: Boolean = false,
var otherField: String? = null, var otherField: String? = null,
@HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT) @HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT)
var foodVector: FloatArray? = null var foodVector: FloatArray? = null
@@ -23,10 +27,12 @@ data class Food(
other as Food other as Food
if (id != other.id) return false if (id != other.id) return false
if (isDel != other.isDel) return false
if (collectId != other.collectId) return false if (collectId != other.collectId) return false
if (foodId != other.foodId) return false if (foodId != other.foodId) return false
if (foodName != other.foodName) return false if (foodName != other.foodName) return false
if (version != other.version) return false if (version != other.version) return false
if (createTime != other.createTime) return false
if (otherField != other.otherField) return false if (otherField != other.otherField) return false
if (!foodVector.contentEquals(other.foodVector)) return false if (!foodVector.contentEquals(other.foodVector)) return false
@@ -35,14 +41,15 @@ data class Food(
override fun hashCode(): Int { override fun hashCode(): Int {
var result = id.hashCode() var result = id.hashCode()
result = 31 * result + isDel.hashCode()
result = 31 * result + (collectId?.hashCode() ?: 0) result = 31 * result + (collectId?.hashCode() ?: 0)
result = 31 * result + (foodId?.hashCode() ?: 0) result = 31 * result + (foodId?.hashCode() ?: 0)
result = 31 * result + (foodName?.hashCode() ?: 0) result = 31 * result + (foodName?.hashCode() ?: 0)
result = 31 * result + (version?.hashCode() ?: 0) result = 31 * result + (version?.hashCode() ?: 0)
result = 31 * result + createTime.hashCode()
result = 31 * result + (otherField?.hashCode() ?: 0) result = 31 * result + (otherField?.hashCode() ?: 0)
result = 31 * result + (foodVector?.contentHashCode() ?: 0) result = 31 * result + (foodVector?.contentHashCode() ?: 0)
return result return result
} }
} }
@@ -1,18 +1,14 @@
package com.sw.dualscreen.objbox package com.sw.dualscreen.objbox
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.sw.dualscreen.MyApp import com.sw.dualscreen.MyApp
import com.sw.dualscreen.utils.AssetsTool
import com.sw.dualscreen.utils.GsonUtils import com.sw.dualscreen.utils.GsonUtils
import com.sw.dualscreen.utils.ImageUtil import com.sw.dualscreen.utils.ImageUtil
import com.sw.plate.App
import io.objectbox.Box import io.objectbox.Box
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
import io.objectbox.query.Query
import org.pytorch.IValue import org.pytorch.IValue
import org.pytorch.Module import org.pytorch.Module
import org.pytorch.torchvision.TensorImageUtils import org.pytorch.torchvision.TensorImageUtils
@@ -23,22 +19,27 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
object FoodModule { object FoodModule {
private const val TAG = "FoodModule"
private const val THRESHOLD = 0.8 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 box: Box<Food>
private lateinit var embeddingsList: List<List<Float>>
private lateinit var labelsList: IntArray // private lateinit var embeddingsList: List<List<Float>>
private lateinit var classInfo: FoodClassInfo // private lateinit var labelsList: IntArray
// private lateinit var classInfo: FoodClassInfo
val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f) val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.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 = {}) { fun init(context: Context, block: () -> Unit = {}) {
Thread { 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) box = ObjectBox.boxStore.boxFor(Food::class)
//初始化默认重新拉取数据,先清空本地数据 //初始化默认重新拉取数据,先清空本地数据
// ObjectBox.boxStore.runInTx {
if (box.all.isNotEmpty()) { if (box.all.isNotEmpty()) {
box.removeAll() box.removeAll()
} }
@@ -58,68 +59,79 @@ object FoodModule {
} }
fun bitmap2FloatArray(bitmap: Bitmap): FloatArray { fun bitmap2FloatArray(bitmap: Bitmap): FloatArray {
var tensorStartTime = System.currentTimeMillis()
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor( val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
bitmap, bitmap,
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB 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 NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
) )
if (module_mobile == null) { Timber.tag(TAG).d("bitmap2FloatArray-inputTensor耗时:${System.currentTimeMillis() - tensorStartTime}")
init(App.getContext()) // if (module_mobile == null) {
} // init(App.getContext())
val outputTensor = module_mobile!!.forward(IValue.from(inputTensor)).toTensor() // }
tensorStartTime = System.currentTimeMillis()
val outputTensor = module.forward(IValue.from(inputTensor)).toTensor()
Timber.tag(TAG).d("bitmap2FloatArray-toTensor耗时:${System.currentTimeMillis() - tensorStartTime}")
return outputTensor.dataAsFloatArray return outputTensor.dataAsFloatArray
} }
fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? { // fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
return uri2FloatArray(uri)?.let { // return uri2FloatArray(uri)?.let {
queryFood(it, queryCount) // queryFood(it, queryCount)
} // }
} // }
/** /**
* 返回识别物品名称列表 * 返回识别物品名称列表
*/ */
fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> { // fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> {
val floatArray = bitmap2FloatArray(bitmap) // val floatArray = bitmap2FloatArray(bitmap)
return queryFood(floatArray, queryCount) // return queryFood(floatArray, queryCount)
} // }
/** /**
* 返回识别物品IdNameScore对象列表 * 返回识别物品IdNameScore对象列表
*/ */
fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> { // fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val floatArray = bitmap2FloatArray(bitmap) // val floatArray = bitmap2FloatArray(bitmap)
return queryFoodNameScore(floatArray, queryCount) // return queryFoodNameScore(floatArray, queryCount)
} // }
fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List<IdNameScore> { fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List<IdNameScore> {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val query: Query<Food> = Timber.tag(TAG).d("queryFoodNameScore-已采集向量总数:${box.all.filter { it.isDel.not() }.size}")
box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() 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 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>() val nameScoreList = mutableListOf<IdNameScore>()
idScoreList.forEach { objScoreList.forEach {
val food = it.get()
nameScoreList.add( nameScoreList.add(
IdNameScore( IdNameScore(
id = it.id, id = food.id,
name = box.get(it.id).foodName ?: "", name = food.foodName ?: "",
score = it.score score = it.score
) )
) )
} }
val nameScoreData = GsonUtils.toJson(nameScoreList) val nameScoreData = GsonUtils.toJson(nameScoreList)
Timber.tag("FoodModule") Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
.d("registerDataChange,queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData") query.close()
return nameScoreList return nameScoreList
} }
fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> { fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val floatArray = bitmap2FloatArray(bitmap) val floatArray = bitmap2FloatArray(bitmap)
Timber.tag("FoodModule") Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
.d("registerDataChange,bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
val nameScoreList = queryFoodNameScore(floatArray, queryCount) val nameScoreList = queryFoodNameScore(floatArray, queryCount)
if (nameScoreList.isEmpty()) { if (nameScoreList.isEmpty()) {
return emptyList() return emptyList()
@@ -147,35 +159,40 @@ object FoodModule {
return sortedScoreList return sortedScoreList
} }
fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> { // fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
val query: Query<Food> = // val query = box.query()
box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() // .equal(Food_.isDel, false)
//查询比较分数 // .and()
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" } // .nearestNeighbors(Food_.foodVector, floatArray, queryCount)
val map = mutableMapOf<String, Int>() // .build()
query.findIdsWithScores().forEach { //// val query: Query<Food> =
val food = box.get(it.id) //// box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
Timber.d("${food.foodName}|${food.foodId}|${food.collectId}|${food.version}|${food.otherField}|${it.score}") // //查询比较分数
if (1 - it.score >= THRESHOLD) { //// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
//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 map = mutableMapOf<String, Int>()
// val nameScoreList = queryFoodNameScore(floatArray, queryCount) // query.findIdsWithScores().forEach {
// nameScoreList.filter { it.score < 0.05 }.forEach { // val food = box.get(it.id)
// val count = map[it.name] ?: 0 // Timber.d("${food.foodName}|${food.foodId}|${food.collectId}|${food.version}|${food.otherField}|${it.score}")
// map[it.name] = count + 1 // 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 } // val list = map.entries.sortedByDescending { it.value }.map { it.key }
// return list // 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( data class IdNameScore(
val id: Long, val id: Long,
@@ -183,36 +200,36 @@ object FoodModule {
val score: Double val score: Double
) )
fun initDefFoodData(context: Context, action: () -> Unit = {}) { // fun initDefFoodData(context: Context, action: () -> Unit = {}) {
//val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX } // //val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX }
//if (count > 0) { // //if (count > 0) {
// return // // 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? { fun copyAssetToCache(context: Context, fileName: String): String? {
// 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦 // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦
val cacheDir = context.getCacheDir() val cacheDir = context.cacheDir
if (!cacheDir.exists()) { if (!cacheDir.exists()) {
cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建 cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建
} }
@@ -239,7 +256,7 @@ object FoodModule {
// 创建文件,如果创建成功,就返回true // 创建文件,如果创建成功,就返回true
val res = outPath.createNewFile() val res = outPath.createNewFile()
if (res) { if (res) {
`is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节 `is` = context.assets.open(fileName) // 拿到main/assets目录的输入流,用于读取字节
fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath
val buf = ByteArray(`is`.available()) // 缓存区 val buf = ByteArray(`is`.available()) // 缓存区
var byteCount: Int var byteCount: Int
@@ -248,7 +265,7 @@ object FoodModule {
while ((`is`.read(buf).also { byteCount = it }) != -1) { while ((`is`.read(buf).also { byteCount = it }) != -1) {
fos.write(buf, 0, byteCount) fos.write(buf, 0, byteCount)
} }
return outPath.getAbsolutePath() return outPath.absolutePath
} }
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
@@ -18,10 +18,12 @@ package com.sw.dualscreen.objbox
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import io.objectbox.Box
import io.objectbox.BoxStore import io.objectbox.BoxStore
import io.objectbox.BoxStoreBuilder import io.objectbox.BoxStoreBuilder
import io.objectbox.android.Admin import io.objectbox.android.Admin
import io.objectbox.android.ObjectBoxLiveData import io.objectbox.android.ObjectBoxLiveData
import io.objectbox.config.DebugFlags
import io.objectbox.exception.DbException import io.objectbox.exception.DbException
import io.objectbox.exception.FileCorruptException import io.objectbox.exception.FileCorruptException
import io.objectbox.sync.Sync import io.objectbox.sync.Sync
@@ -64,6 +66,7 @@ object ObjectBox {
boxStore = try { boxStore = try {
MyObjectBox.builder() MyObjectBox.builder()
.androidContext(context.applicationContext) .androidContext(context.applicationContext)
.debugFlags(DebugFlags.LOG_QUERY_PARAMETERS)
.build() .build()
} catch (e: DbException) { } catch (e: DbException) {
if (e.javaClass == DbException::class.java || e is FileCorruptException) { if (e.javaClass == DbException::class.java || e is FileCorruptException) {
@@ -111,4 +114,42 @@ object ObjectBox {
return true 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.annotation.SuppressLint
import android.content.DialogInterface import android.content.DialogInterface
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -13,18 +11,15 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.sw.dualscreen.R 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.SearchFoodAdapter
import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.BottomSheetDialogBinding import com.sw.dualscreen.databinding.BottomSheetDialogBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.KeyboardUtils import com.sw.dualscreen.utils.KeyboardUtils
import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.dualscreen.viewmodel.UserViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import androidx.core.graphics.drawable.toDrawable
/** /**
* 底部搜索结果弹窗 * 底部搜索结果弹窗
@@ -43,7 +38,7 @@ class CustomBottomSheetDialog(
searchFoodList.forEachIndexed { index, info -> searchFoodList.forEachIndexed { index, info ->
searchFoodList[index].isChecked = index == position searchFoodList[index].isChecked = index == position
} }
val item = searchFoodList[position] val item = searchFoodList[position].also { it.isFromSearch = true }
Timber.d("itemClick ${item.foodName}, position = $position") Timber.d("itemClick ${item.foodName}, position = $position")
itemClickCallback(item) itemClickCallback(item)
checkedItem = item checkedItem = item
@@ -79,7 +74,7 @@ class CustomBottomSheetDialog(
private fun setDialogStyle() { private fun setDialogStyle() {
dialog?.window?.let { dialog?.window?.let {
it.decorView.background = ColorDrawable(Color.TRANSPARENT) it.decorView.background = Color.TRANSPARENT.toDrawable()
it.decorView.setPadding(0, 0, 0, 0) it.decorView.setPadding(0, 0, 0, 0)
it.attributes?.apply { it.attributes?.apply {
width = ViewGroup.LayoutParams.MATCH_PARENT width = ViewGroup.LayoutParams.MATCH_PARENT
@@ -118,15 +113,17 @@ class CustomBottomSheetDialog(
it.adapter = adapter it.adapter = adapter
} }
binding.ivSearch.setOnClickListener { binding.ivSearch.setOnClickListener {
searchInfo() searchFood()
} }
KeyboardUtils.setupEditorAction(binding.etSearch) { KeyboardUtils.setupEditorAction(binding.etSearch) {
searchInfo() searchFood()
} }
registerDataChange() registerDataChange()
searchFood()
} }
private fun searchInfo(){ private fun searchFood(){
debouncer.debounce { debouncer.debounce {
viewModel.searchByFoodName(binding.etSearch.text.toString()) { viewModel.searchByFoodName(binding.etSearch.text.toString()) {
searchFoodList.clear() searchFoodList.clear()
@@ -138,13 +135,13 @@ class CustomBottomSheetDialog(
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun registerDataChange() { private fun registerDataChange() {
lifecycleScope.launch { // lifecycleScope.launch {
viewModel.searchFoodInfoList.collect { // viewModel.searchFoodInfoList.collect {
searchFoodList.clear() // searchFoodList.clear()
searchFoodList.addAll(it) // searchFoodList.addAll(it)
adapter.notifyDataSetChanged() // adapter.notifyDataSetChanged()
} // }
} // }
} }
// private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> { // private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
@@ -176,13 +173,13 @@ class CustomBottomSheetDialog(
override fun onCancel(dialog: DialogInterface) { override fun onCancel(dialog: DialogInterface) {
Timber.d("onCancel") Timber.d("onCancel")
viewModel.cleanSearchFoodInfoList() // viewModel.cleanSearchFoodInfoList()
super.onCancel(dialog) super.onCancel(dialog)
} }
override fun dismiss() { override fun dismiss() {
Timber.d("dismiss") Timber.d("dismiss")
viewModel.cleanSearchFoodInfoList() // viewModel.cleanSearchFoodInfoList()
super.dismiss() super.dismiss()
} }
} }
@@ -40,12 +40,12 @@ class UserViewModel : BaseViewModel() {
private val faceApi: FaceApi = FaceApi() private val faceApi: FaceApi = FaceApi()
// 识别出的列表 // 识别出的列表
private val _identifiedFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList()) // private val _identifiedFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
val identifiedFoodInfoList: StateFlow<List<FoodInfo>> = _identifiedFoodInfoList // val identifiedFoodInfoList: StateFlow<List<FoodInfo>> = _identifiedFoodInfoList
// 搜索出的食物列表 // 搜索出的食物列表
private val _searchFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList()) // private val _searchFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
val searchFoodInfoList: StateFlow<List<FoodInfo>> = _searchFoodInfoList // val searchFoodInfoList: StateFlow<List<FoodInfo>> = _searchFoodInfoList
// 就餐数据 // 就餐数据
// private val _nutritionData = MutableStateFlow<UserNutritionData?>(null) // private val _nutritionData = MutableStateFlow<UserNutritionData?>(null)
@@ -225,6 +225,8 @@ class UserViewModel : BaseViewModel() {
val list: List<CollectedFoodInfo> = response.data ?: emptyList() val list: List<CollectedFoodInfo> = response.data ?: emptyList()
block(list) block(list)
} }
} else {
block(emptyList())
} }
} }
} }
@@ -278,10 +280,10 @@ class UserViewModel : BaseViewModel() {
// } // }
// } // }
fun cleanIdentifiedFoodInfoList() { // fun cleanIdentifiedFoodInfoList() {
Timber.tag(TAG).d("cleanIdentifiedFoodInfoList") // Timber.tag(TAG).d("cleanIdentifiedFoodInfoList")
_identifiedFoodInfoList.value = emptyList<FoodInfo>() // _identifiedFoodInfoList.value = emptyList<FoodInfo>()
} // }
/** /**
* 搜索食物 * 搜索食物
@@ -293,15 +295,15 @@ class UserViewModel : BaseViewModel() {
if (parseResponse(response)) { if (parseResponse(response)) {
val list = response.data ?: emptyList() val list = response.data ?: emptyList()
action(list) action(list)
_identifiedFoodInfoList.value = response.data ?: emptyList() // _identifiedFoodInfoList.value = response.data ?: emptyList()
} }
} }
} }
fun cleanSearchFoodInfoList() { // fun cleanSearchFoodInfoList() {
Timber.tag(TAG).d("cleanSearchFoodInfoList") // Timber.tag(TAG).d("cleanSearchFoodInfoList")
_searchFoodInfoList.value = emptyList<FoodInfo>() // _searchFoodInfoList.value = emptyList<FoodInfo>()
} // }
// /** // /**
// * 获取用户就餐数据 // * 获取用户就餐数据
@@ -383,7 +385,7 @@ class UserViewModel : BaseViewModel() {
val response = repository.getFoodInfo(foodName = foodName) val response = repository.getFoodInfo(foodName = foodName)
if (parseResponse(response)) { if (parseResponse(response)) {
val list = response.data ?: emptyList() val list = response.data ?: emptyList()
_identifiedFoodInfoList.value = list // _identifiedFoodInfoList.value = list
action(list) action(list)
} else { } else {
action(emptyList()) 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" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#FFFF3232"/> <solid android:color="#FFFF3232"/>
<corners android:radius="10dp"/> <corners android:radius="12dp"/>
</shape> </shape>
@@ -3,8 +3,8 @@
android:color="#30000000"> android:color="#30000000">
<item> <item>
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<solid android:color="#FF0032C8" /> <solid android:color="#FFFF3232" />
<corners android:radius="10dp" /> <corners android:radius="12dp" />
</shape> </shape>
</item> </item>
</ripple> </ripple>
@@ -4,8 +4,8 @@
<item> <item>
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<solid android:color="@color/white"/> <solid android:color="@color/white"/>
<stroke android:color="#FF0032C8" android:width="1dp"/> <stroke android:color="#FFFF3232" android:width="1dp"/>
<corners android:radius="10dp"/> <corners android:radius="12dp"/>
</shape> </shape>
</item> </item>
</ripple> </ripple>
@@ -3,14 +3,14 @@
<item android:state_checked="true"> <item android:state_checked="true">
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<solid android:color="#FFFF3232"/> <solid android:color="#FFFF3232"/>
<size android:width="120dp" android:height="5dp"/> <size android:width="120dp" android:height="3dp"/>
<corners android:radius="5dp"/> <corners android:radius="5dp"/>
</shape> </shape>
</item> </item>
<item android:state_checked="false"> <item android:state_checked="false">
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<solid android:color="@color/transparent"/> <solid android:color="@color/transparent"/>
<size android:width="120dp" android:height="5dp"/> <size android:width="120dp" android:height="3dp"/>
<corners android:radius="5dp"/> <corners android:radius="5dp"/>
</shape> </shape>
</item> </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:id="@+id/flPay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/white"
android:visibility="gone"> android:visibility="gone">
<!-- android:background="@color/white"-->
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnPay" android:id="@+id/btnPay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_margin="28dp" android:layout_margin="32dp"
android:layout_height="87dp" android:layout_height="100dp"
android:textStyle="bold" android:textStyle="bold"
android:text="去结算" android:text="去结算"
android:textSize="35sp" android:textSize="40sp"
android:background="@drawable/bg_btn_save" android:background="@drawable/bg_btn_save"
android:textColor="@color/white"/> android:textColor="@color/white"/>
+29 -18
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#FFE7EFF8" android:background="#FFE7EFF8"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"> android:orientation="vertical">
<include <include
@@ -16,42 +16,53 @@
android:layout_height="90dp" android:layout_height="90dp"
android:orientation="horizontal"> android:orientation="horizontal">
<RadioButton <Space
android:id="@+id/rbBusiness"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="50dp" android:layout_weight="1" />
android:layout_marginEnd="25dp"
android:layout_weight="1" <RadioButton
android:id="@+id/rbBusiness"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:button="@null" android:button="@null"
tools:checked="true" android:drawableBottom="@drawable/line_shape_horizontal"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:maxLines="1" android:maxLines="1"
android:paddingHorizontal="50dp"
android:paddingTop="8dp"
android:paddingBottom="35dp"
android:text="经营设置" android:text="经营设置"
android:paddingTop="6dp"
android:paddingBottom="32dp"
android:textColor="@color/setting_radio" android:textColor="@color/setting_radio"
android:textSize="30sp" 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 <RadioButton
android:id="@+id/rbCollect" android:id="@+id/rbCollect"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="25dp"
android:layout_marginEnd="50dp"
android:layout_weight="1"
android:button="@null" android:button="@null"
android:drawableBottom="@drawable/line_shape_horizontal"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:maxLines="1" android:maxLines="1"
android:paddingHorizontal="50dp"
android:paddingTop="8dp"
android:paddingBottom="35dp"
android:text="餐品采集" android:text="餐品采集"
android:paddingTop="6dp"
android:paddingBottom="32dp"
android:textColor="@color/setting_radio" android:textColor="@color/setting_radio"
android:textSize="30sp" android:textSize="30sp"
android:drawableBottom="@drawable/line_shape_horizontal"
android:textStyle="bold" /> android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</RadioGroup> </RadioGroup>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
@@ -69,6 +69,7 @@
android:layout_marginHorizontal="14dp" android:layout_marginHorizontal="14dp"
app:spanCount="2" app:spanCount="2"
tools:itemCount="12" tools:itemCount="12"
android:overScrollMode="never"
tools:listitem="@layout/list_item_search_food" tools:listitem="@layout/list_item_search_food"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/>
</LinearLayout> </LinearLayout>
+19 -15
View File
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="500dp" android:layout_width="500dp"
android:layout_height="300dp" android:layout_height="300dp"
android:gravity="center" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical"
android:background="@drawable/bg_white_radius12" android:background="@drawable/bg_white_radius12"
tools:ignore="HardcodedText"> tools:ignore="HardcodedText">
@@ -11,42 +11,46 @@
<ImageView <ImageView
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:layout_marginTop="20dp"
android:src="@drawable/ic_tip_warn" android:src="@drawable/ic_tip_warn"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/tvWarnContent" android:id="@+id/tvWarnContent"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginHorizontal="30dp" android:layout_marginHorizontal="30dp"
android:layout_marginVertical="30dp" android:layout_marginTop="20dp"
android:gravity="center" android:gravity="center"
tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存" tools:text="这里显示弹窗内容?\n这里显示弹窗内容?\n这里显示弹窗内容"
android:textColor="#ff141428" android:textColor="#ff141428"
android:maxLines="3"
android:ellipsize="end"
android:textSize="22sp" /> android:textSize="22sp" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="90dp"
android:gravity="center_vertical" android:gravity="center"
android:orientation="horizontal"> android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel" android:id="@+id/btnCancel"
android:layout_width="130dp" android:layout_width="135dp"
android:layout_height="60dp" android:layout_height="50dp"
android:background="@drawable/bg_white_stroke_blue_ripple" android:background="@drawable/bg_white_stroke_red_ripple"
android:text="取消" android:text="取消"
android:gravity="center" android:gravity="center"
android:textColor="#FF0033CC" android:textColor="#FFFF3232"
android:textSize="22sp" /> android:textSize="22sp" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm" android:id="@+id/btnConfirm"
android:layout_width="130dp" android:layout_width="135dp"
android:layout_height="60dp" android:layout_height="50dp"
android:layout_marginStart="40dp" android:layout_marginStart="50dp"
android:background="@drawable/bg_blue_ripple" android:background="@drawable/bg_red_ripple"
android:text="确认" android:text="确认"
android:gravity="center" android:gravity="center"
android:textColor="@color/white" android:textColor="@color/white"
@@ -187,9 +187,9 @@
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm" android:id="@+id/btnConfirm"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="87dp" android:layout_height="100dp"
android:layout_marginHorizontal="32dp" android:layout_marginHorizontal="32dp"
android:layout_marginBottom="48dp" android:layout_marginBottom="32dp"
android:text="确定" android:text="确定"
android:background="@drawable/bg_btn_save" android:background="@drawable/bg_btn_save"
android:textColor="@color/white" android:textColor="@color/white"
+6 -4
View File
@@ -111,7 +111,7 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList" android:id="@+id/rvFoodList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="400dp" android:layout_height="382dp"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
@@ -164,20 +164,22 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="24dp" android:layout_marginTop="18dp"
android:minHeight="348dp" android:minHeight="348dp"
android:layout_weight="1" android:layout_weight="1"
app:spanCount="2" app:spanCount="2"
tools:itemCount="10" tools:itemCount="10"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:listitem="@layout/list_item_search_food"/> tools:listitem="@layout/list_item_search_food"/>
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSave" android:id="@+id/btnSave"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="87dp" android:layout_height="100dp"
android:layout_marginHorizontal="32dp" android:layout_marginHorizontal="32dp"
android:layout_marginBottom="48dp" android:layout_marginBottom="32dp"
android:layout_marginTop="20dp"
android:text="保存" android:text="保存"
android:background="@drawable/bg_btn_save" android:background="@drawable/bg_btn_save"
android:textColor="@color/white" android:textColor="@color/white"
+11 -12
View File
@@ -10,7 +10,7 @@
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="80dp" android:layout_height="100dp"
android:src="@drawable/ic_empty_view" android:src="@drawable/ic_empty_view"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@@ -18,18 +18,17 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="30dp" android:layout_marginTop="30dp"
android:layout_marginBottom="39dp"
android:text="暂无采集记录" android:text="暂无采集记录"
android:textColor="#ff000000" android:textColor="@color/black"
android:textSize="36sp" android:textSize="40sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <!-- <TextView-->
android:id="@+id/tvContent" <!-- android:id="@+id/tvContent"-->
android:layout_width="wrap_content" <!-- android:layout_width="wrap_content"-->
android:layout_height="wrap_content" <!-- android:layout_height="wrap_content"-->
android:text="请确认菜品名称,或稍后重试" <!-- android:text="请确认菜品名称,或稍后重试"-->
android:textColor="#ff999999" <!-- android:textColor="#ff999999"-->
android:gravity="center" <!-- android:gravity="center"-->
android:textSize="30sp" /> <!-- android:textSize="30sp" />-->
</LinearLayout> </LinearLayout>
@@ -42,7 +42,7 @@
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
android:layout_marginEnd="75dp" android:layout_marginEnd="75dp"
android:paddingHorizontal="13dp" android:paddingHorizontal="18dp"
android:src="@drawable/ic_close2" android:src="@drawable/ic_close2"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:visibility="visible"/> android:visibility="visible"/>