改造objectbox

This commit is contained in:
2026-01-31 17:49:44 +08:00
parent 4322c9495b
commit af120eabd0
7 changed files with 120 additions and 74 deletions
@@ -6,15 +6,14 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.sw.inbound.objbox.FoodModule import com.sw.inbound.objbox.FoodModule
import com.sw.inbound.sdk.SensorScaleUtils import com.sw.inbound.sdk.SensorScaleUtils
import com.sw.inbound.ui.AppScreen 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.utils.ThreadUtils
import com.sw.inbound.viewmodel.ReceiptViewModel import com.sw.inbound.viewmodel.ReceiptViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlin.getValue import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -35,7 +34,9 @@ class MainActivity : ComponentActivity() {
}) })
} }
SensorScaleUtils.startScale(autoScale = true) SensorScaleUtils.startScale(autoScale = true)
FoodModule.init(this) lifecycleScope.launch {
FoodModule.init(this@MainActivity)
}
} }
override fun onDestroy() { override fun onDestroy() {
@@ -71,7 +71,7 @@ class FoodCollectionActivity : ComponentActivity() {
const val PAGE_SIZE = 30 const val PAGE_SIZE = 30
} }
private var box: Box<Food>? = null // private var box: Box<Food>? = null
private val collectList: MutableList<FoodCollectionBean> = mutableListOf<FoodCollectionBean>() private val collectList: MutableList<FoodCollectionBean> = mutableListOf<FoodCollectionBean>()
private lateinit var binding: ActivityFoodCollectionBinding private lateinit var binding: ActivityFoodCollectionBinding
private lateinit var previewView: PreviewView private lateinit var previewView: PreviewView
@@ -287,11 +287,17 @@ class FoodCollectionActivity : ComponentActivity() {
} }
private val cameraCallback: (Uri) -> Unit = { uri -> private val cameraCallback: (Uri) -> Unit = { uri ->
lifecycleScope.launch {
handlePhoto(uri)
}
}
private suspend fun handlePhoto(uri: Uri) {
val index = collectList.indexOfFirst { it.imageFile == null } val index = collectList.indexOfFirst { it.imageFile == null }
if (index == -1) { if (index == -1) {
toast("每次只允许保存${MAX_COUNT}条数据") toast("每次只允许保存${MAX_COUNT}条数据")
showWaitingDialog2("加载中") showWaitingDialog2("加载中")
rerurn@ cameraCallback return
} }
// toast("index=$index") // toast("index=$index")
try { try {
@@ -373,7 +379,7 @@ class FoodCollectionActivity : ComponentActivity() {
private fun vectorThread() { private fun vectorThread() {
Loading.show(this) Loading.show(this)
Thread { lifecycleScope.launch {
collectList collectList
//.filter { it.bitmap != null } //.filter { it.bitmap != null }
// .filter { it.imageVector != null } // .filter { it.imageVector != null }
@@ -389,10 +395,10 @@ class FoodCollectionActivity : ComponentActivity() {
toast("保存成功") toast("保存成功")
}, 1000) }, 1000)
} }
}.start() }
} }
private fun image2VectorTask( private suspend fun image2VectorTask(
bitmap: Bitmap? = null, bitmap: Bitmap? = null,
imageVector: FloatArray? = null, imageVector: FloatArray? = null,
position: Int position: Int
@@ -408,9 +414,10 @@ class FoodCollectionActivity : ComponentActivity() {
// } // }
val goods = searchGoodsList[clickIndex] val goods = searchGoodsList[clickIndex]
val saveName = goods.goodsName + goods.goodsCode val saveName = goods.goodsName + goods.goodsCode
ObjectBox.boxStore.runInTx { // ObjectBox.boxStore.runInTx {
box?.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector)) // box?.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector))
} // }
ObjectBox.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector))
collectList[position].let { collectList[position].let {
// it.imageVector = imageVector // it.imageVector = imageVector
it.isFinish = true it.isFinish = true
@@ -491,9 +498,9 @@ class FoodCollectionActivity : ComponentActivity() {
} }
private fun initBox() { private fun initBox() {
if (box == null) { // if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class) // box = ObjectBox.boxStore.boxFor(Food::class)
} // }
} }
// private fun uploadAllImage() { // private fun uploadAllImage() {
@@ -234,7 +234,7 @@ abstract class GoodsListActivity : ComponentActivity() {
var isFirstRecognize = true var isFirstRecognize = true
private val takePhotoSuccessCallback: (Uri) -> Unit = { uri -> private val takePhotoSuccessCallback: (Uri) -> Unit = { uri ->
Thread { lifecycleScope.launch {
try { try {
searchFood(uri) searchFood(uri)
} catch (e: Exception) { } catch (e: Exception) {
@@ -307,7 +307,7 @@ abstract class GoodsListActivity : ComponentActivity() {
} }
var isShowRecognizeDialog = true var isShowRecognizeDialog = true
private fun searchFood(uri: Uri) { private suspend fun searchFood(uri: Uri) {
ImageUtil.uriToBitmap(this, uri)?.let { bitmap -> ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
val newBmp = BitmapCropper.cropCenter( val newBmp = BitmapCropper.cropCenter(
original = bitmap, original = bitmap,
@@ -4,8 +4,8 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.sw.inbound.R 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.DialogCollectSearchBinding
import com.sw.inbound.databinding.LayoutEmptySearchBinding import com.sw.inbound.databinding.LayoutEmptySearchBinding
import com.sw.inbound.model.response.SearchGoodsInfo import com.sw.inbound.model.response.SearchGoodsInfo
import com.sw.inbound.objbox.Food
import com.sw.inbound.objbox.ObjectBox import com.sw.inbound.objbox.ObjectBox
import com.sw.inbound.utils.ext.addOnActionSearchListener import com.sw.inbound.utils.ext.addOnActionSearchListener
import com.sw.inbound.utils.ext.hideKeyboard import com.sw.inbound.utils.ext.hideKeyboard
import com.sw.inbound.utils.ext.toast import com.sw.inbound.utils.ext.toast
import com.sw.inbound.viewmodel.ReceiptViewModel import kotlinx.coroutines.launch
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
class CollectSearchDialog( class CollectSearchDialog(
@@ -69,8 +66,8 @@ class CollectSearchDialog(
dismiss() dismiss()
return@setOnClickListener return@setOnClickListener
} }
deleteGoods(clickIndex) { showDeleteDialog(clickIndex) {
it.postDelayed({dismiss()}, 500) it.postDelayed({ dismiss() }, 500)
} }
} }
binding.rvSearch.let { binding.rvSearch.let {
@@ -142,40 +139,47 @@ class CollectSearchDialog(
} }
list[position].isSelected = true list[position].isSelected = true
notifyDataSetChanged() notifyDataSetChanged()
deleteGoods(position) showDeleteDialog(position)
} }
} }
} }
private fun deleteGoods(position: Int, deleteAction:()-> Unit = {}) { private fun showDeleteDialog(position: Int, deleteAction: () -> Unit = {}) {
WarnDialog( WarnDialog(
context = context, context = context,
content = "请确认是否删除物品:${list[position].goodsName}", content = "请确认是否删除物品:${list[position].goodsName}",
confirmBlock = { confirmBlock = {
Thread { // Thread {
activity?.runOnUiThread { // }.start()
Loading.show(activity!!) activity?.lifecycleScope?.launch {
} deleteGoods(position, deleteAction)
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()
}).show() }).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() { private fun finishRefresh() {
binding.refreshLayout.let { binding.refreshLayout.let {
if (pageNo == 1) { if (pageNo == 1) {
@@ -186,18 +190,19 @@ class CollectSearchDialog(
} }
} }
private var box: Box<Food>? = null // private var box: Box<Food>? = null
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun getCollectGoods(searchName: String? = null) { private fun getCollectGoods(searchName: String? = null) = activity?.lifecycleScope?.launch {
if (box == null) { // if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class) // box = ObjectBox.boxStore.boxFor(Food::class)
} // }
list.clear() list.clear()
var queryList = box?.all?.distinctBy { it.name } // var queryList = box?.all?.distinctBy { it.name }
if (searchName.isNullOrBlank().not()) { // if (searchName.isNullOrBlank().not()) {
queryList = queryList?.filter { it.name?.contains(searchName!!) == true } // queryList = queryList?.filter { it.name?.contains(searchName!!) == true }
} // }
val queryList = ObjectBox.filter(searchName)
queryList?.forEach { queryList?.forEach {
list.add((SearchGoodsInfo.Record(goodsName = it.name))) list.add((SearchGoodsInfo.Record(goodsName = it.name)))
} }
@@ -15,10 +15,9 @@ import androidx.camera.view.PreviewView
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.lifecycleScope
import coil.load import coil.load
import coil.transform.RoundedCornersTransformation 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.R
import com.sw.inbound.activity.GoodsListActivity import com.sw.inbound.activity.GoodsListActivity
import com.sw.inbound.databinding.DialogGoodsStoreBinding 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.BitmapSaver
import com.sw.inbound.utils.CameraUtils import com.sw.inbound.utils.CameraUtils
import com.sw.inbound.utils.ImageUtil 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.dp
import com.sw.inbound.utils.ext.gone import com.sw.inbound.utils.ext.gone
import com.sw.inbound.utils.ext.hideKeyboard 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.toast
import com.sw.inbound.utils.ext.visible import com.sw.inbound.utils.ext.visible
import com.sw.inbound.utils.removeTrailingZeros import com.sw.inbound.utils.removeTrailingZeros
import io.objectbox.Box import kotlinx.coroutines.launch
import io.objectbox.kotlin.boxFor
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import kotlin.Int
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
@@ -441,8 +440,10 @@ class GoodsStoreDialog(
} }
} }
} }
private var imageFile: File?=null
private var box: Box<Food>? = null private var imageFile: File? = null
// private var box: Box<Food>? = null
private fun collectGoodsVector() { private fun collectGoodsVector() {
try { try {
ImageUtil.uriToBitmap(context, imageUri!!)?.let { bitmap -> ImageUtil.uriToBitmap(context, imageUri!!)?.let { bitmap ->
@@ -458,21 +459,36 @@ class GoodsStoreDialog(
) )
imageFile = file imageFile = file
Timber.d("${this.javaClass.simpleName}-collectGoodsVector-裁剪bitmap保存文件路径:${file?.absolutePath}") Timber.d("${this.javaClass.simpleName}-collectGoodsVector-裁剪bitmap保存文件路径:${file?.absolutePath}")
if (box == null) { // if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class) // box = ObjectBox.boxStore.boxFor(Food::class)
} // }
Thread { activity?.lifecycleScope?.launch {
try { try {
val imageVector = FoodModule.bitmap2FloatArray(bitmap, true) val imageVector = FoodModule.bitmap2FloatArray(bitmap, true)
if (imageVector != null) { if (imageVector != null) {
ObjectBox.boxStore.runInTx { val food = Food(
box?.put(Food(name = goods.goodsName + goods.goodsCode, foodIdx = 0, foodVector = imageVector)) name = goods.goodsName + goods.goodsCode,
} foodIdx = 0,
foodVector = imageVector
)
ObjectBox.put(food)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() 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) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -47,9 +47,11 @@ object FoodModule {
suspend fun init(context: Context) { suspend fun init(context: Context) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt")) module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
val box = ObjectBox.getBox<Food>() ObjectBox.safeDbOp {
if (box?.all?.isEmpty() == true) { val box = ObjectBox.getBox<Food>()
initDefFoodData(context) if (box?.all?.isEmpty() == true) {
initDefFoodData(context)
}
} }
} }
} }
@@ -174,6 +174,21 @@ object ObjectBox {
getBox<Food>()?.put(entities) getBox<Food>()?.put(entities)
} }
suspend fun filter(name:String?) = safeDbOp {
var list = getBox<Food>()?.all?.distinctBy { it.name }
if (name.isNullOrBlank().not()) {
list?.filter { it.name?.contains(name!!) == true }
}
list?:emptyList()
}
suspend fun remove(name:String) = safeDbOp {
getBox<Food>()?.run {
val filterIdList = all.filter { it.name == name }.map { it.id }
removeByIds(filterIdList)
}
}
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性 private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
private const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录 private const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录
private const val BACKUP_DIR_NAME = "objectbox_backup" // 备份目录 private const val BACKUP_DIR_NAME = "objectbox_backup" // 备份目录