From 2a55ddb6171d877e83646f409798e3dbe7efdbe5 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Thu, 8 Jan 2026 15:02:44 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=87=E9=9B=86=E5=90=91=E9=87=8F=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + app/objectbox-models/default.json | 12 +- app/src/main/AndroidManifest.xml | 3 +- .../activity/CollectedDataActivity.kt | 134 ------- .../activity/CollectedFoodActivity.kt | 23 +- .../activity/FoodCollectionActivity.kt | 370 ------------------ .../sw/dualscreen/activity/InitActivity.kt | 3 +- .../sw/dualscreen/activity/MainActivity.kt | 205 ++++++++-- .../activity/fragment/CollectFragment.kt | 180 +++++---- .../dialog/{WarnDialog.kt => RemindDialog.kt} | 3 +- .../sw/dualscreen/model/response/DataBean.kt | 4 + .../sw/dualscreen/model/response/FoodInfo.kt | 28 +- .../java/com/sw/dualscreen/objbox/DataBean.kt | 30 +- .../java/com/sw/dualscreen/objbox/Food.kt | 9 +- .../com/sw/dualscreen/objbox/FoodModule.kt | 211 +++++----- .../com/sw/dualscreen/objbox/ObjectBox.kt | 41 ++ .../com/sw/dualscreen/utils/DateTimeUtil.kt | 32 ++ .../view/CustomBottomSheetDialog.kt | 37 +- .../sw/dualscreen/viewmodel/UserViewModel.kt | 30 +- .../res/drawable-xxhdpi/ic_empty_view.png | Bin 21426 -> 16459 bytes .../main/res/drawable-xxhdpi/ic_tip_warn.png | Bin 15695 -> 9564 bytes app/src/main/res/drawable/bg_btn_save.xml | 2 +- .../{bg_blue_ripple.xml => bg_red_ripple.xml} | 4 +- ...ple.xml => bg_white_stroke_red_ripple.xml} | 4 +- .../res/drawable/line_shape_horizontal.xml | 4 +- .../res/layout/activity_collected_data.xml | 92 ----- .../res/layout/activity_food_collection.xml | 182 --------- app/src/main/res/layout/activity_main.xml | 8 +- app/src/main/res/layout/activity_setting.xml | 49 ++- .../main/res/layout/bottom_sheet_dialog.xml | 1 + app/src/main/res/layout/dialog_warn.xml | 34 +- app/src/main/res/layout/fragment_business.xml | 4 +- app/src/main/res/layout/fragment_collect.xml | 10 +- .../main/res/layout/layout_empty_search.xml | 23 +- .../res/layout/list_item_collected_food.xml | 2 +- 35 files changed, 663 insertions(+), 1112 deletions(-) delete mode 100644 app/src/main/java/com/sw/dualscreen/activity/CollectedDataActivity.kt delete mode 100644 app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt rename app/src/main/java/com/sw/dualscreen/dialog/{WarnDialog.kt => RemindDialog.kt} (97%) create mode 100644 app/src/main/java/com/sw/dualscreen/utils/DateTimeUtil.kt rename app/src/main/res/drawable/{bg_blue_ripple.xml => bg_red_ripple.xml} (70%) rename app/src/main/res/drawable/{bg_white_stroke_blue_ripple.xml => bg_white_stroke_red_ripple.xml} (72%) delete mode 100644 app/src/main/res/layout/activity_collected_data.xml delete mode 100644 app/src/main/res/layout/activity_food_collection.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 742ec1d..7bfcfe6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -114,6 +114,7 @@ dependencies { val objectboxVersion = "5.0.1" debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion") // releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion") +// implementation("io.objectbox:objectbox-fulltext:5.0.1") implementation(libs.androidx.recyclerview) diff --git a/app/objectbox-models/default.json b/app/objectbox-models/default.json index 6259a14..7dd04ba 100644 --- a/app/objectbox-models/default.json +++ b/app/objectbox-models/default.json @@ -5,7 +5,7 @@ "entities": [ { "id": "1:594331511073099531", - "lastPropertyId": "9:4394472487596452023", + "lastPropertyId": "11:7949771231314213889", "name": "Food", "properties": [ { @@ -45,6 +45,16 @@ "id": "9:4394472487596452023", "name": "otherField", "type": 9 + }, + { + "id": "10:5704890122356491756", + "name": "createTime", + "type": 9 + }, + { + "id": "11:7949771231314213889", + "name": "isDel", + "type": 1 } ], "relations": [] diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4917f68..f9f5f13 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,7 +50,8 @@ + android:exported="true" + android:theme="@style/Theme.AppCompat.Light.NoActionBar"> diff --git a/app/src/main/java/com/sw/dualscreen/activity/CollectedDataActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/CollectedDataActivity.kt deleted file mode 100644 index 1527313..0000000 --- a/app/src/main/java/com/sw/dualscreen/activity/CollectedDataActivity.kt +++ /dev/null @@ -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() { -// private val viewModel by viewModels() -// override fun getViewModel(): BaseViewModel { -// return viewModel -// } -// -// override fun inflateViewBinding(): ActivityCollectedDataBinding { -// return ActivityCollectedDataBinding.inflate(layoutInflater) -// } -// -// private val list: MutableList = 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? = 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() -// } -//} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt index 09b33ac..1c07a3a 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt @@ -8,17 +8,21 @@ import com.sw.dualscreen.GlobalData import com.sw.dualscreen.R import com.sw.dualscreen.adapter.CollectedFoodNewAdapter import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding -import com.sw.dualscreen.dialog.WarnDialog +import com.sw.dualscreen.dialog.RemindDialog import com.sw.dualscreen.ext.addOnActionSearchListener import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.hideKeyboard import com.sw.dualscreen.ext.visible +import com.sw.dualscreen.model.response.ResetBoxEvent import com.sw.dualscreen.objbox.CollectedFoodInfo import com.sw.dualscreen.objbox.Food +import com.sw.dualscreen.objbox.ObjectBox import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.plate.utils.ToastUtils import io.objectbox.Box +import io.objectbox.kotlin.boxFor +import org.greenrobot.eventbus.EventBus class CollectedFoodActivity : BaseActivity() { @@ -47,6 +51,7 @@ class CollectedFoodActivity : BaseActivity() { override fun initialize() { super.initialize() + box = ObjectBox.boxStore.boxFor(Food::class) //binding.root.setOnClickListener { it.hideKeyboard() } binding.rvFoodList.let { it.layoutManager = LinearLayoutManager(this) @@ -99,7 +104,7 @@ class CollectedFoodActivity : BaseActivity() { } - private var box: Box? = null + private lateinit var box: Box @SuppressLint("NotifyDataSetChanged") private fun getCollectGoods(searchName: String? = null) { @@ -181,7 +186,7 @@ class CollectedFoodActivity : BaseActivity() { } private fun loadDeleteDialog(position: Int) { - WarnDialog( + RemindDialog( context = context, content = "请确认是否删除菜品:${list[position].foodName}?", confirmBlock = { @@ -195,8 +200,16 @@ class CollectedFoodActivity : BaseActivity() { viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess -> if (deleteSuccess) { Thread { - val filterIdList = box?.all?.filter { it.foodName == food.foodName }?.map { it.id } - box?.removeByIds(filterIdList) + val filterFoodList = box.all.filter { it.foodName == food.foodName } + filterFoodList.forEach { it.isDel = true } + box.put(filterFoodList) +// val filterIdList = filterFoodList.map { it.id } +////// ObjectBox.boxStore.runInTx { +//// box.removeByIds(filterIdList) +////// EventBus.getDefault().post(ResetBoxEvent()) +////// } + + runOnUiThread { hideWaitingDialog() adapter.removeAt(position) diff --git a/app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt deleted file mode 100644 index be4bcc0..0000000 --- a/app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt +++ /dev/null @@ -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() { -// companion object { -// val MAX_COUNT = 100 -// } -// private val viewModel by viewModels() -// private var selectedFoodId: String? = "" -// private var selectedFoodName: String? = "" -// -// private var box: Box? = null -// private val foodCollectionList: MutableList = mutableListOf() -// -//// private lateinit var cameraHelper: CameraHelper -// -// private lateinit var foodAdapter: GenericItemAdapter -// private val foodList = mutableListOf() // 适配器内部维护的数据列表 -// 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 { -// 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 -// } -// -//} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/InitActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/InitActivity.kt index ef16bc9..2a1c9df 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/InitActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/InitActivity.kt @@ -51,7 +51,7 @@ class InitActivity : BaseActivity() { } } } - 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) { val vectorList = list.map { vt -> val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")?.map { it.toFloatOrNull()?:0.0f }?.toFloatArray() @@ -63,6 +63,7 @@ class InitActivity : BaseActivity() { foodVector = foodVector ) } + val box = ObjectBox.boxStore.boxFor(Food::class) box.put(vectorList) } diff --git a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt index d32a596..7b2fbbc 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt @@ -25,8 +25,10 @@ import androidx.recyclerview.widget.GridLayoutManager import com.google.common.util.concurrent.ListenableFuture import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalKey +import com.sw.dualscreen.activity.fragment.CollectFragment import com.sw.dualscreen.adapter.SearchFoodAdapter import com.sw.dualscreen.databinding.ActivityMainBinding +import com.sw.dualscreen.dialog.RemindDialog import com.sw.dualscreen.ext.dp import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.load @@ -36,12 +38,16 @@ import com.sw.dualscreen.model.response.ClickBackEvent import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodOrder import com.sw.dualscreen.model.response.PaySuccessEvent +import com.sw.dualscreen.model.response.ResetBoxEvent import com.sw.dualscreen.model.response.UserFaceModel +import com.sw.dualscreen.objbox.Food import com.sw.dualscreen.objbox.FoodModule import com.sw.dualscreen.objbox.FoodModule.IdNameScore +import com.sw.dualscreen.objbox.ObjectBox import com.sw.dualscreen.presentation.MainScreenPresentation import com.sw.dualscreen.sdk.SensorScaleUtils import com.sw.dualscreen.socket.TcpClient +import com.sw.dualscreen.utils.ActivityManager import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.GsonUtils @@ -58,7 +64,12 @@ import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.facedb.FaceDatabase import com.sw.plate.utils.arcface.facedb.entity.FaceEntity import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel +import io.objectbox.Box +import io.objectbox.kotlin.boxFor import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.json.JSONObject @@ -69,6 +80,7 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.Locale import java.util.concurrent.Executors +import kotlin.collections.get import kotlin.math.roundToInt /** @@ -135,6 +147,7 @@ class MainActivity : BaseActivity() { override fun initialize() { super.initialize() + box = ObjectBox.boxStore.boxFor(Food::class) isPageVisible = true addBackEventListener() //FoodModule.init(this) @@ -262,7 +275,7 @@ class MainActivity : BaseActivity() { binding.previewView.visibility = View.VISIBLE binding.ivImg.visibility = View.GONE resumeAnalysis() - viewModel.cleanIdentifiedFoodInfoList() +// viewModel.cleanIdentifiedFoodInfoList() return } pauseAnalysis() @@ -318,6 +331,9 @@ class MainActivity : BaseActivity() { SensorScaleUtils.addWeightListener { weight -> Timber.d("registerDataChange weight = $weight") if (isPageVisible.not() || isStartRecognize.not()) { + if (ActivityManager.currentActivity() is MainActivity) { + isPageVisible = true + } return@addWeightListener } val isWeightChange = weight - lastWeight > 0.05 @@ -328,8 +344,10 @@ class MainActivity : BaseActivity() { private fun recognizeByWeight(weight: Double, isWeightChange: Boolean, block: () -> Unit = {}) { presentation?.updateWeight(weight) - if (weight <= 0.005) {//余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1 + if (weight <= 0.005) { + ////余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1 isRecognitionFood = true + return } if (isWeightChange && isRecognitionFood) { // 大于50g @@ -368,7 +386,6 @@ class MainActivity : BaseActivity() { } failCount++ hideWaitingDialog() -// ToastUtils.showToast("拍照异常,请手动放置后重新识别") shutdownCamera() setupCamera() @@ -389,39 +406,42 @@ class MainActivity : BaseActivity() { val queryData = GsonUtils.toJson(list) val recData = GsonUtils.toJson(scoreList) Timber.d("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData") + if (list.isEmpty()) { + binding.tvToSearch.let { + it.text = "未查询到,手动搜索" + it.visible() + } + return + } list.forEach { foodInfo -> val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName } val score = scoreItem?.score ?: 0.0 foodInfo.score = ((1 - score) * 10000).roundToInt() } - val list2 = try { - val orderList = scoreList.map { it.name.trim() } - list.sortedBy { orderList.indexOf(it.foodName) } - } catch (e: Exception) { - e.printStackTrace() - list - } + //接口已处理排序 +// val list2 = try { +// val orderList = scoreList.map { it.name.trim() } +// list.sortedBy { orderList.indexOf(it.foodName) } +// } catch (e: Exception) { +// e.printStackTrace() +// list +// } searchFoodList.clear() - searchFoodList.addAll(list2) + searchFoodList.addAll(list) adapter.notifyDataSetChanged() - //adapter.updateData(list) - if (list2.isNotEmpty()) { - checkedItem = list2[0] - checkedItem!!.photoUri = lastPhotoUri - checkedItem!!.isChecked = true - adapter.notifyItemChanged(0) - updateCurrentFood(checkedItem) - lastPhotoUri = null - LightManager.closeRedLight() - binding.flPay.visible() - binding.tvToSearch.let { - it.text = "以上都不是,手动搜索" - it.visible() - } - } else { - binding.flPay.gone() - binding.tvToSearch.gone() + checkedItem = list[0].also { + it.photoUri = lastPhotoUri + it.isChecked = true + } + adapter.notifyItemChanged(0) + updateCurrentFood(checkedItem) +// lastPhotoUri = null + LightManager.closeRedLight() + + binding.tvToSearch.let { + it.text = "以上都不是,手动搜索" + it.visible() } } @@ -653,14 +673,14 @@ class MainActivity : BaseActivity() { binding.tvTitleTime.text = dateTime } - private var isPageVisible = true + public var isPageVisible = true override fun onResume() { super.onResume() isPageVisible = true // 启动定时器 handler.postDelayed(timeoutRunnable, TIME_OUT) - +// ToastUtils.showToast("isPageVisible=$isPageVisible") if (isFirstOpen) { //首次 setupSecondaryDisplay() @@ -778,6 +798,11 @@ class MainActivity : BaseActivity() { } foodOrderId = orderId block() + + if (checkedItem!!.isFromSearch == true) { + //当前菜名为手动搜索选择,非识别结果,保存向量数据 + saveFoodVector(checkedItem!!) + } } } } @@ -886,6 +911,111 @@ class MainActivity : BaseActivity() { } } + /** + * 将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() + 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 + + private fun uri2File(uri: Uri, block: (File?, FloatArray?) -> Unit) { + ImageUtil.uriToBitmap(this, uri)?.let { bitmap -> + val imageVector = try { + FoodModule.bitmap2FloatArray(bitmap) + } catch (e: Exception) { + e.printStackTrace() + return@let + } + val imageFile = BitmapSaver.saveToAppFilesDir( + bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" + ) + Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${imageFile?.absolutePath}") + block(imageFile, imageVector) + if (bitmap.isRecycled.not()) { + bitmap.recycle() + } + } + } + override fun onDestroy() { //SensorScaleUtils.closeScale() faceTaskJob?.cancel() @@ -1091,13 +1221,18 @@ class MainActivity : BaseActivity() { hideWaitingDialog() Timber.d("registerDataChange photoUri 识别数据名称:$foodName") + binding.flPay.run { + val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) + //0-计费,1-不计费 + if (chargeMode == 0) visible() else gone() + } if (TextUtils.isEmpty(foodName)) { LightManager.closeGreenLight() LightManager.closeRedLight() //binding.layoutRescan.visibility = View.VISIBLE - binding.flPay.visible() + binding.tvToSearch.let { - it.text = "未识别到,去手动搜索" + it.text = "未识别到,手动搜索" it.visible() } } else { @@ -1117,4 +1252,10 @@ class MainActivity : BaseActivity() { } } +// @Subscribe(threadMode = ThreadMode.MAIN) +// public fun onResetBox(event: ResetBoxEvent) { +// ObjectBox.boxStore.close() +// ObjectBox.init(this) +// } + } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt index c49432b..5e9b8cd 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt @@ -36,6 +36,7 @@ import com.sw.plate.utils.ToastUtils import io.objectbox.Box import io.objectbox.kotlin.boxFor import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import timber.log.Timber @@ -51,7 +52,12 @@ class CollectFragment : BaseFragment() { private var selectedFoodId: String? = "" private var selectedFoodName: String? = "" - private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) } + private var box: Box?=null + fun initBox() { + if (box == null) { + box = ObjectBox.boxStore.boxFor(Food::class) + } + } private val foodCollectionList = mutableListOf().apply { repeat(MAX_COUNT) { add(FoodCollectionBean(isShowCamera = true)) @@ -192,7 +198,7 @@ class CollectFragment : BaseFragment() { } binding.btnFoodSearch.setOnClickListener { - searchInfo() + searchFood() } binding.btnSave.setOnClickListener { @@ -209,7 +215,7 @@ class CollectFragment : BaseFragment() { binding.editFoodName.setOnEditorActionListener { v, actionId, event -> if (actionId == EditorInfo.IME_ACTION_SEARCH) { - searchInfo() + searchFood() val imm = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(v.windowToken, 0) @@ -236,15 +242,14 @@ class CollectFragment : BaseFragment() { it.adapter = searchFoodAdapter } - searchInfo() + searchFood() } - @SuppressLint("NotifyDataSetChanged") private fun upload() { lifecycleScope.launch { val totalFileCount = foodCollectionList.count { it.imageFile != null } - settingActivity?.showWaitingDialog("图片上传中0/$totalFileCount") + settingActivity?.showWaitingDialog2("图片上传中0/$totalFileCount") val params = HashMap() //params["placeId"] = restId.toRequestBody() params["foodId"] = checkedItem!!.foodId.toRequestBody() @@ -256,46 +261,58 @@ class CollectFragment : BaseFragment() { uploadImage = { batch -> val files = batch.map { it.imageFile } val foodVectorList = - batch.filter { it.imageVector != null }.map { it.imageVector!!.joinToString(separator = ",", prefix = "[", postfix = "]")} + batch.filter { it.imageVector != null }.map { + it.imageVector!!.joinToString( + separator = ",", + prefix = "[", + postfix = "]" + ) + } val foodVectorJson = foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]") Timber.tag(TAG).d("json=$foodVectorJson") params["foodVector"] = foodVectorJson.toRequestBody() settingActivity?.viewModel?.uploadCollectFoodPics(files, params) }, - onProgress = { count, batch, idList-> - activity?.runOnUiThread { - settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount") - //batch.forEach { - // it.uploadSuccess = true - //} - Thread { - val foodList = batch.mapIndexed { index, it -> - Food( - collectId = if (index < idList.size) idList[index] else null, - foodId = checkedItem!!.foodId, - foodName = checkedItem!!.foodName, - foodVector = it.imageVector, - version = GlobalData.foodModelVersion - ) - } - box.put(foodList) - activity?.runOnUiThread { - batch.forEach { it.uploadSuccess = true } - collectionAdapter.notifyDataSetChanged() - } - }.start() + onProgress = { count, batch, idList -> + //batch.forEach { + // it.uploadSuccess = true + //} + runBlocking { + activity?.runOnUiThread { + settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount") + } + val foodList = batch.mapIndexed { index, it -> + Food( + collectId = if (index < idList.size) idList[index] else null, + foodId = checkedItem!!.foodId, + foodName = checkedItem!!.foodName, + foodVector = it.imageVector, + version = GlobalData.foodModelVersion + ) + } + initBox() + box?.put(foodList) + activity?.runOnUiThread { + batch.forEach { it.isFinish = true } + collectionAdapter.notifyDataSetChanged() + } } }, onError = { - settingActivity?.hideWaitingDialog() activity?.runOnUiThread { - ToastUtils.showToast("上传失败,请稍后重试") + binding.root.postDelayed({ + settingActivity?.hideWaitingDialog() + ToastUtils.showToast("上传失败,请稍后重试") + }, 1000) } }, onComplete = { //vectorThread() - settingActivity?.hideWaitingDialog() + binding.root.postDelayed({ + settingActivity?.hideWaitingDialog() + ToastUtils.showToast("上传成功") + }, 1000) } ).processUploads() } @@ -320,59 +337,59 @@ class CollectFragment : BaseFragment() { // } } - private fun vectorThread() { - settingActivity?.showWaitingDialog("加载中……") - Thread { - foodCollectionList -// .filter { it.bitmap != null } -// .filter { it.imageVector != null } - .forEachIndexed { index, it -> - if (it.imageVector == null) { - return@forEachIndexed - } - image2VectorTask(imageVector = it.imageVector!!, index) - } - activity?.runOnUiThread { - binding.root.postDelayed({ - settingActivity?.hideWaitingDialog() - }, 1000) - } - }.start() - } - - private fun image2VectorTask(imageVector: FloatArray?, position: Int) { - if (imageVector == null) return -//// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!) -//// -////// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector) -////// Timber.tag("mzf1").e(base64Str) -//////// viewModel.postImageData( -//////// context, -//////// foodId = selectedFoodId.toString(), -//////// foodName = selectedFoodName.toString(), -//////// foodVector = base64Str, -//////// uri = item.imageUri!! -//////// ) -// box.put( -// Food( -// collectId = null, -// foodId = checkedItem!!.foodId, -// foodName = checkedItem!!.foodName, -// foodVector = imageVector, -// version = "1.0.0" -// ) -// ) -// if (position > -1) { -// foodCollectionList[position].isFinish = true +// private fun vectorThread() { +// settingActivity?.showWaitingDialog("加载中……") +// Thread { +// foodCollectionList +//// .filter { it.bitmap != null } +//// .filter { it.imageVector != null } +// .forEachIndexed { index, it -> +// if (it.imageVector == null) { +// return@forEachIndexed +// } +// image2VectorTask(imageVector = it.imageVector!!, index) +// } // activity?.runOnUiThread { -// collectionAdapter.notifyItemChanged(position) +// binding.root.postDelayed({ +// settingActivity?.hideWaitingDialog() +// }, 1000) // } -// } - } +// }.start() +// } + +// private fun image2VectorTask(imageVector: FloatArray?, position: Int) { +// if (imageVector == null) return +////// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!) +////// +//////// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector) +//////// Timber.tag("mzf1").e(base64Str) +////////// viewModel.postImageData( +////////// context, +////////// foodId = selectedFoodId.toString(), +////////// foodName = selectedFoodName.toString(), +////////// foodVector = base64Str, +////////// uri = item.imageUri!! +////////// ) +//// box.put( +//// Food( +//// collectId = null, +//// foodId = checkedItem!!.foodId, +//// foodName = checkedItem!!.foodName, +//// foodVector = imageVector, +//// version = "1.0.0" +//// ) +//// ) +//// if (position > -1) { +//// foodCollectionList[position].isFinish = true +//// activity?.runOnUiThread { +//// collectionAdapter.notifyItemChanged(position) +//// } +//// } +// } @SuppressLint("NotifyDataSetChanged") - private fun searchInfo() { + private fun searchFood() { debouncer.debounce { settingActivity?.searchByFoodName(binding.editFoodName.text.toString()) { searchFoodList.clear() @@ -399,8 +416,7 @@ class CollectFragment : BaseFragment() { clickIndex = -1 binding.editFoodName.setText("") - searchFoodList.clear() - searchFoodAdapter.notifyDataSetChanged() + searchFood() } override fun onResume() { diff --git a/app/src/main/java/com/sw/dualscreen/dialog/WarnDialog.kt b/app/src/main/java/com/sw/dualscreen/dialog/RemindDialog.kt similarity index 97% rename from app/src/main/java/com/sw/dualscreen/dialog/WarnDialog.kt rename to app/src/main/java/com/sw/dualscreen/dialog/RemindDialog.kt index c70b2d2..f696eb6 100644 --- a/app/src/main/java/com/sw/dualscreen/dialog/WarnDialog.kt +++ b/app/src/main/java/com/sw/dualscreen/dialog/RemindDialog.kt @@ -5,7 +5,7 @@ import android.view.LayoutInflater import android.view.View import com.sw.dualscreen.databinding.DialogWarnBinding -class WarnDialog( +class RemindDialog( context: Context, var content:String, var cancelBlock: () -> Unit = {}, @@ -31,5 +31,4 @@ class WarnDialog( } } - } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt b/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt index 237f0b3..3606b6b 100644 --- a/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt +++ b/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt @@ -144,4 +144,8 @@ data class FoodVector( val foodName: String? = null, val foodVector: String? = null, val version: String? = null +) + +data class ResetBoxEvent( + var num: Int = 0 ) \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt b/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt index 97c0b01..c6646b4 100644 --- a/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt +++ b/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt @@ -12,33 +12,33 @@ import kotlinx.parcelize.Parcelize @Parcelize data class FoodInfo( //菜品id - val foodId:String, + val foodId: String, //菜品名称 - val foodName:String? = null, + val foodName: String? = null, //能量 - val calorie:Double? = null, + val calorie: Double? = null, //蛋白质 - val protein:Double? = null, + val protein: Double? = null, //脂肪 - val fat:Double? = null, + val fat: Double? = null, //碳水化合物 - val carbohydrate:Double? = null, + val carbohydrate: Double? = null, //规格售卖价格(元) val specPrice: Double? = null, //VIP售卖价(元) - val vipPrice:Double? = null, + val vipPrice: Double? = null, //餐品营养id - val foodMaterialId:String? = null, + val foodMaterialId: String? = null, //菜品规格id - val specId:String? = null, + val specId: String? = null, //规格重量(g) - val specWeight:Double? = null, + val specWeight: Double? = null, //主食 - val stapleFood:Double? = null, + val stapleFood: Double? = null, //果蔬 - val fruitsVegetables:Double? = null, + val fruitsVegetables: Double? = null, //肉蛋 - val meatEggs:Double? = null, + val meatEggs: Double? = null, //推荐能量 val recommendCalorie: Double? = null, //图片 @@ -48,6 +48,8 @@ data class FoodInfo( var score: Int = 0, var isChecked: Boolean = false, var photoUri: Uri? = null, + //true-搜索结果数据,false-识别查询数据 + var isFromSearch: Boolean? = null // @SerializedName("foodTypeAndRealIntakeVoList") // val foodTypeAndRealIntakeVoList: List? = listOf(), diff --git a/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt b/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt index 8096f52..2fa7f70 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt @@ -25,7 +25,35 @@ data class FoodCollectionBean( var isShowCamera: Boolean = false, var isFinish:Boolean = false, var uploadSuccess:Boolean = false -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FoodCollectionBean + + if (isShowCamera != other.isShowCamera) return false + if (isFinish != other.isFinish) return false + if (uploadSuccess != other.uploadSuccess) return false + if (imageFile != other.imageFile) return false + if (imageUri != other.imageUri) return false + if (bitmap != other.bitmap) return false + if (!imageVector.contentEquals(other.imageVector)) return false + + return true + } + + override fun hashCode(): Int { + var result = isShowCamera.hashCode() + result = 31 * result + isFinish.hashCode() + result = 31 * result + uploadSuccess.hashCode() + result = 31 * result + (imageFile?.hashCode() ?: 0) + result = 31 * result + (imageUri?.hashCode() ?: 0) + result = 31 * result + (bitmap?.hashCode() ?: 0) + result = 31 * result + (imageVector?.contentHashCode() ?: 0) + return result + } +} data class CollectedFoodInfo( // var id: String? = null, diff --git a/app/src/main/java/com/sw/dualscreen/objbox/Food.kt b/app/src/main/java/com/sw/dualscreen/objbox/Food.kt index d1b53db..410dcdf 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/Food.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/Food.kt @@ -1,9 +1,11 @@ package com.sw.dualscreen.objbox +import com.sw.dualscreen.utils.DateTimeUtil import io.objectbox.annotation.Entity import io.objectbox.annotation.HnswIndex import io.objectbox.annotation.Id import io.objectbox.annotation.VectorDistanceType +import java.time.LocalDateTime @Entity data class Food( @@ -12,6 +14,8 @@ data class Food( var foodId: String? = null, var foodName: String? = null, var version: String? = null, + var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now()), + var isDel: Boolean = false, var otherField: String? = null, @HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT) var foodVector: FloatArray? = null @@ -23,10 +27,12 @@ data class Food( other as Food if (id != other.id) return false + if (isDel != other.isDel) return false if (collectId != other.collectId) return false if (foodId != other.foodId) return false if (foodName != other.foodName) return false if (version != other.version) return false + if (createTime != other.createTime) return false if (otherField != other.otherField) return false if (!foodVector.contentEquals(other.foodVector)) return false @@ -35,14 +41,15 @@ data class Food( override fun hashCode(): Int { var result = id.hashCode() + result = 31 * result + isDel.hashCode() result = 31 * result + (collectId?.hashCode() ?: 0) result = 31 * result + (foodId?.hashCode() ?: 0) result = 31 * result + (foodName?.hashCode() ?: 0) result = 31 * result + (version?.hashCode() ?: 0) + result = 31 * result + createTime.hashCode() result = 31 * result + (otherField?.hashCode() ?: 0) result = 31 * result + (foodVector?.contentHashCode() ?: 0) return result } - } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt index 2cd5eb3..09775e9 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt @@ -1,18 +1,14 @@ package com.sw.dualscreen.objbox +import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.net.Uri -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import com.sw.dualscreen.MyApp -import com.sw.dualscreen.utils.AssetsTool import com.sw.dualscreen.utils.GsonUtils import com.sw.dualscreen.utils.ImageUtil -import com.sw.plate.App import io.objectbox.Box import io.objectbox.kotlin.boxFor -import io.objectbox.query.Query import org.pytorch.IValue import org.pytorch.Module import org.pytorch.torchvision.TensorImageUtils @@ -23,22 +19,27 @@ import java.io.IOException import java.io.InputStream object FoodModule { - + private const val TAG = "FoodModule" private const val THRESHOLD = 0.8 - private var module_mobile: Module? = null + + // private const val THRESHOLD = 0.0 + private lateinit var module: Module private lateinit var box: Box - private lateinit var embeddingsList: List> - private lateinit var labelsList: IntArray - private lateinit var classInfo: FoodClassInfo + + // private lateinit var embeddingsList: List> +// private lateinit var labelsList: IntArray +// private lateinit var classInfo: FoodClassInfo val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f) val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f) - val DEFAULT_FOOD_INDEX = -1 +// val DEFAULT_FOOD_INDEX = -1 + @SuppressLint("SuspiciousIndentation") fun init(context: Context, block: () -> Unit = {}) { Thread { - module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt")) + module = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt")) box = ObjectBox.boxStore.boxFor(Food::class) //初始化默认重新拉取数据,先清空本地数据 +// ObjectBox.boxStore.runInTx { if (box.all.isNotEmpty()) { box.removeAll() } @@ -58,68 +59,79 @@ object FoodModule { } fun bitmap2FloatArray(bitmap: Bitmap): FloatArray { + var tensorStartTime = System.currentTimeMillis() val inputTensor = TensorImageUtils.bitmapToFloat32Tensor( bitmap, NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB ) - if (module_mobile == null) { - init(App.getContext()) - } - val outputTensor = module_mobile!!.forward(IValue.from(inputTensor)).toTensor() + Timber.tag(TAG).d("bitmap2FloatArray-inputTensor耗时:${System.currentTimeMillis() - tensorStartTime}") +// if (module_mobile == null) { +// init(App.getContext()) +// } + tensorStartTime = System.currentTimeMillis() + val outputTensor = module.forward(IValue.from(inputTensor)).toTensor() + Timber.tag(TAG).d("bitmap2FloatArray-toTensor耗时:${System.currentTimeMillis() - tensorStartTime}") return outputTensor.dataAsFloatArray } - fun queryFood(uri: Uri, queryCount: Int = 15): List? { - return uri2FloatArray(uri)?.let { - queryFood(it, queryCount) - } - } +// fun queryFood(uri: Uri, queryCount: Int = 15): List? { +// return uri2FloatArray(uri)?.let { +// queryFood(it, queryCount) +// } +// } /** * 返回识别物品名称列表 */ - fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List { - val floatArray = bitmap2FloatArray(bitmap) - return queryFood(floatArray, queryCount) - } +// fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List { +// val floatArray = bitmap2FloatArray(bitmap) +// return queryFood(floatArray, queryCount) +// } /** * 返回识别物品IdNameScore对象列表 */ - fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List { - val floatArray = bitmap2FloatArray(bitmap) - return queryFoodNameScore(floatArray, queryCount) - } +// fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List { +// val floatArray = bitmap2FloatArray(bitmap) +// return queryFoodNameScore(floatArray, queryCount) +// } fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List { val startTime = System.currentTimeMillis() - val query: Query = - box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() + Timber.tag(TAG).d("queryFoodNameScore-已采集向量总数:${box.all.filter { it.isDel.not() }.size}") + box.store.runInTx { } + val query = box.query() + .equal(Food_.isDel, false) + .and() + .nearestNeighbors(Food_.foodVector, floatArray, queryCount) + .build() //查询比较分数 // val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" } - val idScoreList = query.findIdsWithScores() +// val idScoreList = query.findIdsWithScores() + val objScoreList = query.findWithScores() + Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}") val nameScoreList = mutableListOf() - idScoreList.forEach { + objScoreList.forEach { + val food = it.get() nameScoreList.add( IdNameScore( - id = it.id, - name = box.get(it.id).foodName ?: "", + id = food.id, + name = food.foodName ?: "", score = it.score ) ) } val nameScoreData = GsonUtils.toJson(nameScoreList) - Timber.tag("FoodModule") - .d("registerDataChange,queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData") + Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData") + query.close() return nameScoreList } fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List { val startTime = System.currentTimeMillis() val floatArray = bitmap2FloatArray(bitmap) - Timber.tag("FoodModule") - .d("registerDataChange,bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}") + Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}") val nameScoreList = queryFoodNameScore(floatArray, queryCount) if (nameScoreList.isEmpty()) { return emptyList() @@ -147,35 +159,40 @@ object FoodModule { return sortedScoreList } - fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List { - val query: Query = - box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() - //查询比较分数 -// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" } - val map = mutableMapOf() - query.findIdsWithScores().forEach { - val food = box.get(it.id) - Timber.d("${food.foodName}|${food.foodId}|${food.collectId}|${food.version}|${food.otherField}|${it.score}") - if (1 - it.score >= THRESHOLD) { - //FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score) - food.foodName?.let { key -> - val count = map[key] ?: 0 - map.put(key, count + 1) - } - } - } - val list = map.entries.sortedByDescending { it.value }.map { it.key } - return list - +// fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List { +// val query = box.query() +// .equal(Food_.isDel, false) +// .and() +// .nearestNeighbors(Food_.foodVector, floatArray, queryCount) +// .build() +//// val query: Query = +//// box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() +// //查询比较分数 +//// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" } // val map = mutableMapOf() -// val nameScoreList = queryFoodNameScore(floatArray, queryCount) -// nameScoreList.filter { it.score < 0.05 }.forEach { -// val count = map[it.name] ?: 0 -// map[it.name] = count + 1 +// query.findIdsWithScores().forEach { +// val food = box.get(it.id) +// Timber.d("${food.foodName}|${food.foodId}|${food.collectId}|${food.version}|${food.otherField}|${it.score}") +// if (1 - it.score >= THRESHOLD) { +// //FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score) +// food.foodName?.let { key -> +// val count = map[key] ?: 0 +// map.put(key, count + 1) +// } +// } // } // val list = map.entries.sortedByDescending { it.value }.map { it.key } // return list - } +// +//// val map = mutableMapOf() +//// val nameScoreList = queryFoodNameScore(floatArray, queryCount) +//// nameScoreList.filter { it.score < 0.05 }.forEach { +//// val count = map[it.name] ?: 0 +//// map[it.name] = count + 1 +//// } +//// val list = map.entries.sortedByDescending { it.value }.map { it.key } +//// return list +// } data class IdNameScore( val id: Long, @@ -183,36 +200,36 @@ object FoodModule { val score: Double ) - fun initDefFoodData(context: Context, action: () -> Unit = {}) { - //val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX } - //if (count > 0) { - // return - //} - 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>>() {}.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() - } +// fun initDefFoodData(context: Context, action: () -> Unit = {}) { +// //val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX } +// //if (count > 0) { +// // return +// //} +// val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json") +// val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json") +// val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json") +// +// embeddingsList = +// Gson().fromJson(embeddingsJson, object : TypeToken>>() {}.type) +// labelsList = Gson().fromJson(labelsJson, IntArray::class.java) +// classInfo = +// Gson().fromJson(classInfoJson, FoodClassInfo::class.java) +// +// val foodMap = classInfo.idx_to_class +// embeddingsList.forEachIndexed { index, floatList -> +// val classIdx = labelsList[index] +// val foodName = foodMap["$classIdx"] +// val array = floatList.toFloatArray() +// box.put( +// Food( +// foodName = foodName, +// foodVector = array, +// //foodIdx = DEFAULT_FOOD_INDEX +// ) +// ) +// } +// action() +// } /** @@ -225,7 +242,7 @@ object FoodModule { */ fun copyAssetToCache(context: Context, fileName: String): String? { // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦 - val cacheDir = context.getCacheDir() + val cacheDir = context.cacheDir if (!cacheDir.exists()) { cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建 } @@ -239,7 +256,7 @@ object FoodModule { // 创建文件,如果创建成功,就返回true val res = outPath.createNewFile() if (res) { - `is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节 + `is` = context.assets.open(fileName) // 拿到main/assets目录的输入流,用于读取字节 fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath val buf = ByteArray(`is`.available()) // 缓存区 var byteCount: Int @@ -248,7 +265,7 @@ object FoodModule { while ((`is`.read(buf).also { byteCount = it }) != -1) { fos.write(buf, 0, byteCount) } - return outPath.getAbsolutePath() + return outPath.absolutePath } } catch (e: IOException) { e.printStackTrace() diff --git a/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt b/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt index 127f817..6f82890 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt @@ -18,10 +18,12 @@ package com.sw.dualscreen.objbox import android.content.Context import android.util.Log +import io.objectbox.Box import io.objectbox.BoxStore import io.objectbox.BoxStoreBuilder import io.objectbox.android.Admin import io.objectbox.android.ObjectBoxLiveData +import io.objectbox.config.DebugFlags import io.objectbox.exception.DbException import io.objectbox.exception.FileCorruptException import io.objectbox.sync.Sync @@ -64,6 +66,7 @@ object ObjectBox { boxStore = try { MyObjectBox.builder() .androidContext(context.applicationContext) + .debugFlags(DebugFlags.LOG_QUERY_PARAMETERS) .build() } catch (e: DbException) { if (e.javaClass == DbException::class.java || e is FileCorruptException) { @@ -111,4 +114,42 @@ object ObjectBox { return true } +// /** +// * 安全获取 Food 实体 foodVector 字段的向量索引(推荐写法) +// */ +// fun getFoodVectorIndex(box: Box): VectorIndex? { +// return try { +// // 核心修复:使用自动生成的 Food_.foodVector 而非字符串,避免拼写错误 +// val foodVectorProperty = Food_.foodVector +// // 获取向量索引(适配 ObjectBox 3.5+ 所有版本) +// foodVectorProperty.vectorIndex +// } catch (e: Exception) { +// // 容错处理:打印错误信息,避免崩溃 +// e.printStackTrace() +// null +// } +// } +// +// // 刷新索引的调用示例(结合你之前的更新场景) +// fun refreshFoodVectorIndex(box: Box) { +// val vectorIndex = getFoodVectorIndex(box) +// vectorIndex?.run { +// // 同步刷新索引(解决查询异常问题) +// refresh() +// // 等待索引构建完成(超时 5 秒,避免无限等待) +// waitUntilBuilt(5000) +// } +// } +// +// // 更新 isDel 字段后的完整调用流程 +// fun markFoodAsDeleted(box: Box, foodId: Long) { +// val food = box[foodId] +// if (food != null) { +// food.isDel = true +// box.put(food) +// // 更新后刷新索引 +// refreshFoodVectorIndex(box) +// } +// } + } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/utils/DateTimeUtil.kt b/app/src/main/java/com/sw/dualscreen/utils/DateTimeUtil.kt new file mode 100644 index 0000000..8dec5b2 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/utils/DateTimeUtil.kt @@ -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秒")}") + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt b/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt index f8a0c1d..dba726d 100644 --- a/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt +++ b/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt @@ -3,8 +3,6 @@ package com.sw.dualscreen.view import android.annotation.SuppressLint import android.content.DialogInterface import android.graphics.Color -import android.graphics.Typeface -import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -13,18 +11,15 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.sw.dualscreen.R -import com.sw.dualscreen.adapter.GenericItemAdapter -import com.sw.dualscreen.adapter.GridSpacingItemDecoration import com.sw.dualscreen.adapter.SearchFoodAdapter -import com.sw.dualscreen.adapter.dpToPx import com.sw.dualscreen.databinding.BottomSheetDialogBinding -import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.KeyboardUtils import com.sw.dualscreen.viewmodel.UserViewModel import kotlinx.coroutines.launch import timber.log.Timber +import androidx.core.graphics.drawable.toDrawable /** * 底部搜索结果弹窗 @@ -43,7 +38,7 @@ class CustomBottomSheetDialog( searchFoodList.forEachIndexed { index, info -> searchFoodList[index].isChecked = index == position } - val item = searchFoodList[position] + val item = searchFoodList[position].also { it.isFromSearch = true } Timber.d("itemClick ${item.foodName}, position = $position") itemClickCallback(item) checkedItem = item @@ -79,7 +74,7 @@ class CustomBottomSheetDialog( private fun setDialogStyle() { dialog?.window?.let { - it.decorView.background = ColorDrawable(Color.TRANSPARENT) + it.decorView.background = Color.TRANSPARENT.toDrawable() it.decorView.setPadding(0, 0, 0, 0) it.attributes?.apply { width = ViewGroup.LayoutParams.MATCH_PARENT @@ -118,15 +113,17 @@ class CustomBottomSheetDialog( it.adapter = adapter } binding.ivSearch.setOnClickListener { - searchInfo() + searchFood() } KeyboardUtils.setupEditorAction(binding.etSearch) { - searchInfo() + searchFood() } registerDataChange() + + searchFood() } - private fun searchInfo(){ + private fun searchFood(){ debouncer.debounce { viewModel.searchByFoodName(binding.etSearch.text.toString()) { searchFoodList.clear() @@ -138,13 +135,13 @@ class CustomBottomSheetDialog( @SuppressLint("NotifyDataSetChanged") private fun registerDataChange() { - lifecycleScope.launch { - viewModel.searchFoodInfoList.collect { - searchFoodList.clear() - searchFoodList.addAll(it) - adapter.notifyDataSetChanged() - } - } +// lifecycleScope.launch { +// viewModel.searchFoodInfoList.collect { +// searchFoodList.clear() +// searchFoodList.addAll(it) +// adapter.notifyDataSetChanged() +// } +// } } // private fun createAdapter(): GenericItemAdapter { @@ -176,13 +173,13 @@ class CustomBottomSheetDialog( override fun onCancel(dialog: DialogInterface) { Timber.d("onCancel") - viewModel.cleanSearchFoodInfoList() +// viewModel.cleanSearchFoodInfoList() super.onCancel(dialog) } override fun dismiss() { Timber.d("dismiss") - viewModel.cleanSearchFoodInfoList() +// viewModel.cleanSearchFoodInfoList() super.dismiss() } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt index 4ab49f7..6c64050 100644 --- a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt @@ -40,12 +40,12 @@ class UserViewModel : BaseViewModel() { private val faceApi: FaceApi = FaceApi() // 识别出的列表 - private val _identifiedFoodInfoList = MutableStateFlow>(emptyList()) - val identifiedFoodInfoList: StateFlow> = _identifiedFoodInfoList +// private val _identifiedFoodInfoList = MutableStateFlow>(emptyList()) +// val identifiedFoodInfoList: StateFlow> = _identifiedFoodInfoList // 搜索出的食物列表 - private val _searchFoodInfoList = MutableStateFlow>(emptyList()) - val searchFoodInfoList: StateFlow> = _searchFoodInfoList +// private val _searchFoodInfoList = MutableStateFlow>(emptyList()) +// val searchFoodInfoList: StateFlow> = _searchFoodInfoList // 就餐数据 // private val _nutritionData = MutableStateFlow(null) @@ -225,6 +225,8 @@ class UserViewModel : BaseViewModel() { val list: List = response.data ?: emptyList() block(list) } + } else { + block(emptyList()) } } } @@ -278,10 +280,10 @@ class UserViewModel : BaseViewModel() { // } // } - fun cleanIdentifiedFoodInfoList() { - Timber.tag(TAG).d("cleanIdentifiedFoodInfoList") - _identifiedFoodInfoList.value = emptyList() - } +// fun cleanIdentifiedFoodInfoList() { +// Timber.tag(TAG).d("cleanIdentifiedFoodInfoList") +// _identifiedFoodInfoList.value = emptyList() +// } /** * 搜索食物 @@ -293,15 +295,15 @@ class UserViewModel : BaseViewModel() { if (parseResponse(response)) { val list = response.data ?: emptyList() action(list) - _identifiedFoodInfoList.value = response.data ?: emptyList() +// _identifiedFoodInfoList.value = response.data ?: emptyList() } } } - fun cleanSearchFoodInfoList() { - Timber.tag(TAG).d("cleanSearchFoodInfoList") - _searchFoodInfoList.value = emptyList() - } +// fun cleanSearchFoodInfoList() { +// Timber.tag(TAG).d("cleanSearchFoodInfoList") +// _searchFoodInfoList.value = emptyList() +// } // /** // * 获取用户就餐数据 @@ -383,7 +385,7 @@ class UserViewModel : BaseViewModel() { val response = repository.getFoodInfo(foodName = foodName) if (parseResponse(response)) { val list = response.data ?: emptyList() - _identifiedFoodInfoList.value = list +// _identifiedFoodInfoList.value = list action(list) } else { action(emptyList()) diff --git a/app/src/main/res/drawable-xxhdpi/ic_empty_view.png b/app/src/main/res/drawable-xxhdpi/ic_empty_view.png index 30b581dbbab7f2d6dee1fcc80c82ee841c3758b2..d0e860f94592b870884a765ff9f56ef03b7f1675 100644 GIT binary patch literal 16459 zcmZ|0bySq!_Xf%g0}Ps3=0JX<)O5cmx!=PY>ebFh3I(GcW&RfRQT*R26)|hBdc9nGsBcJt7!+@t~W;t~i z-=;^86en%hmTsGRsg24hPN>@46gDmt{K{qycmj=u8dZAI8CUXtf}&4jT+d)|B+VJ; zcHrI_NTJ~H1bz}yj;oyU!6h7-AoL8fB1Zu);E}5=R_ywqHYo?0Ph)n@f`XczK343P zcazRa7qok77rJMJim?Mvii6`iGVrxXyg-|r=g@s%P9 z&b0SN7K7bjatm$o>0lN+G!OwV!W@2>G|<&-_=(&kE(kS3%MVw><>vB~lK`_JL3n6| zxE1lt5B>)_P*K!Af4*2sxXk(Rv6~1m7WOy%;g83Im-`h-#V~w03Pe;CZZW}bvw*Cp zV8<|Vl>?qJM81z5GP2JwZ2YkljHd|x>hu+J3;WMUTfTAHb{>4NEs~Nro2tV7^s@uA z>^we6*n0+OX9S9($k_bHqLmY+Pp<@Fwx3=d^!)lzTx`VQ_C(R4*=5bS3ByMdKUl)} z$w9fh)l0d6xCsITYDv5fr&fhZg5^XXn`;DK{Q#ej_y`?MU#5Ghdc;T5G5?#^*N&21P8pvOnx zM!txKu~ebZ(6+M5kHA>#m7$h>M)tGCmI7YloDc|JFu9N7^chkMzl#4OZO{NE&r%du za24;2<)XB}rQjRwC+@)3dJzmK99=9$UTRVH9i^kd%Fe@4nM7iVqWxd@;SoS2p%W86Mw_CG2dNj%BkZWg%m0WYEBFp$GQqG1rz z8G&T4EjUA+PfP_*yA2gm(k;A!B#h$bC-kY%l10`Ob zrDx_wz=Nlsd)EfWj7Q)PAuH0EuFTtX#ulZ*^JAa+<<$wzy$AsbQtE=%FDYT-KD&>Uw$UQD#|p6idHgxJ)I%nKoTg`R?Q*%$brO zu2KHZ*Kopz#Px1?7|+`DYce^9FMJ(>j!}F2nfHuwh}Ss6&x-&2ltFc=UkTWG95shn zn{Z>RXiWn)J$lU`7v~pVh1W6B1Q0lq1u&N+!$_wi7#gZ>+~Ap~l+ti6QH7#73XUry zAH_`sZrSIVDpp&^N5LlnBqZ4GIw9noPy}2JGEi~~>?0A>p& zHi&1btV%4&8Ss`CF-Nte-h7!F_p!VUraT$MJ`hKmXq5QSHTvVf2y=LF>(UltR+~fR z5-@OF`9vu$7nlM2bjXb2C!*Lc=V(xRm!8plul-}V=k#Vv{h?RB&fgq zM695ybIFsrg|l0xeYz@Qb!&awvH!VYE7BBrZ3#2ca154h8M3viA5vuDif%=*L+Kw5 zWP_e(Mn$|f%@YTL3uo*RFZ^y5BqbecW$}$LfW#05KO~-5jAjwt?~L#?wz08ED|#_L z2gv3uJKS+SV|gc{y)%Xu+VcQM$fE$E;7F~{02PacZ)*-pB@d~N$R1zJzlb0AkM{sv zW$&r`7xKE!rMDsel}(kEockeJcxWIzJR1E7HHU$UuLp#`PG$rJpW_(m`bXgb%a?u~ z{&Lj|@byP`M}o%NqXDQ?r-v;Yn3=x#bovom4!^N9Iv9zH8Yh9|R#^68?He@;h7Ju6 zYh^DGcJ^d*llKos)y@1+psueo9e*cdvwg?X&lj@kk8hVZ@WrzSY;O9I(8a*V1r4JR zk(%E#mzVy325*Xh7`;rZ^m;NbV%apH@MPVxW*_=qzDEs6Q*~Ho?~J3h4Wf1(V3MqQ z6-Kuf*QEE!;R|Gd-7>;8ouEvkG8KF#5*(f-^qpVYt(B{q=jOZaAtJfe4%&

~~n%dgA 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 a8ye6=N1Q?32#6hc0z#TxH?BAIO#VOZL1AbB diff --git a/app/src/main/res/drawable-xxhdpi/ic_tip_warn.png b/app/src/main/res/drawable-xxhdpi/ic_tip_warn.png index 385d7e5bd20619ee99e6dbf73ecafde7877b9709..a6dfec0e4339be9a82ea618765641085bfa66419 100644 GIT binary patch literal 9564 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 H6ZQW9K74Xj literal 15695 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 KtCO+#`2PUoEPRmw diff --git a/app/src/main/res/drawable/bg_btn_save.xml b/app/src/main/res/drawable/bg_btn_save.xml index 83c1497..05736a9 100644 --- a/app/src/main/res/drawable/bg_btn_save.xml +++ b/app/src/main/res/drawable/bg_btn_save.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_blue_ripple.xml b/app/src/main/res/drawable/bg_red_ripple.xml similarity index 70% rename from app/src/main/res/drawable/bg_blue_ripple.xml rename to app/src/main/res/drawable/bg_red_ripple.xml index 7ea674f..e640de6 100644 --- a/app/src/main/res/drawable/bg_blue_ripple.xml +++ b/app/src/main/res/drawable/bg_red_ripple.xml @@ -3,8 +3,8 @@ android:color="#30000000"> - - + + diff --git a/app/src/main/res/drawable/bg_white_stroke_blue_ripple.xml b/app/src/main/res/drawable/bg_white_stroke_red_ripple.xml similarity index 72% rename from app/src/main/res/drawable/bg_white_stroke_blue_ripple.xml rename to app/src/main/res/drawable/bg_white_stroke_red_ripple.xml index df460db..e50fd2c 100644 --- a/app/src/main/res/drawable/bg_white_stroke_blue_ripple.xml +++ b/app/src/main/res/drawable/bg_white_stroke_red_ripple.xml @@ -4,8 +4,8 @@ - - + + diff --git a/app/src/main/res/drawable/line_shape_horizontal.xml b/app/src/main/res/drawable/line_shape_horizontal.xml index 37a5000..de4ba9d 100644 --- a/app/src/main/res/drawable/line_shape_horizontal.xml +++ b/app/src/main/res/drawable/line_shape_horizontal.xml @@ -3,14 +3,14 @@ - + - + diff --git a/app/src/main/res/layout/activity_collected_data.xml b/app/src/main/res/layout/activity_collected_data.xml deleted file mode 100644 index f324dcf..0000000 --- a/app/src/main/res/layout/activity_collected_data.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_food_collection.xml b/app/src/main/res/layout/activity_food_collection.xml deleted file mode 100644 index 3d27766..0000000 --- a/app/src/main/res/layout/activity_food_collection.xml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -