入库功能增加相机预览及拍照

This commit is contained in:
2025-11-11 11:52:41 +08:00
parent 9927ebe7f1
commit 750d92e2f9
10 changed files with 295 additions and 93 deletions
@@ -67,11 +67,6 @@ class FoodCollectionActivity : ComponentActivity() {
}
private val collectionAdapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(collectList).apply {
// setOnItemClickListener { _, _, position ->
// if (collectList[position].isShowCamera) {
// takePhoto()
// }
// }
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
collectList[position].let {
it.bitmap = null
@@ -79,13 +74,6 @@ class FoodCollectionActivity : ComponentActivity() {
it.isShowCamera = true
}
collectionAdapter.notifyItemChanged(position)
// if (position == collectList.size - 1) {
//
// return@addOnItemChildClickListener
// }
// collectList.removeAt(position)
// adapter.notifyItemRemoved(position)
// adapter.notifyItemRangeChanged(position, collectList.size)
}
}
}
@@ -156,25 +144,6 @@ class FoodCollectionActivity : ComponentActivity() {
binding.btnCollectedGoods.setOnClickListener {
CollectSearchDialog(this@FoodCollectionActivity).show()
}
// binding.btnAddDefault.clickWithDebounce {
// Thread{
// FoodModule.initDefFoodData(this) {
// runOnUiThread {
// toast("添加完成")
// }
// }
// }.start()
// }
// binding.btnRemoveDefault.clickWithDebounce {
// initBox()
// val list = box?.all?.filter { it.foodIdx == FoodModule.DEFAULT_FOOD_INDEX }
// if (list.isNullOrEmpty()) {
// toast("不存在默认数据")
// return@clickWithDebounce
// }
// box?.removeByIds(list.map { it.id })
// toast("移除完成")
// }
binding.btnClearData.setOnClickListener { clearData() }
binding.rvSearch.let {
it.layoutManager = GridLayoutManager(this, 2, LinearLayoutManager.VERTICAL, false)
@@ -191,49 +160,10 @@ class FoodCollectionActivity : ComponentActivity() {
searchGoods()
}
}
// binding.radioGroup.setOnCheckedChangeListener { group, checkedId ->
// if (checkedId == R.id.rbCollected) {
// binding.llSearchInput.gone()
// binding.refreshLayout.let {
// it.setEnableRefresh(false)
// it.setEnableLoadMore(false)
// }
// getCollectedGoods()
// return@setOnCheckedChangeListener
// }
// if (checkedId == R.id.rbSearch) {
// binding.llSearchInput.visible()
// pageNo = 1
// clickIndex = -1
// searchGoodsList.clear()
// searchAdapter.notifyDataSetChanged()
// binding.refreshLayout.let {
// it.setEnableRefresh(true)
// it.setEnableLoadMore(false)
// }
// loadEmptyView()
// return@setOnCheckedChangeListener
// }
// }
// binding.radioGroup.check(R.id.rbCollected)
loadEmptyView()
}
@SuppressLint("NotifyDataSetChanged")
private fun getCollectedGoods() {
searchGoodsList.clear()
initBox()
box?.all?.distinctBy { it.name }?.forEach {
searchGoodsList.add((SearchGoodsInfo.Record(goodsName = it.name)))
}
binding.rvSearch.layoutManager = GridLayoutManager(this, 2, LinearLayoutManager.VERTICAL, false)
searchAdapter.notifyDataSetChanged()
if (searchGoodsList.isEmpty()) {
loadEmptyView()
}
}
private var pageNo = 1
private var clickIndex = -1
@@ -313,12 +243,6 @@ class FoodCollectionActivity : ComponentActivity() {
it.isShowCamera = false
}
collectionAdapter.notifyItemChanged(index)
// if (collectList.size < 6) {
// collectList.add(collectList.size - 1, FoodCollectionBean(bitmap = cropBitmap))
// } else {
// collectList[collectList.size - 1] = FoodCollectionBean(bitmap = cropBitmap)
// }
// adapter.notifyDataSetChanged()
}
}
@@ -15,8 +15,8 @@ import java.io.File
@SuppressLint("NotifyDataSetChanged")
class GoodsRecognizeDialog(
context: Context,
var goodsFile: File?=null,
var goodsName: String?=null,
var goodsFile: File? = null,
var goodsName: String? = null,
var list: MutableList<SearchGoodsInfo.Record>,
//var closeCallback:()->Unit,
var findGoods: () -> Unit
@@ -55,7 +55,11 @@ class GoodsRecognizeDialog(
}
private fun showGoodsStoreDialog(position: Int) {
GoodsStoreDialog(context, list[position]) {
GoodsStoreDialog(
context = context,
funcType = 0,
goods = list[position]
) {
activity?.addGoods(it)
activity?.startTime = System.currentTimeMillis()
dismiss()
@@ -63,7 +63,11 @@ class GoodsSearchDialog(
context.toast("请选择物品")
return@setOnClickListener
}
GoodsStoreDialog(context, list[clickIndex]) {
GoodsStoreDialog(
context = context,
funcType = 1,
goods = list[clickIndex]
) {
activity?.addGoods(it)
activity?.startTime = System.currentTimeMillis()
activity?.goodsRecognizeDialog?.dismiss()
@@ -2,6 +2,7 @@ package com.sw.inbound.dialog
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.text.InputType
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
@@ -9,6 +10,7 @@ import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import androidx.camera.view.PreviewView
import androidx.core.graphics.toColorInt
import androidx.core.widget.addTextChangedListener
import coil.load
@@ -16,15 +18,29 @@ import com.shuwei.intelligent.shelves.utils.ext.appendText
import com.shuwei.intelligent.shelves.utils.ext.buildSpannableString
import com.sw.inbound.activity.GoodsListActivity
import com.sw.inbound.databinding.DialogGoodsStoreBinding
import com.sw.inbound.databinding.LayoutImagePreviewBinding
import com.sw.inbound.databinding.LayoutTakePhotoBinding
import com.sw.inbound.ext.toFormattedString
import com.sw.inbound.model.response.DictType
import com.sw.inbound.model.response.GoodsInfo
import com.sw.inbound.model.response.SearchGoodsInfo
import com.sw.inbound.objbox.Food
import com.sw.inbound.objbox.FoodModule
import com.sw.inbound.objbox.ObjectBox
import com.sw.inbound.sdk.SensorScaleUtils
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.dp
import com.sw.inbound.utils.ext.gone
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 timber.log.Timber
import kotlin.Int
import kotlin.math.abs
import kotlin.math.min
@@ -32,6 +48,7 @@ import kotlin.math.min
class GoodsStoreDialog(
context: Context,
var goods: SearchGoodsInfo.Record,
var funcType: Int,
var cancelBlock: () -> Unit = {},
var confirmBlock: (GoodsInfo) -> Unit = {},
) : BaseDialog(context) {
@@ -65,7 +82,9 @@ class GoodsStoreDialog(
setText(goods.goodsName)
isEnabled = false
}
binding.ivGoodsImage.load(goods.relativeUrl)
//binding.ivGoodsImage.load(goods.relativeUrl)
addChildToContainer()
//flContainer
binding.btnCancel.setOnClickListener {
cancelBlock()
dismiss()
@@ -101,9 +120,12 @@ class GoodsStoreDialog(
}
val first = goods.unitVoList!!.first { it.businessUnitId == dictType.id }
val stringBuilder = StringBuilder()
.append("1").append(dictType.value).append("=").append(first.purchaseValue!!.toInt().toString()).append(goods.kcUnitName)
.append("1").append(dictType.value).append("=")
.append(first.purchaseValue!!.toInt().toString())
.append(goods.kcUnitName)
.append("")
.append("1").append(goods.kcUnitName).append("=").append(first.consumeValue).append("")
.append("1").append(goods.kcUnitName).append("=")
.append(first.consumeValue).append("")
binding.tvUnitCalculate.text = stringBuilder.toString()
//val count = getUnitCount(dictType.id!!)
//if (count.isBlank() || "0" == count) {
@@ -249,6 +271,10 @@ class GoodsStoreDialog(
}
private fun confirmInputData() {
if (funcType == 1 && imageUri == null) {
context.toast("请采集图片")
return
}
val unit = binding.tvProcurementUnit.text.trim().toString()
if (unit.isBlank()) {
context.toast("请选择入库单位")
@@ -288,6 +314,9 @@ class GoodsStoreDialog(
context.toast("入库金额不能为0")
return
}
if (funcType == 1) {
collectGoodsVector()
}
val id = binding.tvProcurementUnit.tag.toString()
val unitVo = goods.unitVoList!!.first { it.businessUnitId == id }
confirmBlock(
@@ -320,8 +349,96 @@ class GoodsStoreDialog(
private fun getUnitCount(id: String): String {
val first = goods.unitVoList!!.first { it.businessUnitId == id }
val count = first.getPurchaseUnitValue( this.realWeight)
val count = first.getPurchaseUnitValue(this.realWeight)
return count
}
private val cameraUtils: CameraUtils by lazy {
CameraUtils(activity!!)
}
override fun show() {
super.show()
takePhotoBinding?.let {
cameraUtils.setPreviewController(previewView)
cameraUtils.bind()
it.llCameraFlag.run {
postDelayed({
gone()
}, 1000)
}
}
}
private var imageUri: Uri? = null
private val takePhotoCallback: (Uri) -> Unit = { uri ->
takePhotoBinding?.let {
imageUri = uri
it.flCameraPreview.gone()
it.cvImageBg.visible()
it.ivGoodsImage.load(uri)
}
}
private var previewView: PreviewView? = null
private var takePhotoBinding: LayoutTakePhotoBinding? = null
private var imagePreviewBinding: LayoutImagePreviewBinding? = null
private fun addChildToContainer() {
val inflater = LayoutInflater.from(context)
if (funcType == 1) {
//预览及拍照
takePhotoBinding = LayoutTakePhotoBinding.inflate(inflater, binding.flContainer, true)
activity?.unBindCamera()
setOnDismissListener {
cameraUtils.unbind()
}
cameraUtils.initCamera()
takePhotoBinding?.let {
previewView = it.previewView
it.flCameraPreview.visible()
it.cvImageBg.gone()
it.btnStartPreview.setOnClickListener { _ ->
it.cvImageBg.gone()
it.flCameraPreview.visible()
}
it.btnTakePhoto.setOnClickListener { _ ->
cameraUtils.takePhoto(takePhotoCallback)
}
}
return
}
//只显示原始图片
imagePreviewBinding = LayoutImagePreviewBinding.inflate(inflater, binding.flContainer)
imagePreviewBinding?.imageView?.load(goods.relativeUrl)
}
private var box: Box<Food>? = null
private fun collectGoodsVector() {
try {
ImageUtil.uriToBitmap(context, imageUri!!)?.let { bitmap ->
val cropBitmap = BitmapCropper.cropCenter(
original = bitmap,
targetWidth = 1000, targetHeight = 1300,
offsetX = 30, offsetY = 100
)
val file = BitmapSaver.saveToAppFilesDir(
cropBitmap, context, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-collectGoodsVector-裁剪bitmap保存文件路径:${file?.absolutePath}")
if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class)
}
Thread {
try {
val imageVector = FoodModule.bitmap2FloatArray(bitmap)
box?.put(Food(name = goods.goodsName, foodIdx = 0, foodVector = imageVector))
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -96,7 +96,7 @@ object FoodModule {
nameScoreList.add(IdNameScore(id = it.id, name = box.get(it.id).name?:"", score = it.score))
}
Timber.tag("FoodModule").d("queryFood数据:${nameScoreList.toJsonString()}")
idScoreList.filter { it.score < 0.07 }.forEach {
idScoreList.filter { it.score < 0.05 }.forEach {
val food = box.get(it.id)
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
food.name?.let { key ->
@@ -206,8 +206,8 @@ class CrashHandler private constructor(private val context: Context) :
// val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
val crashDir = File(context.filesDir, CRASH_REPORTS_DIR)
if (!crashDir.exists() && !crashDir.mkdirs()) {
Timber.tag(TAG).e("Failed to create crash report directory")
if (!crashDir.exists()) {
crashDir.mkdirs()
return
}
@@ -302,15 +302,18 @@
<FrameLayout
android:id="@+id/flContainer"
android:layout_width="488dp"
android:layout_height="356dp"
android:background="@drawable/bg_white_radius10_stroke2"
android:padding="2dp"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"/>
<!-- android:padding="2dp"-->
<!-- android:layout_width="488dp"-->
<!-- android:layout_height="356dp"-->
<!-- android:layout_marginBottom="37dp"-->
<!-- android:background="@drawable/bg_white_radius10_stroke2"-->
<FrameLayout
android:layout_width="488dp"
android:layout_height="70dp"
android:layout_marginTop="37dp"
android:background="@drawable/bg_gray_radius10">
<TextView
@@ -17,7 +17,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="系统异常,请稍后重试!"
android:text="识别失败,请稍后重试!"
android:textColor="#ff000000"
android:textSize="36sp"
android:textStyle="bold" />
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"
android:layout_gravity="center"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_launcher" />
</merge>
@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<FrameLayout
android:layout_width="488dp"
android:layout_height="325dp"
android:background="@drawable/bg_white_radius10_stroke2_dash">
<androidx.cardview.widget.CardView
android:id="@+id/flCameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:cardCornerRadius="10dp"
app:cardElevation="0dp">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/cvImageBg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="3dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="10dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/ivGoodsImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="centerCrop"
tools:ignore="ContentDescription" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/llCameraFlag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@drawable/bg_white_radius10_stroke2_dash"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="96dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:src="@mipmap/ic_camera_gray"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="图片采集"
android:textColor="#ffb4b4c8"
android:textSize="24sp"
tools:ignore="HardcodedText" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Button
android:id="@+id/btnStartPreview"
android:layout_width="150dp"
android:layout_height="50dp"
android:background="@drawable/bg_white_stroke_green_ripple"
android:text="取消"
android:textColor="#FF14CD71"
android:textSize="21sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<LinearLayout
android:id="@+id/btnTakePhoto"
android:layout_width="150dp"
android:layout_height="50dp"
android:background="@drawable/bg_green_ripple"
android:gravity="center">
<ImageView
android:layout_width="26dp"
android:layout_height="wrap_content"
android:src="@mipmap/ic_camera_white"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginStart="10dp"
android:text="采集"
android:textColor="@color/white"
android:textSize="21sp"
android:textStyle="bold"
tools:ignore="HardcodedText,UseCompatTextViewDrawableXml" />
</LinearLayout>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>