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 30b581d..d0e860f 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_empty_view.png and b/app/src/main/res/drawable-xxhdpi/ic_empty_view.png differ 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 385d7e5..a6dfec0 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_tip_warn.png and b/app/src/main/res/drawable-xxhdpi/ic_tip_warn.png differ 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -