diff --git a/app/src/main/java/com/sw/inbound/MainActivity.kt b/app/src/main/java/com/sw/inbound/MainActivity.kt index bb5710f..b6cbcf0 100644 --- a/app/src/main/java/com/sw/inbound/MainActivity.kt +++ b/app/src/main/java/com/sw/inbound/MainActivity.kt @@ -5,15 +5,22 @@ import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +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 @AndroidEntryPoint class MainActivity : ComponentActivity() { + + val viewModel: ReceiptViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() @@ -27,6 +34,8 @@ class MainActivity : ComponentActivity() { finish() }) } + SensorScaleUtils.startScale(autoScale = true){} + FoodModule.init(this) } override fun onDestroy() { 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 3976f41..87ea702 100644 --- a/app/src/main/java/com/sw/inbound/activity/GoodsListActivity.kt +++ b/app/src/main/java/com/sw/inbound/activity/GoodsListActivity.kt @@ -8,26 +8,32 @@ import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.camera.core.CameraSelector +import androidx.camera.core.Preview import androidx.camera.view.CameraController import androidx.camera.view.LifecycleCameraController import androidx.camera.view.PreviewView import androidx.core.graphics.toColorInt +import androidx.core.view.postDelayed +import androidx.core.view.updateLayoutParams import com.shuwei.intelligent.shelves.utils.ext.appendText import com.shuwei.intelligent.shelves.utils.ext.buildSpannableString import com.sw.inbound.GlobalData import com.sw.inbound.databinding.ActivityGoodsListBinding +import com.sw.inbound.databinding.LayoutCameraPreviewBinding import com.sw.inbound.databinding.ListItemReceiptGoodsBinding import com.sw.inbound.databinding.ListItemSelfProcurementBinding import com.sw.inbound.dialog.DropdownPopup import com.sw.inbound.dialog.GoodsRecognizeDialog import com.sw.inbound.dialog.GoodsSearchDialog import com.sw.inbound.dialog.NotReceiptWarnDialog +import com.sw.inbound.model.request.GoodsAddParam import com.sw.inbound.model.request.PurchaseWarehouseParam import com.sw.inbound.model.request.UploadInfo 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.FoodModule +import com.sw.inbound.sdk.SensorScaleUtils import com.sw.inbound.utils.BitmapCropper import com.sw.inbound.utils.BitmapSaver import com.sw.inbound.utils.ImageUtil @@ -67,6 +73,8 @@ abstract class GoodsListActivity : ComponentActivity() { abstract fun initView() abstract fun getWarehouseList(): List private lateinit var backPressedCallback: OnBackPressedCallback + private lateinit var previewView: PreviewView + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityGoodsListBinding.inflate(layoutInflater) @@ -76,7 +84,6 @@ abstract class GoodsListActivity : ComponentActivity() { isNewReceipt = intent.getBooleanExtra(IS_NEW_RECEIPT, false) id = intent.getStringExtra(ID) ?: "" supplierId = intent.getStringExtra(SUPPLIER_ID) ?: "" - FoodModule.init(this) initCamera() replaceIncludeContent() initRecyclerView() @@ -105,18 +112,34 @@ abstract class GoodsListActivity : ComponentActivity() { } dropdownPopup?.showAsDropDown(v) } + binding.tvPurchaseNo.setOnClickListener { + GoodsRecognizeDialog( + context = this, + list = mutableListOf() + ) { + GoodsSearchDialog(this).show() + }.show() + } binding.tvUserName.text = GlobalData.deviceId + binding.tvUserName.gone() binding.ivLogout.gone() + val previewBinding = + LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview) + previewView = previewBinding.previewView.also { + it.updateLayoutParams { + width = 1 + height = 1 + } + } + setPreviewController(previewView) - setPreviewController(binding.previewView) - - viewModel.startSensorScale() + //viewModel.startSensorScale() binding.root.postDelayed({ - viewModel.readWeight { + SensorScaleUtils.addWeightListener { weightCallback?.invoke(it) recognizeWeight(weight = it) } - }, 5 * 1000) + }, 3 * 1000) } private var weightCallback: ((weight: Int) -> Unit)? = null @@ -128,21 +151,23 @@ abstract class GoodsListActivity : ComponentActivity() { if (weight < 200) { return } - if (searchFoodEnable.not()) { + if (isShowRecognizeDialog.not()) { return } + isShowRecognizeDialog = false runOnUiThread { recognizeFood() } } + private val takePhotoSuccessCallback: (Uri) -> Unit = { uri -> + Thread { + searchFood(uri) + }.start() + } + fun recognizeFood() { - searchFoodEnable = false - takePhoto { uri -> - Thread { - searchFood(uri) - }.start() - } + takePhoto(takePhotoSuccessCallback) } private var dropdownPopup: DropdownPopup? = null @@ -156,7 +181,7 @@ abstract class GoodsListActivity : ComponentActivity() { } } - private var searchFoodEnable = true + private var isShowRecognizeDialog = true private fun searchFood(uri: Uri) { ImageUtil.uriToBitmap(this, uri)?.let { bitmap -> val newBmp = BitmapCropper.cropCenter( @@ -174,7 +199,6 @@ abstract class GoodsListActivity : ComponentActivity() { searchMultipleGoods(foodNameList) { goodsList -> //if (goodsList.size != foodNameList.size) { // toast("系统异常,请稍后重试!!!") - // searchFoodEnable = true // return@searchMultipleGoods //} handleGoodsResult( @@ -188,19 +212,25 @@ abstract class GoodsListActivity : ComponentActivity() { private fun handleGoodsResult(goodsList: MutableList) { runOnUiThread { if (goodsRecognizeDialog?.isShowing == true) { - searchFoodEnable = true + isShowRecognizeDialog = false return@runOnUiThread } - goodsRecognizeDialog = GoodsRecognizeDialog(this, goodsList, { - goodsRecognizeDialog = null - searchFoodEnable = true - }) { + goodsRecognizeDialog = GoodsRecognizeDialog( + context = this, + list = goodsList + ) { GoodsSearchDialog(this).show() }.apply { - if (isFinishing || isDestroyed) { - return@apply + setOnDismissListener { + goodsRecognizeDialog = null + isShowRecognizeDialog = true + } + setOnShowListener { + isShowRecognizeDialog = false + } + if (isFinishing.not() && isDestroyed.not()) { + show() } - show() } } } @@ -210,20 +240,27 @@ abstract class GoodsListActivity : ComponentActivity() { private var cameraController: LifecycleCameraController? = null private var photoCaptureHelper: PhotoCaptureHelper? = null private var isCameraReady = false - fun takePhoto(cameraCallback: (Uri) -> Unit) { + fun takePhoto(callback: (Uri) -> Unit) { cameraController?.let { if (photoCaptureHelper == null) { - photoCaptureHelper = PhotoCaptureHelper( - context = this, - cameraController = cameraController!!, - onSuccess = cameraCallback, - onError = { msg -> - //toast(msg) - } - ) + initCaptureHelper() } } - photoCaptureHelper?.takePhoto() + photoCaptureHelper?.let { + it.addSuccessCallback(callback) + it.takePhoto() + } + } + + fun initCaptureHelper() { + photoCaptureHelper = PhotoCaptureHelper( + context = this, + cameraController = cameraController!!, + onSuccess = {}, + onError = { msg -> + //toast(msg) + } + ) } fun setPreviewController(previewView: PreviewView?) { @@ -271,7 +308,6 @@ abstract class GoodsListActivity : ComponentActivity() { override fun onDestroy() { super.onDestroy() cameraController?.unbind() - viewModel.stopSensorScale() isCameraReady = false } @@ -407,4 +443,27 @@ abstract class GoodsListActivity : ComponentActivity() { } } + fun uploadImage(imageUri: Uri, callback: (String?) -> Unit) { + viewModel.uploadImage(imageUri) { imgUrl -> + if (imgUrl.isNullOrBlank()) { + callback(null) + return@uploadImage + } + callback(imgUrl) + } + } + + fun createNewGoods(imageUri: Uri, param: GoodsAddParam, callback: (Boolean) -> Unit) { + viewModel.uploadImage(imageUri) { imgUrl -> + if (imgUrl.isNullOrBlank()) { + toast("图片上传失败") + return@uploadImage + } + param.relativeUrl = imgUrl + viewModel.createNewGoods(param = param, callback = callback) + } + } + + private var preview: Preview? = null + } \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/adapter/RecognizeAdapter.kt b/app/src/main/java/com/sw/inbound/adapter/RecognizeAdapter.kt index bc0ad6d..62fd280 100644 --- a/app/src/main/java/com/sw/inbound/adapter/RecognizeAdapter.kt +++ b/app/src/main/java/com/sw/inbound/adapter/RecognizeAdapter.kt @@ -34,14 +34,15 @@ class RecognizeAdapter(var list: MutableList) : ) } it.ivRecImage.load(item!!.relativeUrl) - it.root.run { - setShapeDrawable( - strokeColor = if (item.isSelected) "#FF0033CC" else "#FFDCDCF0", - strokeWidth = 2.dp, - radius = 12.dp - ) - //setPadding(if (item.isSelected) 2.dp else 0) - } + //it.root.run { + // setShapeDrawable( + // //strokeColor = if (item.isSelected) "#FF0033CC" else "#FFDCDCF0", + // strokeColor = "#FFDCDCF0", + // strokeWidth = 2.dp, + // radius = 12.dp + // ) + // //setPadding(if (item.isSelected) 2.dp else 0) + //} } } diff --git a/app/src/main/java/com/sw/inbound/dialog/AddGoodsDialog.kt b/app/src/main/java/com/sw/inbound/dialog/AddGoodsDialog.kt index e77fe0a..d252ade 100644 --- a/app/src/main/java/com/sw/inbound/dialog/AddGoodsDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/AddGoodsDialog.kt @@ -1,146 +1,249 @@ -//package com.sw.inbound.dialog -// -//import android.content.Context -//import android.graphics.SurfaceTexture -//import android.view.LayoutInflater -//import android.view.TextureView -//import android.view.View -//import android.widget.FrameLayout -//import android.widget.TextView -//import androidx.compose.ui.text.style.LineHeightStyle -//import com.sw.inbound.GlobalData -//import com.sw.inbound.activity.GoodsListActivity -//import com.sw.inbound.databinding.DialogAddGoodsBinding -//import com.sw.inbound.model.response.DictType -//import com.sw.inbound.utils.ext.dp -//import com.sw.inbound.utils.ext.gone -//import com.sw.inbound.utils.ext.toast -//import com.sw.inbound.utils.ext.visible -//import kotlin.math.min -// -//class AddGoodsDialog( -// context: Context, -// var cancelBlock: () -> Unit = {}, -// var confirmBlock: () -> Unit = {}, -//) : BaseDialog(context) { -// -// private lateinit var binding: DialogAddGoodsBinding -// override fun getRootView(): View { -// val inflater = LayoutInflater.from(context) -// binding = DialogAddGoodsBinding.inflate(inflater) -// return binding.root -// } -// -// private var dropdownPopup: DropdownPopup? = null -// -// private val dropdownList: MutableList = mutableListOf() -// private var activity: GoodsListActivity? = null -// override fun initView() { -// activity = getReceiptActivity() -// binding.previewView.let { -// it.visible() -// activity?.setPreviewController(it) -// activity?.setPreviewController(it) -//// val textureView = it.getChildAt(0) as? TextureView -//// textureView?.surfaceTextureListener = object : TextureView.SurfaceTextureListener { -//// override fun onSurfaceTextureAvailable( -//// surface: SurfaceTexture, -//// width: Int, -//// height: Int -//// ) { -//// it.visible() -//// activity?.setPreviewController(it) -//// } -//// -//// override fun onSurfaceTextureSizeChanged( -//// surface: SurfaceTexture, -//// width: Int, -//// height: Int -//// ) { -//// } -//// -//// override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { -//// return true -//// } -//// -//// override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { -//// } -//// } -// } -// binding.ivGoodsImage.gone() -// binding.btnClose.setOnClickListener { -// cancelBlock() -// dismiss() -// } -// binding.btnConfirm.setOnClickListener { -// confirmBlock() -// dismiss() -// } -// binding.btnStartPreview.setOnClickListener { -// binding.ivGoodsImage.gone() -// } -// binding.btnTakePhoto.setOnClickListener { -// activity?.takePhoto { uri -> -// binding.ivGoodsImage.setImageURI(uri) -// binding.ivGoodsImage.visible() -// } -// } -// showDropDownList( -// binding.flProcurementUnit, -// textView = binding.tvProcurementUnit, -// list = GlobalData.unitTypeList, -// height = 213.dp -// ) +package com.sw.inbound.dialog + +import android.content.Context +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.TextView +import androidx.camera.view.PreviewView +import androidx.core.view.updateLayoutParams +import coil.load +import com.sw.inbound.GlobalData +import com.sw.inbound.activity.GoodsListActivity +import com.sw.inbound.databinding.DialogAddGoodsBinding +import com.sw.inbound.databinding.LayoutCameraPreviewBinding +import com.sw.inbound.ext.toSafeBigDecimal +import com.sw.inbound.ext.toSafeFloat +import com.sw.inbound.model.request.GoodsAddParam +import com.sw.inbound.model.response.DictType +import com.sw.inbound.utils.ext.dp +import com.sw.inbound.utils.ext.gone +import com.sw.inbound.utils.ext.toast +import com.sw.inbound.utils.ext.visible +import kotlin.math.min + +class AddGoodsDialog( + context: Context, + var cancelBlock: () -> Unit = {}, + var confirmBlock: (String) -> Unit = {}, +) : BaseDialog(context) { + + private lateinit var binding: DialogAddGoodsBinding + override fun getRootView(): View { + val inflater = LayoutInflater.from(context) + binding = DialogAddGoodsBinding.inflate(inflater) + return binding.root + } + + private var dropdownPopup: DropdownPopup? = null + + private val dropdownList: MutableList = mutableListOf() + private var activity: GoodsListActivity? = null + + override fun show() { + super.show() + + window?.decorView?.postDelayed({ + activity?.setPreviewController(previewView) + }, 600) + } + + private val takePhotoCallback: (Uri) -> Unit = { uri -> + binding.flCameraPreview.gone() + imageUri = uri + binding.cvImageBg.visible() + binding.ivGoodsImage.load(uri) + } + + private lateinit var previewView: PreviewView + override fun initView() { + activity = getReceiptActivity() + val previewBinding = + LayoutCameraPreviewBinding.inflate(LayoutInflater.from(context), binding.flCameraPreview) + previewBinding.previewView.let { + it.updateLayoutParams { + width = 428.dp + height = 320.dp + } + previewView = it + } +// binding.flCameraPreview.removeAllViews() +// binding.flCameraPreview.addView(previewView) + binding.flCameraPreview.visible() + binding.cvImageBg.gone() + binding.btnClose.setOnClickListener { + cancelBlock() + dismiss() + } + binding.btnConfirm.setOnClickListener { + submit() + } + binding.btnStartPreview.setOnClickListener { + binding.cvImageBg.gone() + binding.flCameraPreview.visible() + } + binding.btnTakePhoto.setOnClickListener { + activity?.takePhoto(takePhotoCallback) + } + binding.btnUploadImg.gone() + binding.btnUploadImg.setOnClickListener { + uploadImage() + } + binding.tvGoodsType.let { tv -> + tv.text = "原材料" + tv.tag = GlobalData.goodsTypeList.firstOrNull { it.value == "原材料" }?.id + ?: "1980098911276191746" + } + showDropDownList( + binding.flProcurementUnit, + textView = binding.tvProcurementUnit, + list = GlobalData.unitTypeList, + height = 213.dp + ) // showDropDownList( // binding.flGoodsType, // textView = binding.tvGoodsType, // list = GlobalData.goodsTypeList, // ) -// showDropDownList( -// binding.flStorageType, -// textView = binding.tvStorageType, -// list = GlobalData.storageTypeList, -// ) -// showDropDownList( -// dropdownView = binding.flWarehouseUnit, -// textView = binding.tvWarehouseUnit, -// list = GlobalData.unitTypeList -// ) { -// binding.tvSelectUseUnit.text = it.value -// } -// } -// -// private fun showDropDownList( -// dropdownView: FrameLayout, -// textView: TextView, -// list: List, -// height: Int = 355.dp, -// block:(DictType)-> Unit= {} -// ) { -// dropdownView.setOnClickListener { -// dropdownList.clear() -// val tempList = -// list.filter { it.id.isNullOrBlank().not() && it.value.isNullOrBlank().not() } -// if (tempList.isEmpty()) { -// context.toast("暂无下拉数据") -// return@setOnClickListener -// } -// dropdownList.addAll(tempList) -// val realHeight = min(height, dropdownList.size * 71.dp) -// dropdownPopup = DropdownPopup( -// context = context, -// list = dropdownList, -// popWidth = dropdownView.width, -// popHeight = realHeight -// ) { -// textView.text = it.value -// block(it) -// } -// dropdownPopup?.showAsDropDown(dropdownView) -// dropdownPopup?.setOnDismissListener { -// dropdownPopup = null -// } -// } -// } -// -//} \ No newline at end of file + showDropDownList( + binding.flStorageType, + textView = binding.tvStorageType, + list = GlobalData.storageTypeList, + ) + showDropDownList( + dropdownView = binding.flWarehouseUnit, + textView = binding.tvWarehouseUnit, + list = GlobalData.unitTypeList + ) { + binding.tvSelectUseUnit.text = it.value + binding.tvSelectProcurementUnit.text = it.value + } + } + + private fun showDropDownList( + dropdownView: FrameLayout, + textView: TextView, + list: List, + height: Int = 355.dp, + block: (DictType) -> Unit = {} + ) { + dropdownView.setOnClickListener { + dropdownList.clear() + val tempList = + list.filter { it.id.isNullOrBlank().not() && it.value.isNullOrBlank().not() } + if (tempList.isEmpty()) { + context.toast("暂无下拉数据") + return@setOnClickListener + } + dropdownList.addAll(tempList) + val realHeight = min(height, dropdownList.size * 71.dp) + dropdownPopup = DropdownPopup( + context = context, + list = dropdownList, + popWidth = dropdownView.width, + popHeight = realHeight + ) { + textView.run { + text = it.value + tag = it.id + } + block(it) + } + dropdownPopup?.showAsDropDown(dropdownView) + dropdownPopup?.setOnDismissListener { + dropdownPopup = null + } + } + } + + private fun submit() { + if (imageUri == null) { + context.toast("请采集图片") + return + } + val goodsName = binding.etGoodsName.text.trim().toString() + if (goodsName.isBlank()) { + context.toast("请输入物品名称") + return + } + val netRateText = binding.etNetMaterialRate.text.trim().toString() + if (netRateText.isBlank()) { + context.toast("请输入净材率") + return + } + val netRate = (netRateText.toFloatOrNull() ?: 0f) / 100f + val goodsType = binding.tvGoodsType.tag.toString() + if (goodsType.isBlank()) { + context.toast("请录入物品类型") + return + } + val storageType = binding.tvStorageType.tag.toString() + if (storageType.isBlank()) { + context.toast("请录入存储方式") + return + } + val warehouseUnit = binding.tvWarehouseUnit.tag.toString() + if (warehouseUnit.isBlank()) { + context.toast("请录入库存单位") + return + } + val purchaseUnit = binding.tvProcurementUnit.tag.toString() + if (purchaseUnit.isBlank()) { + context.toast("请录采购单位") + return + } + val costValue = binding.etUseUnit.text.trim().toString() + if (costValue.isBlank()) { + context.toast("请输入物品名称") + return + } + val goodsCount = binding.etGoodsCount.text.trim().toString() + if (costValue.isBlank()) { + context.toast("请输入采购数量") + return + } + val goodsPrice = binding.etGoodsPrice.text.trim().toString() + if (goodsPrice.isBlank()) { + context.toast("请输入采购单价") + return + } + val param = GoodsAddParam( + goodName = goodsName, + netRate = netRate, + goodType = goodsType, + storageType = storageType, + unitId = warehouseUnit, + consumeValue = costValue, + purchaseUnit = purchaseUnit, + purchaseValue = goodsCount.toSafeFloat(), + purchasePrice = goodsPrice.toSafeBigDecimal(), + ) + activity?.createNewGoods(imageUri!!, param) { + if (it.not()) { + context.toast("新增物品失败") + return@createNewGoods + } + context.toast("新增物品成功") + confirmBlock(goodsName) + dismiss() + } + } + + private var imageUri: Uri? = null + + private fun uploadImage() { + if (imageUri == null) { + context.toast("请采集图片") + return + } + activity?.uploadImage(imageUri!!) { imgUrl -> + if (imgUrl.isNullOrBlank()) { + context.toast("图片上传失败") + return@uploadImage + } + context.toast(imgUrl) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/dialog/GoodsRecognizeDialog.kt b/app/src/main/java/com/sw/inbound/dialog/GoodsRecognizeDialog.kt index b0b283e..205bb7f 100644 --- a/app/src/main/java/com/sw/inbound/dialog/GoodsRecognizeDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/GoodsRecognizeDialog.kt @@ -15,7 +15,6 @@ import com.sw.inbound.model.response.SearchGoodsInfo class GoodsRecognizeDialog( context: Context, var list: MutableList, - var dismissDialog: () -> Unit, var findGoods: () -> Unit ) : BaseDialog(context) { @@ -27,11 +26,6 @@ class GoodsRecognizeDialog( return binding.root } - override fun dismiss() { - dismissDialog() - super.dismiss() - } - private val recognizeAdapter by lazy { RecognizeAdapter(list).apply { isStateViewEnable = true diff --git a/app/src/main/java/com/sw/inbound/dialog/GoodsSearchDialog.kt b/app/src/main/java/com/sw/inbound/dialog/GoodsSearchDialog.kt index 4e0da1c..48bd29d 100644 --- a/app/src/main/java/com/sw/inbound/dialog/GoodsSearchDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/GoodsSearchDialog.kt @@ -38,9 +38,17 @@ class GoodsSearchDialog( searchGoods() } } -// binding.btnAddGoods.setOnClickListener { -// AddGoodsDialog(context).show() -// } + binding.btnAddGoods.setOnClickListener { + AddGoodsDialog( + context = context, + cancelBlock = {}, + confirmBlock = { goodsName-> + pageNo = 1 + binding.etInputGoods.setText(goodsName) + searchGoods() + } + ).show() + } binding.btnConfirm.setOnClickListener { if (list.isEmpty()) { context.toast("请搜索物品") 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 ac4912f..cc94e94 100644 --- a/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt @@ -80,6 +80,7 @@ class GoodsStoreDialog( text = it.value tag = it.id } + binding.tvCountUnit.text = it.value } } dropdownPopup?.showAsDropDown(v) @@ -166,22 +167,22 @@ class GoodsStoreDialog( private fun confirmInputData() { val unit = binding.tvProcurementUnit.text.trim().toString() if (unit.isBlank()) { - context.toast("请选择单位") + context.toast("请选择入库单位") return } val countText = binding.etProcurementCount.text.trim().toString() if (countText.isBlank()) { - context.toast("请录入数量") - return - } - val priceText = binding.tvProcurementPrice.text.trim().toString() - if (priceText.isBlank()) { - context.toast("请录入单价") + context.toast("请录入入库数量") return } val amountText = binding.etProcurementAmount.text.trim().toString() if (amountText.isBlank()) { - context.toast("请录入金额") + context.toast("请录入入库金额") + return + } + val priceText = binding.tvProcurementPrice.text.trim().toString() + if (priceText.isBlank()) { + context.toast("请录入入库单价") return } confirmBlock( diff --git a/app/src/main/java/com/sw/inbound/model/request/GoodsAddParam.kt b/app/src/main/java/com/sw/inbound/model/request/GoodsAddParam.kt index ed9e9f5..610afff 100644 --- a/app/src/main/java/com/sw/inbound/model/request/GoodsAddParam.kt +++ b/app/src/main/java/com/sw/inbound/model/request/GoodsAddParam.kt @@ -27,7 +27,7 @@ data class GoodsAddParam( /** * 存储方式 */ - var storageType: Int? = null, + var storageType: String? = null, /** * 净材率 @@ -37,18 +37,18 @@ data class GoodsAddParam( /** * 库存单位id */ - var unitId: Int? = null, + var unitId: String? = null, /** * 采购单位 */ - var purchaseUnit: Int = 0, + var purchaseUnit: String? = null, /** * 单位转换值 */ var purchaseValue: Float = 0F, - var purchaseValueStr: String = "", + //var purchaseValueStr: String = "", /** * 采购单价 @@ -119,7 +119,7 @@ data class GoodsAddParam( val purchaseUnitStr: String get() { - if (purchaseUnit == 0) { + if (purchaseUnit == null) { return "" } return GlobalData.unitTypeList.find { it.id == purchaseUnit.toString() }?.value ?: "" @@ -131,16 +131,16 @@ data class GoodsAddParam( if (goodType.isNullOrBlank() || goodType == "0") { return "请选择物品类型" } - if (storageType == null || storageType == 0) { + if (storageType.isNullOrBlank()) { return "请选择存储方式" } if (netRate == null || netRate == 0f) { return "请录入净材率" } - if (unitId == null || unitId == 0) { + if (unitId.isNullOrBlank()) { return "请选择库存单位" } - if (purchaseUnit == null || purchaseUnit == 0) { + if (purchaseUnit.isNullOrBlank()) { return "请选择采购单位" } if (purchaseValue == null || purchaseValue == 0f) { diff --git a/app/src/main/java/com/sw/inbound/model/response/ApiResponse.kt b/app/src/main/java/com/sw/inbound/model/response/ApiResponse.kt index c795fef..bf322ec 100644 --- a/app/src/main/java/com/sw/inbound/model/response/ApiResponse.kt +++ b/app/src/main/java/com/sw/inbound/model/response/ApiResponse.kt @@ -15,7 +15,7 @@ data class ApiResponse( val msg: String? = "", val data: T? = null ) { - fun isSuccess(): Boolean = code == "00000" + fun isSuccess(): Boolean = ("00000" == code) val result: T? get() = data diff --git a/app/src/main/java/com/sw/inbound/network/api/ApiService.kt b/app/src/main/java/com/sw/inbound/network/api/ApiService.kt index 3d2f049..529b24d 100644 --- a/app/src/main/java/com/sw/inbound/network/api/ApiService.kt +++ b/app/src/main/java/com/sw/inbound/network/api/ApiService.kt @@ -139,11 +139,19 @@ interface ApiService { /** * 上传图片 */ +// @Multipart +// @POST//("sys/common/upload") +// suspend fun uploadImage( +// @Url url: String = "${GlobalData.appBaseUrl}${apiPrefix}/sys/common/upload", +// @Part("code") code: RequestBody, +// @Part file: MultipartBody.Part, +// ): ApiResponse + @Multipart @POST//("sys/common/upload") suspend fun uploadImage( - @Url url: String = "${GlobalData.appBaseUrl}${apiPrefix}/sys/common/upload", - @Part("code") code: RequestBody, + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/upload", + //@Part("code") code: RequestBody, @Part file: MultipartBody.Part, ): ApiResponse @@ -166,7 +174,7 @@ interface ApiService { suspend fun selfPurchaseGoodsAdd( @Url url: String = "${GlobalData.appBaseUrl}${apiPrefix}/pad/goodsAdd", @Body goodsAddParam: GoodsAddParam, - ): ApiResponse + ): ApiResponse /** * 添加物品采购单位 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 a52972a..5fb8c36 100644 --- a/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt +++ b/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt @@ -30,7 +30,7 @@ object FoodModule { val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f) fun init(context: Context) { - Thread{ + Thread { module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt")) box = ObjectBox.boxStore.boxFor(Food::class) // if (box.all.isNotEmpty()) { @@ -60,24 +60,30 @@ object FoodModule { return outputTensor.dataAsFloatArray } - fun queryFood(uri: Uri, queryCount:Int = 15): List? { + fun queryFood(uri: Uri, queryCount: Int = 15): List? { return uri2FloatArray(uri)?.let { queryFood(it, queryCount) } } - fun queryFood(bitmap: Bitmap, queryCount:Int = 15): List { - 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 - ) + + fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List { + 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 + ) val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor() val floatArray = outputTensor.dataAsFloatArray - return queryFood(floatArray,queryCount) + if (bitmap.isRecycled.not()) { + bitmap.recycle() + } + return queryFood(floatArray, queryCount) } - fun queryFood(floatArray: FloatArray, queryCount:Int = 15): List { - val query: Query = box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() + 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() diff --git a/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt b/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt index 6a6413b..4f4160d 100644 --- a/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt +++ b/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt @@ -113,17 +113,18 @@ class RemoteRepository @Inject constructor( suspend fun uploadImage(uri: Uri): ApiResponse { Timber.d("uploadImage uri = $uri") return safeApiCall { - val codeRequestBody = RequestBody.create( - MultipartBody.FORM, - "4" - ) + //val codeRequestBody = RequestBody.create( + // MultipartBody.FORM, + // "4" + //) val part = FileUtils.genRequestPart(context = ContextUtils.getAppContext(), imageUri = uri) if (part == null) { ApiResponse(code = "-1", msg = "解析图片失败") } else { - apiService.uploadImage(code = codeRequestBody, file = part) + //apiService.uploadImage(code = codeRequestBody, file = part) + apiService.uploadImage(file = part) } } } @@ -164,7 +165,7 @@ class RemoteRepository @Inject constructor( /** * 自采购-添加商品 */ - suspend fun selfPurchaseGoodsAdd(goodsAddParam: GoodsAddParam): ApiResponse { + suspend fun selfPurchaseGoodsAdd(goodsAddParam: GoodsAddParam): ApiResponse { return safeApiCall { apiService.selfPurchaseGoodsAdd(goodsAddParam = goodsAddParam) } diff --git a/app/src/main/java/com/sw/inbound/sdk/SensorScaleUtils.kt b/app/src/main/java/com/sw/inbound/sdk/SensorScaleUtils.kt index b926b18..3942a89 100644 --- a/app/src/main/java/com/sw/inbound/sdk/SensorScaleUtils.kt +++ b/app/src/main/java/com/sw/inbound/sdk/SensorScaleUtils.kt @@ -32,6 +32,8 @@ object SensorScaleUtils { SensorScale.STATE_OVER_WEIGHT -> "量程溢出" else -> "未知" } + + weightCallback?.invoke((value * 1000).toInt()) //Timber.d("readWeight state = ${stateStr}, weight = $value") // 只使用稳定值 // if (state == SensorScale.STATE_STABLE) { @@ -52,6 +54,11 @@ object SensorScaleUtils { }) } + private var weightCallback: ((Int) -> Unit)? = null + fun addWeightListener(callback: (Int) -> Unit) { + weightCallback = callback + } + /** * 开启称重 * @param autoScale 是否开启自动读取 diff --git a/app/src/main/java/com/sw/inbound/utils/MultiClickDetector.kt b/app/src/main/java/com/sw/inbound/utils/MultiClickDetector.kt new file mode 100644 index 0000000..3863e66 --- /dev/null +++ b/app/src/main/java/com/sw/inbound/utils/MultiClickDetector.kt @@ -0,0 +1,49 @@ +package com.sw.inbound.utils + +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.view.View +import java.util.Arrays + +class MultiClickDetector(private val targetCount: Int = 10, + private val intervalMs: Long = 50) { + private var clickCount = 0 + private val clickTimestamps = LongArray(targetCount) + private val handler = Handler(Looper.getMainLooper()) + private lateinit var callback: () -> Unit + + fun setOnMultiClickListener(view: View, action: () -> Unit) { + callback = action + view.setOnClickListener { + System.arraycopy(clickTimestamps, 1, clickTimestamps, 0, targetCount - 1) + clickTimestamps[targetCount - 1] = SystemClock.uptimeMillis() + + if (clickTimestamps[0] >= SystemClock.uptimeMillis() - intervalMs) { + callback.invoke() + resetCount() + } + } + } + + // 使用Handler的延迟检测方案 + fun setOnDelayedMultiClickListener(view: View, action: () -> Unit) { + callback = action + view.setOnClickListener { + clickCount++ + handler.removeCallbacks(resetTask) + handler.postDelayed(resetTask, intervalMs) + + if (clickCount >= targetCount) { + callback.invoke() + resetCount() + } + } + } + + private val resetTask = Runnable { resetCount() } + private fun resetCount() { + clickCount = 0 + Arrays.fill(clickTimestamps, 0L) + } +} diff --git a/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt b/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt index 66308b1..14fb926 100644 --- a/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt +++ b/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt @@ -25,6 +25,12 @@ class PhotoCaptureHelper( private val onSuccess: (Uri) -> Unit = {}, private val onError: (String) -> Unit = {} ) { + private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf() + fun addSuccessCallback(callback:(Uri) -> Unit) { + if (callbackList.contains(callback).not()) { + callbackList.add(callback) + } + } /** * 拍照方法 @@ -56,6 +62,9 @@ class PhotoCaptureHelper( val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile) Timber.d("照片保存成功: $photoUri") onSuccess(photoUri) + callbackList.forEach { + it(photoUri) + } } override fun onError(exception: ImageCaptureException) { diff --git a/app/src/main/java/com/sw/inbound/viewmodel/BaseViewModel.kt b/app/src/main/java/com/sw/inbound/viewmodel/BaseViewModel.kt index 42fa81a..2e72f72 100644 --- a/app/src/main/java/com/sw/inbound/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/com/sw/inbound/viewmodel/BaseViewModel.kt @@ -11,7 +11,6 @@ import com.sw.inbound.model.response.GoodsInfo import com.sw.inbound.model.response.SearchGoodsInfo import com.sw.inbound.network.LoadingState import com.sw.inbound.repository.RemoteRepository -import com.sw.inbound.sdk.SensorScaleUtils import com.sw.inbound.utils.ToastUtils import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -110,18 +109,18 @@ abstract class BaseViewModel( ToastUtils.showToast(message) } - fun startSensorScale() { - Timber.d("开始称重") - SensorScaleUtils.startScale(callback = { weight -> - _weightInfo.value = weight - handleSensorScale(weight) - }) - } - - fun stopSensorScale() { - Timber.d("关闭称重") - SensorScaleUtils.stopContinuousRead() - } +// fun startSensorScale() { +// Timber.d("开始称重") +// SensorScaleUtils.startScale(callback = { weight -> +// _weightInfo.value = weight +// handleSensorScale(weight) +// }) +// } +// +// fun stopSensorScale() { +// Timber.d("关闭称重") +// SensorScaleUtils.stopContinuousRead() +// } abstract fun handleSensorScale(weight: Double) @@ -239,7 +238,7 @@ abstract class BaseViewModel( response.result ?.filter { it.id.isNullOrBlank().not() && it.title.isNullOrBlank().not() } ?.forEach { - list.add(DictType(id = it.id, value = it.title)) + list.add(DictType(id = it.value, value = it.title)) } GlobalData.storageTypeList = list } diff --git a/app/src/main/java/com/sw/inbound/viewmodel/ReceiptViewModel.kt b/app/src/main/java/com/sw/inbound/viewmodel/ReceiptViewModel.kt index b6fe3f0..92c0560 100644 --- a/app/src/main/java/com/sw/inbound/viewmodel/ReceiptViewModel.kt +++ b/app/src/main/java/com/sw/inbound/viewmodel/ReceiptViewModel.kt @@ -1,10 +1,13 @@ package com.sw.inbound.viewmodel +import android.net.Uri +import android.util.Log import androidx.lifecycle.viewModelScope import com.sw.inbound.ext.toFormattedString import com.sw.inbound.ext.toSafeBigDecimal import com.sw.inbound.ext.toSafeDouble import com.sw.inbound.ext.toSafeFloat +import com.sw.inbound.model.request.GoodsAddParam import com.sw.inbound.model.request.PurchaseWarehouseParam import com.sw.inbound.model.request.UploadInfo import com.sw.inbound.model.response.DictType @@ -13,6 +16,7 @@ import com.sw.inbound.model.response.PurchaseInfo import com.sw.inbound.model.response.SearchGoodsInfo import com.sw.inbound.repository.RemoteRepository import com.sw.inbound.utils.ToastUtils +import com.sw.inbound.utils.ext.toJsonString import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -150,7 +154,7 @@ class ReceiptViewModel @Inject constructor( // _selectedItem.value = purchaseOrder // } - fun getReceiveDetail(id: String, action:(PurchaseInfo?)->Unit = {}) { + fun getReceiveDetail(id: String, action: (PurchaseInfo?) -> Unit = {}) { launchWithLoading { val response = repository.getReceiveDetail(id) if (parseResponse(response)) { @@ -245,12 +249,13 @@ class ReceiptViewModel @Inject constructor( _amountUserInput.value = boolean } - private var weightCallback:((Int)->Unit)?=null - fun readWeight(callback:(Int)->Unit) { - weightCallback = callback - } + //private var weightCallback: ((Int) -> Unit)? = null + //fun readWeight(callback: (Int) -> Unit) { + // weightCallback = callback + //} + override fun handleSensorScale(weight: Double) { - weightCallback?.invoke((weight*1000).toInt()) +// weightCallback?.invoke((weight * 1000).toInt()) // val currentItem = _selectedItem.value // // currentItem?.let { info -> @@ -346,4 +351,35 @@ class ReceiptViewModel @Inject constructor( callback(resp.data == true) } } + + fun uploadImage(imageUri: Uri, callback: (String?) -> Unit) { + launchWithLoading { + val resp = repository.uploadImage(imageUri) + if (!parseResponse(resp)) { + callback(null) + return@launchWithLoading + } + callback(resp.data) + } + } + + fun createNewGoods(imageUri: Uri? = null, param: GoodsAddParam, callback: (Boolean) -> Unit) { + launchWithLoading { + if (imageUri != null) { + val resp = repository.uploadImage(imageUri) + if (!parseResponse(resp)) { + callback(false) + return@launchWithLoading + } + param.relativeUrl = resp.data + } + Timber.tag("ReceiptViewModel").d("createNewGoods入参:${param.toJsonString()}") + val response = repository.selfPurchaseGoodsAdd(param) + if (!parseResponse(response)) { + callback(false) + return@launchWithLoading + } + callback(true) + } + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_selector_radius12_stroke2.xml b/app/src/main/res/drawable/bg_selector_radius12_stroke2.xml new file mode 100644 index 0000000..5ab2d9d --- /dev/null +++ b/app/src/main/res/drawable/bg_selector_radius12_stroke2.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_goods_list.xml b/app/src/main/res/layout/activity_goods_list.xml index 88f1d1b..6484268 100644 --- a/app/src/main/res/layout/activity_goods_list.xml +++ b/app/src/main/res/layout/activity_goods_list.xml @@ -56,6 +56,7 @@ android:paddingHorizontal="20dp"> - diff --git a/app/src/main/res/layout/dialog_add_goods.xml b/app/src/main/res/layout/dialog_add_goods.xml index e3da498..83c83c8 100644 --- a/app/src/main/res/layout/dialog_add_goods.xml +++ b/app/src/main/res/layout/dialog_add_goods.xml @@ -36,50 +36,60 @@ android:layout_width="430dp" android:layout_height="322dp" android:layout_marginTop="40dp" - android:background="@drawable/bg_white_radius10_stroke2_dash" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/llGoodsInfo"> + app:layout_constraintTop_toTopOf="@+id/llGoodsInfo" + android:background="@drawable/bg_white_radius10_stroke2_dash"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_gravity="center"/> + + - - - - - - +