From af120eabd01444846583459ca1f95f0274c47ca0 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Sat, 31 Jan 2026 17:49:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E9=80=A0objectbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/sw/inbound/MainActivity.kt | 9 +- .../activity/FoodCollectionActivity.kt | 29 ++++--- .../sw/inbound/activity/GoodsListActivity.kt | 4 +- .../sw/inbound/dialog/CollectSearchDialog.kt | 83 ++++++++++--------- .../com/sw/inbound/dialog/GoodsStoreDialog.kt | 46 ++++++---- .../java/com/sw/inbound/objbox/FoodModule.kt | 8 +- .../java/com/sw/inbound/objbox/ObjectBox.kt | 15 ++++ 7 files changed, 120 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/com/sw/inbound/MainActivity.kt b/app/src/main/java/com/sw/inbound/MainActivity.kt index 698c753..086b553 100644 --- a/app/src/main/java/com/sw/inbound/MainActivity.kt +++ b/app/src/main/java/com/sw/inbound/MainActivity.kt @@ -6,15 +6,14 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope import com.sw.inbound.objbox.FoodModule import com.sw.inbound.sdk.SensorScaleUtils import com.sw.inbound.ui.AppScreen -import com.sw.inbound.utils.ContextUtils -import com.sw.inbound.utils.SPUtil import com.sw.inbound.utils.ThreadUtils import com.sw.inbound.viewmodel.ReceiptViewModel import dagger.hilt.android.AndroidEntryPoint -import kotlin.getValue +import kotlinx.coroutines.launch @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -35,7 +34,9 @@ class MainActivity : ComponentActivity() { }) } SensorScaleUtils.startScale(autoScale = true) - FoodModule.init(this) + lifecycleScope.launch { + FoodModule.init(this@MainActivity) + } } override fun onDestroy() { diff --git a/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt b/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt index 28b885d..df0c8ce 100644 --- a/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt +++ b/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt @@ -71,7 +71,7 @@ class FoodCollectionActivity : ComponentActivity() { const val PAGE_SIZE = 30 } - private var box: Box? = null + // private var box: Box? = null private val collectList: MutableList = mutableListOf() private lateinit var binding: ActivityFoodCollectionBinding private lateinit var previewView: PreviewView @@ -287,11 +287,17 @@ class FoodCollectionActivity : ComponentActivity() { } private val cameraCallback: (Uri) -> Unit = { uri -> + lifecycleScope.launch { + handlePhoto(uri) + } + } + + private suspend fun handlePhoto(uri: Uri) { val index = collectList.indexOfFirst { it.imageFile == null } if (index == -1) { toast("每次只允许保存${MAX_COUNT}条数据") showWaitingDialog2("加载中") - rerurn@ cameraCallback + return } // toast("index=$index") try { @@ -373,7 +379,7 @@ class FoodCollectionActivity : ComponentActivity() { private fun vectorThread() { Loading.show(this) - Thread { + lifecycleScope.launch { collectList //.filter { it.bitmap != null } // .filter { it.imageVector != null } @@ -389,10 +395,10 @@ class FoodCollectionActivity : ComponentActivity() { toast("保存成功") }, 1000) } - }.start() + } } - private fun image2VectorTask( + private suspend fun image2VectorTask( bitmap: Bitmap? = null, imageVector: FloatArray? = null, position: Int @@ -408,9 +414,10 @@ class FoodCollectionActivity : ComponentActivity() { // } val goods = searchGoodsList[clickIndex] val saveName = goods.goodsName + goods.goodsCode - ObjectBox.boxStore.runInTx { - box?.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector)) - } +// ObjectBox.boxStore.runInTx { +// box?.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector)) +// } + ObjectBox.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector)) collectList[position].let { // it.imageVector = imageVector it.isFinish = true @@ -491,9 +498,9 @@ class FoodCollectionActivity : ComponentActivity() { } private fun initBox() { - if (box == null) { - box = ObjectBox.boxStore.boxFor(Food::class) - } +// if (box == null) { +// box = ObjectBox.boxStore.boxFor(Food::class) +// } } // private fun uploadAllImage() { diff --git a/app/src/main/java/com/sw/inbound/activity/GoodsListActivity.kt b/app/src/main/java/com/sw/inbound/activity/GoodsListActivity.kt index 7e23007..2e78903 100644 --- a/app/src/main/java/com/sw/inbound/activity/GoodsListActivity.kt +++ b/app/src/main/java/com/sw/inbound/activity/GoodsListActivity.kt @@ -234,7 +234,7 @@ abstract class GoodsListActivity : ComponentActivity() { var isFirstRecognize = true private val takePhotoSuccessCallback: (Uri) -> Unit = { uri -> - Thread { + lifecycleScope.launch { try { searchFood(uri) } catch (e: Exception) { @@ -307,7 +307,7 @@ abstract class GoodsListActivity : ComponentActivity() { } var isShowRecognizeDialog = true - private fun searchFood(uri: Uri) { + private suspend fun searchFood(uri: Uri) { ImageUtil.uriToBitmap(this, uri)?.let { bitmap -> val newBmp = BitmapCropper.cropCenter( original = bitmap, diff --git a/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt b/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt index 74bfb14..48726f6 100644 --- a/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt @@ -4,8 +4,8 @@ import android.annotation.SuppressLint import android.content.Context import android.view.LayoutInflater import android.view.View -import androidx.activity.viewModels import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import com.sw.inbound.R @@ -14,14 +14,11 @@ import com.sw.inbound.adapter.CollectSearchAdapter import com.sw.inbound.databinding.DialogCollectSearchBinding import com.sw.inbound.databinding.LayoutEmptySearchBinding import com.sw.inbound.model.response.SearchGoodsInfo -import com.sw.inbound.objbox.Food import com.sw.inbound.objbox.ObjectBox import com.sw.inbound.utils.ext.addOnActionSearchListener import com.sw.inbound.utils.ext.hideKeyboard import com.sw.inbound.utils.ext.toast -import com.sw.inbound.viewmodel.ReceiptViewModel -import io.objectbox.Box -import io.objectbox.kotlin.boxFor +import kotlinx.coroutines.launch @SuppressLint("NotifyDataSetChanged") class CollectSearchDialog( @@ -69,8 +66,8 @@ class CollectSearchDialog( dismiss() return@setOnClickListener } - deleteGoods(clickIndex) { - it.postDelayed({dismiss()}, 500) + showDeleteDialog(clickIndex) { + it.postDelayed({ dismiss() }, 500) } } binding.rvSearch.let { @@ -142,40 +139,47 @@ class CollectSearchDialog( } list[position].isSelected = true notifyDataSetChanged() - deleteGoods(position) + showDeleteDialog(position) } } } - private fun deleteGoods(position: Int, deleteAction:()-> Unit = {}) { + private fun showDeleteDialog(position: Int, deleteAction: () -> Unit = {}) { WarnDialog( context = context, content = "请确认是否删除物品:${list[position].goodsName}?", confirmBlock = { - Thread { - activity?.runOnUiThread { - Loading.show(activity!!) - } - val name = list[position].goodsName - val filterIdList = box?.all?.filter { it.name == name }?.map { it.id } - ObjectBox.boxStore.runInTx { - box?.removeByIds(filterIdList) - } - activity?.runOnUiThread { - Loading.dismiss() - //list.remove(list[position]) - //searchAdapter.notifyDataSetChanged() - searchAdapter.removeAt(position) - activity?.toast("删除成功") - if (list.isEmpty()) { - loadEmptyView() - } - deleteAction() - } - }.start() +// Thread { +// }.start() + activity?.lifecycleScope?.launch { + deleteGoods(position, deleteAction) + } }).show() } + private suspend fun deleteGoods(position: Int, deleteAction: () -> Unit = {}) { + activity?.runOnUiThread { + Loading.show(activity!!) + } + val name = list[position].goodsName ?: "" +// val filterIdList = box?.all?.filter { it.name == name }?.map { it.id } +// ObjectBox.boxStore.runInTx { +// box?.removeByIds(filterIdList) +// } + ObjectBox.remove(name) + activity?.runOnUiThread { + Loading.dismiss() + //list.remove(list[position]) + //searchAdapter.notifyDataSetChanged() + searchAdapter.removeAt(position) + activity?.toast("删除成功") + if (list.isEmpty()) { + loadEmptyView() + } + deleteAction() + } + } + private fun finishRefresh() { binding.refreshLayout.let { if (pageNo == 1) { @@ -186,18 +190,19 @@ class CollectSearchDialog( } } - private var box: Box? = null +// private var box: Box? = null @SuppressLint("NotifyDataSetChanged") - private fun getCollectGoods(searchName: String? = null) { - if (box == null) { - box = ObjectBox.boxStore.boxFor(Food::class) - } + private fun getCollectGoods(searchName: String? = null) = activity?.lifecycleScope?.launch { +// 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 } - } +// var queryList = box?.all?.distinctBy { it.name } +// if (searchName.isNullOrBlank().not()) { +// queryList = queryList?.filter { it.name?.contains(searchName!!) == true } +// } + val queryList = ObjectBox.filter(searchName) queryList?.forEach { list.add((SearchGoodsInfo.Record(goodsName = it.name))) } diff --git a/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt b/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt index 3bfcd4d..61a56f2 100644 --- a/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt @@ -15,10 +15,9 @@ import androidx.camera.view.PreviewView import androidx.core.graphics.toColorInt import androidx.core.view.updateLayoutParams import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope import coil.load import coil.transform.RoundedCornersTransformation -import com.sw.inbound.utils.ext.appendText -import com.sw.inbound.utils.ext.buildSpannableString import com.sw.inbound.R import com.sw.inbound.activity.GoodsListActivity import com.sw.inbound.databinding.DialogGoodsStoreBinding @@ -35,6 +34,8 @@ import com.sw.inbound.utils.BitmapCropper import com.sw.inbound.utils.BitmapSaver import com.sw.inbound.utils.CameraUtils import com.sw.inbound.utils.ImageUtil +import com.sw.inbound.utils.ext.appendText +import com.sw.inbound.utils.ext.buildSpannableString import com.sw.inbound.utils.ext.dp import com.sw.inbound.utils.ext.gone import com.sw.inbound.utils.ext.hideKeyboard @@ -43,11 +44,9 @@ import com.sw.inbound.utils.ext.roundedDecimalPlace import com.sw.inbound.utils.ext.toast import com.sw.inbound.utils.ext.visible import com.sw.inbound.utils.removeTrailingZeros -import io.objectbox.Box -import io.objectbox.kotlin.boxFor +import kotlinx.coroutines.launch import timber.log.Timber import java.io.File -import kotlin.Int import kotlin.math.abs import kotlin.math.min @@ -441,8 +440,10 @@ class GoodsStoreDialog( } } } - private var imageFile: File?=null - private var box: Box? = null + + private var imageFile: File? = null + + // private var box: Box? = null private fun collectGoodsVector() { try { ImageUtil.uriToBitmap(context, imageUri!!)?.let { bitmap -> @@ -458,21 +459,36 @@ class GoodsStoreDialog( ) imageFile = file Timber.d("${this.javaClass.simpleName}-collectGoodsVector-裁剪bitmap保存文件路径:${file?.absolutePath}") - if (box == null) { - box = ObjectBox.boxStore.boxFor(Food::class) - } - Thread { +// if (box == null) { +// box = ObjectBox.boxStore.boxFor(Food::class) +// } + activity?.lifecycleScope?.launch { try { val imageVector = FoodModule.bitmap2FloatArray(bitmap, true) if (imageVector != null) { - ObjectBox.boxStore.runInTx { - box?.put(Food(name = goods.goodsName + goods.goodsCode, foodIdx = 0, foodVector = imageVector)) - } + val food = Food( + name = goods.goodsName + goods.goodsCode, + foodIdx = 0, + foodVector = imageVector + ) + ObjectBox.put(food) } } catch (e: Exception) { e.printStackTrace() } - }.start() + } +// Thread { +// try { +// val imageVector = FoodModule.bitmap2FloatArray(bitmap, true) +// if (imageVector != null) { +// ObjectBox.boxStore.runInTx { +// box?.put(Food(name = goods.goodsName + goods.goodsCode, foodIdx = 0, foodVector = imageVector)) +// } +// } +// } catch (e: Exception) { +// e.printStackTrace() +// } +// }.start() } } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt b/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt index df93c55..6f2c71c 100644 --- a/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt +++ b/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt @@ -47,9 +47,11 @@ object FoodModule { suspend fun init(context: Context) { withContext(Dispatchers.IO) { module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt")) - val box = ObjectBox.getBox() - if (box?.all?.isEmpty() == true) { - initDefFoodData(context) + ObjectBox.safeDbOp { + val box = ObjectBox.getBox() + if (box?.all?.isEmpty() == true) { + initDefFoodData(context) + } } } } diff --git a/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt b/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt index d034dbe..71a96b9 100644 --- a/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt +++ b/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt @@ -174,6 +174,21 @@ object ObjectBox { getBox()?.put(entities) } + suspend fun filter(name:String?) = safeDbOp { + var list = getBox()?.all?.distinctBy { it.name } + if (name.isNullOrBlank().not()) { + list?.filter { it.name?.contains(name!!) == true } + } + list?:emptyList() + } + + suspend fun remove(name:String) = safeDbOp { + getBox()?.run { + val filterIdList = all.filter { it.name == name }.map { it.id } + removeByIds(filterIdList) + } + } + private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性 private const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录 private const val BACKUP_DIR_NAME = "objectbox_backup" // 备份目录