自采入库、新增物品、上传图片、全部收货接口调试,其它逻辑优化

This commit is contained in:
2025-10-29 20:08:12 +08:00
parent 0611005d04
commit 7930ba0cd1
24 changed files with 651 additions and 306 deletions
@@ -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() {
@@ -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<DictType>
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<SearchGoodsInfo.Record>) {
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
}
@@ -34,14 +34,15 @@ class RecognizeAdapter(var list: MutableList<SearchGoodsInfo.Record>) :
)
}
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)
//}
}
}
@@ -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<DictType> = 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<DictType> = 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<DictType>,
// 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
// }
// }
// }
//
//}
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<DictType>,
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)
}
}
}
@@ -15,7 +15,6 @@ import com.sw.inbound.model.response.SearchGoodsInfo
class GoodsRecognizeDialog(
context: Context,
var list: MutableList<SearchGoodsInfo.Record>,
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
@@ -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("请搜索物品")
@@ -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(
@@ -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) {
@@ -15,7 +15,7 @@ data class ApiResponse<T>(
val msg: String? = "",
val data: T? = null
) {
fun isSuccess(): Boolean = code == "00000"
fun isSuccess(): Boolean = ("00000" == code)
val result: T?
get() = data
@@ -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<String>
@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<String>
@@ -166,7 +174,7 @@ interface ApiService {
suspend fun selfPurchaseGoodsAdd(
@Url url: String = "${GlobalData.appBaseUrl}${apiPrefix}/pad/goodsAdd",
@Body goodsAddParam: GoodsAddParam,
): ApiResponse<SearchGoodsInfo>
): ApiResponse<String>
/**
* 添加物品采购单位
@@ -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<String>? {
fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
return uri2FloatArray(uri)?.let {
queryFood(it, queryCount)
}
}
fun queryFood(bitmap: Bitmap, queryCount:Int = 15): List<String> {
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<String> {
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<String> {
val query: Query<Food> = box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
val query: Query<Food> =
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<String, Int>()
@@ -113,17 +113,18 @@ class RemoteRepository @Inject constructor(
suspend fun uploadImage(uri: Uri): ApiResponse<String> {
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<SearchGoodsInfo> {
suspend fun selfPurchaseGoodsAdd(goodsAddParam: GoodsAddParam): ApiResponse<String> {
return safeApiCall {
apiService.selfPurchaseGoodsAdd(goodsAddParam = goodsAddParam)
}
@@ -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 是否开启自动读取
@@ -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)
}
}
@@ -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) {
@@ -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
}
@@ -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)
}
}
}
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<solid android:color="@color/white"/>
<stroke android:color="#FFDCDCF0" android:width="2dp"/>
<corners android:radius="12dp"/>
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="@color/white"/>
<stroke android:color="#ff999999" android:width="2dp"/>
<corners android:radius="12dp"/>
</shape>
</item>
</selector>
@@ -56,6 +56,7 @@
android:paddingHorizontal="20dp">
<LinearLayout
android:id="@+id/layoutTop"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center_vertical"
@@ -68,12 +69,13 @@
android:textColor="#ff333333"
android:textSize="24sp"
android:textStyle="bold"
android:paddingVertical="8dp"
tools:text="采购单号:CG2506190526" />
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="2dp"
android:layout_height="2dp"
<FrameLayout
android:id="@+id/flCameraPreview"
android:layout_width="1dp"
android:layout_height="1dp"
android:layout_marginStart="5dp" />
<Space
@@ -129,6 +131,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
tools:listitem="@layout/list_item_receipt_goods"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="8" />
+62 -37
View File
@@ -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">
<LinearLayout
android:id="@+id/llStartPreview"
<!-- <LinearLayout-->
<!-- android:id="@+id/llStartPreview"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_gravity="center"-->
<!-- android:gravity="center"-->
<!-- android:orientation="vertical"-->
<!-- android:visibility="gone">-->
<!-- <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" />-->
<!-- </LinearLayout>-->
<FrameLayout
android:id="@+id/flCameraPreview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
android:layout_gravity="center"/>
<androidx.cardview.widget.CardView
android:id="@+id/cvImageBg"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="10dp"
android:layout_margin="3dp"
app:cardElevation="0dp">
<ImageView
android:layout_width="96dp"
android:layout_height="wrap_content"
android:id="@+id/ivGoodsImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
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" />
</LinearLayout>
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="428dp"
android:layout_height="320dp"
android:layout_gravity="center"
android:visibility="gone"/>
<ImageView
android:id="@+id/ivGoodsImage"
android:layout_width="428dp"
android:layout_height="320dp"
android:layout_gravity="center"
android:scaleType="centerCrop"
tools:ignore="ContentDescription" />
</androidx.cardview.widget.CardView>
</FrameLayout>
<Button
@@ -120,6 +130,20 @@
tools:ignore="UseCompatTextViewDrawableXml" />
</LinearLayout>
<Button
android:id="@+id/btnUploadImg"
android:layout_width="210dp"
android:layout_height="70dp"
android:layout_marginTop="39dp"
android:background="@drawable/bg_white_stroke_green_ripple"
android:text="上传图片"
android:textColor="#FF14CD71"
android:textSize="30sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@id/btnTakePhoto"
app:layout_constraintStart_toStartOf="@id/btnStartPreview"
app:layout_constraintTop_toBottomOf="@id/btnTakePhoto" />
<LinearLayout
android:id="@+id/llGoodsInfo"
android:layout_width="0dp"
@@ -242,7 +266,7 @@
android:id="@+id/flGoodsType"
android:layout_width="432dp"
android:layout_height="60dp"
android:background="@drawable/bg_white_radius10_stroke2">
android:background="@drawable/bg_gray_radius10">
<TextView
android:id="@+id/tvGoodsType"
@@ -253,7 +277,7 @@
android:layout_marginEnd="20dp"
android:ellipsize="end"
android:hint="请选择"
android:textColor="#ff141428"
android:textColor="#ff96a0aa"
android:textColorHint="#FF96A0AA"
android:textSize="24sp"
android:textStyle="bold"
@@ -265,7 +289,8 @@
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="20dp"
android:src="@mipmap/ic_triangle_down"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription"
android:visibility="invisible"/>
</FrameLayout>
<FrameLayout
+16 -16
View File
@@ -108,21 +108,20 @@
android:background="#FFDCDCF0"
app:layout_constraintBottom_toBottomOf="parent" />
<!-- <Button-->
<!-- android:id="@+id/btnAddGoods"-->
<!-- android:layout_width="240dp"-->
<!-- android:layout_height="80dp"-->
<!-- android:background="@drawable/bg_white_stroke_blue_ripple"-->
<!-- android:text="添加物品"-->
<!-- android:textColor="#FF0033CC"-->
<!-- android:textSize="30sp"-->
<!-- android:textStyle="bold"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toStartOf="@+id/btnConfirm"-->
<!-- app:layout_constraintHorizontal_chainStyle="packed"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="@id/dividerLine"-->
<!-- android:visibility="gone"/>-->
<Button
android:id="@+id/btnAddGoods"
android:layout_width="240dp"
android:layout_height="80dp"
android:background="@drawable/bg_white_stroke_blue_ripple"
android:text="添加物品"
android:textColor="#FF0033CC"
android:textSize="30sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnConfirm"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/dividerLine" />
<Button
android:id="@+id/btnConfirm"
@@ -133,9 +132,10 @@
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
android:layout_marginStart="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/btnAddGoods"
app:layout_constraintTop_toTopOf="@id/dividerLine" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -170,6 +170,7 @@
tools:ignore="Autofill,TextFields" />
<TextView
android:id="@+id/tvCountUnit"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="end|center_vertical"
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="428dp"
android:layout_height="320dp"
android:layout_gravity="center"/>
</merge>
@@ -4,6 +4,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:padding="2dp"
android:background="@drawable/bg_selector_radius12_stroke2"
xmlns:tools="http://schemas.android.com/tools">
<ImageView