初始代码提交
This commit is contained in:
@@ -0,0 +1,693 @@
|
||||
package com.sw.inbound.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.sw.inbound.R
|
||||
import com.sw.inbound.adapter.FoodCollectionAdapter
|
||||
import com.sw.inbound.adapter.GoodsSearchAdapter
|
||||
import com.sw.inbound.databinding.ActivityFoodCollectionBinding
|
||||
import com.sw.inbound.databinding.LayoutCameraPreviewBinding
|
||||
import com.sw.inbound.databinding.LayoutEmptySearchBinding
|
||||
import com.sw.inbound.dialog.CollectSearchDialog
|
||||
import com.sw.inbound.dialog.CustomDialog
|
||||
import com.sw.inbound.dialog.Loading
|
||||
import com.sw.inbound.model.response.SearchGoodsInfo
|
||||
import com.sw.inbound.objbox.Food
|
||||
import com.sw.inbound.objbox.FoodCollectionBean
|
||||
import com.sw.inbound.objbox.FoodModule
|
||||
import com.sw.inbound.objbox.ObjectBox
|
||||
import com.sw.inbound.utils.BitmapCropper
|
||||
import com.sw.inbound.utils.BitmapSaver
|
||||
import com.sw.inbound.utils.CameraUtils
|
||||
import com.sw.inbound.utils.ImageUploader
|
||||
import com.sw.inbound.utils.ImageUtil
|
||||
import com.sw.inbound.utils.ext.addOnActionSearchListener
|
||||
import com.sw.inbound.utils.ext.clickWithDebounce
|
||||
import com.sw.inbound.utils.ext.dp
|
||||
import com.sw.inbound.utils.ext.gone
|
||||
import com.sw.inbound.utils.ext.hideKeyboard
|
||||
import com.sw.inbound.utils.ext.invisible
|
||||
import com.sw.inbound.utils.ext.startActivity
|
||||
import com.sw.inbound.utils.ext.toObject
|
||||
import com.sw.inbound.utils.ext.toast
|
||||
import com.sw.inbound.utils.ext.visible
|
||||
import com.sw.inbound.viewmodel.ReceiptViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.kotlin.boxFor
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@AndroidEntryPoint
|
||||
class FoodCollectionActivity : ComponentActivity() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "FoodCollectionActivity"
|
||||
const val MAX_COUNT = 102
|
||||
val PAGE_SIZE = 30
|
||||
}
|
||||
|
||||
private var box: Box<Food>? = null
|
||||
private val collectList: MutableList<FoodCollectionBean> = mutableListOf<FoodCollectionBean>()
|
||||
private lateinit var binding: ActivityFoodCollectionBinding
|
||||
private lateinit var previewView: PreviewView
|
||||
|
||||
val viewModel: ReceiptViewModel by viewModels()
|
||||
|
||||
private val cameraUtils: CameraUtils by lazy {
|
||||
CameraUtils(this)
|
||||
}
|
||||
private val collectionAdapter: FoodCollectionAdapter by lazy {
|
||||
FoodCollectionAdapter(collectList).apply {
|
||||
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
|
||||
collectList[position].let {
|
||||
it.imageFile = null
|
||||
it.imageVector = null
|
||||
it.bitmap = null
|
||||
it.isFinish = false
|
||||
it.isShowCamera = true
|
||||
it.uploadSuccess = false
|
||||
}
|
||||
collectionAdapter.notifyItemChanged(position)
|
||||
// val count = collectList.count { it.imageFile != null && it.uploadSuccess.not() }
|
||||
// binding.btnUploadImage.text = "待上传图片${count}张"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
cameraUtils.bind()
|
||||
binding.llCameraFlag.run {
|
||||
visible()
|
||||
postDelayed({
|
||||
gone()
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
cameraUtils.unbind()
|
||||
binding.llCameraFlag.visible()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityFoodCollectionBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
cameraUtils.initCamera()
|
||||
binding.root.setOnClickListener {
|
||||
it.hideKeyboard()
|
||||
}
|
||||
val previewBinding =
|
||||
LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
|
||||
previewView = previewBinding.previewView.also {
|
||||
it.updateLayoutParams {
|
||||
width = 420.dp
|
||||
height = 420.dp
|
||||
}
|
||||
}
|
||||
cameraUtils.setPreviewController(previewView)
|
||||
binding.btnBack.setOnClickListener { finish() }
|
||||
binding.rvFoodCollection.let {
|
||||
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
|
||||
it.adapter = collectionAdapter
|
||||
}
|
||||
|
||||
repeat(MAX_COUNT) {
|
||||
collectList.add(FoodCollectionBean(isShowCamera = true))
|
||||
}
|
||||
collectionAdapter.notifyDataSetChanged()
|
||||
|
||||
binding.ivGoodsSearch.setOnClickListener { searchGoods() }
|
||||
binding.etGoodsInput.let { v ->
|
||||
v.addOnActionSearchListener {
|
||||
searchGoods()
|
||||
}
|
||||
}
|
||||
// binding.btnUploadImage.setOnClickListener {
|
||||
// val count = collectList.count { it.imageFile!=null && it.uploadSuccess.not() }
|
||||
// if (count == 0) {
|
||||
// toast("已全部上传成功")
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
// uploadAllImage()
|
||||
// }
|
||||
binding.btnSave.setOnClickListener { _ ->
|
||||
if (clickIndex == -1) {
|
||||
toast("请选择物品名称")
|
||||
return@setOnClickListener
|
||||
}
|
||||
val count = collectList.count { it.imageVector != null }
|
||||
if (count == 0) {
|
||||
toast("请拍摄物品照片")
|
||||
return@setOnClickListener
|
||||
}
|
||||
// vectorThread()
|
||||
// uploadAllImage()
|
||||
|
||||
upload()
|
||||
}
|
||||
binding.btnTakePhoto.clickWithDebounce {
|
||||
takePhoto()
|
||||
}
|
||||
binding.btnCollectedGoods.setOnClickListener {
|
||||
CollectSearchDialog(this@FoodCollectionActivity).show()
|
||||
}
|
||||
binding.btnClearData.setOnClickListener { clearData() }
|
||||
binding.rvSearch.let {
|
||||
it.layoutManager = GridLayoutManager(this, 2, LinearLayoutManager.VERTICAL, false)
|
||||
it.adapter = searchAdapter
|
||||
}
|
||||
binding.refreshLayout.let {
|
||||
it.setEnableRefresh(true)
|
||||
it.setEnableLoadMore(false)
|
||||
it.setOnRefreshListener {
|
||||
pageNo = 1
|
||||
searchGoods()
|
||||
}
|
||||
it.setOnLoadMoreListener {
|
||||
searchGoods()
|
||||
}
|
||||
}
|
||||
|
||||
loadEmptyView()
|
||||
|
||||
binding.tvTitle.setOnClickListener {
|
||||
// startActivity<LocalImagePreviewActivity> { }
|
||||
|
||||
// val goodsImage = GoodsImage(goodsId = "1993493094888140802", goodsName = "多宝鱼", startTime = 1764120531856L, endTime = 1764121842825L)
|
||||
// uploadImage(goodsImage)
|
||||
|
||||
// val goodsImage = GoodsImage(goodsId = "1986366073649287170", goodsName = "黑鱼(整条鱼)", startTime = 1764122703093L, endTime = 1764123156022L)
|
||||
// uploadImage(goodsImage)
|
||||
|
||||
// val goodsImage = GoodsImage(goodsId = "1986699063868801025", goodsName = "鸡腿肉", startTime = 1764123375841L, endTime = 1764124039162L)
|
||||
// uploadImage(goodsImage)
|
||||
}
|
||||
}
|
||||
|
||||
private var pageNo = 1
|
||||
private var clickIndex = -1
|
||||
|
||||
private val searchGoodsList: MutableList<SearchGoodsInfo.Record> = mutableListOf()
|
||||
private val searchAdapter by lazy {
|
||||
GoodsSearchAdapter(searchGoodsList).apply {
|
||||
isStateViewEnable = true
|
||||
setOnItemClickListener { adapter, view, position ->
|
||||
clickIndex = position
|
||||
if (list[position].isSelected) {
|
||||
return@setOnItemClickListener
|
||||
}
|
||||
list.forEach { item ->
|
||||
item.isSelected = false
|
||||
}
|
||||
list[position].isSelected = true
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishRefresh() {
|
||||
binding.refreshLayout.let {
|
||||
if (pageNo == 1) {
|
||||
it.finishRefresh(500)
|
||||
} else {
|
||||
it.finishLoadMore(500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun takePhoto() {
|
||||
val count = collectList.count { it.imageFile != null }
|
||||
if (count == MAX_COUNT) {
|
||||
toast("每次只允许保存${MAX_COUNT}条数据")
|
||||
return
|
||||
}
|
||||
cameraUtils.takePhoto(cameraCallback)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun clearData() {
|
||||
collectList.forEach {
|
||||
try {
|
||||
if (it.bitmap?.isRecycled?.not() == true) {
|
||||
it.bitmap?.recycle()
|
||||
}
|
||||
it.bitmap = null
|
||||
it.imageVector = null
|
||||
it.imageFile = null
|
||||
it.isShowCamera = true
|
||||
it.isFinish = false
|
||||
it.uploadSuccess = false
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
collectionAdapter.notifyDataSetChanged()
|
||||
|
||||
clickIndex = -1
|
||||
binding.etGoodsInput.setText("")
|
||||
searchGoodsList.clear()
|
||||
searchAdapter.notifyDataSetChanged()
|
||||
loadEmptyView()
|
||||
binding.ivFinish.invisible()
|
||||
// binding.btnUploadImage.text = "待上传图片${0}张"
|
||||
}
|
||||
|
||||
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||
val index = collectList.indexOfFirst { it.imageFile == null }
|
||||
if (index == -1) {
|
||||
toast("每次只允许保存${MAX_COUNT}条数据")
|
||||
rerurn@ cameraCallback
|
||||
}
|
||||
// toast("index=$index")
|
||||
try {
|
||||
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
|
||||
val cropBitmap = BitmapCropper.cropCenter(
|
||||
original = bitmap,
|
||||
targetWidth = 1000, targetHeight = 1300,
|
||||
offsetX = 30, offsetY = 100
|
||||
)
|
||||
initBox()
|
||||
val imageVector = try {
|
||||
FoodModule.bitmap2FloatArray(cropBitmap, false)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
toast("操作失败")
|
||||
return@let
|
||||
}
|
||||
val foodList = FoodModule.queryFoodNameScore(imageVector)
|
||||
val filterList =
|
||||
foodList.filter { it.score < FoodModule.BAG_RATE && it.name.contains("黑袋子") }
|
||||
if (filterList.isNotEmpty()) {
|
||||
toast("当前物品可能被袋子遮挡,请检查后重试")
|
||||
return@let
|
||||
}
|
||||
val file = BitmapSaver.saveToAppFilesDir(
|
||||
cropBitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||
)
|
||||
Timber.d("${this.javaClass.simpleName}-searchFood-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||
collectList[index].let {
|
||||
it.imageVector = imageVector
|
||||
it.bitmap = null
|
||||
it.imageFile = file
|
||||
it.isShowCamera = false
|
||||
it.isFinish = false
|
||||
it.uploadSuccess = false
|
||||
}
|
||||
collectionAdapter.notifyItemChanged(index)
|
||||
// val count = collectList.count { it.imageFile != null && it.uploadSuccess.not() }
|
||||
// binding.btnUploadImage.text = "待上传图片${count}张"
|
||||
if (cropBitmap.isRecycled.not()) {
|
||||
cropBitmap.recycle()
|
||||
}
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
toast("程序异常${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun vectorThread() {
|
||||
Loading.show(this)
|
||||
Thread {
|
||||
collectList
|
||||
//.filter { it.bitmap != null }
|
||||
// .filter { it.imageVector != null }
|
||||
.forEachIndexed { index, it ->
|
||||
//image2VectorTask(it.bitmap!!, index)
|
||||
it.imageVector?.let{ imageVector ->
|
||||
image2VectorTask(imageVector = imageVector, position = index)
|
||||
}
|
||||
}
|
||||
runOnUiThread {
|
||||
window.decorView.postDelayed({
|
||||
Loading.dismiss()
|
||||
toast("保存成功")
|
||||
}, 1000)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun image2VectorTask(
|
||||
bitmap: Bitmap? = null,
|
||||
imageVector: FloatArray? = null,
|
||||
position: Int
|
||||
) {
|
||||
try {
|
||||
initBox()
|
||||
// val imageVector = try {
|
||||
// FoodModule.bitmap2FloatArray(bitmap)
|
||||
// } catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// //toast("保存失败")
|
||||
// return
|
||||
// }
|
||||
val goods = searchGoodsList[clickIndex]
|
||||
val saveName = goods.goodsName + goods.goodsCode
|
||||
ObjectBox.boxStore.runInTx {
|
||||
box?.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector))
|
||||
}
|
||||
collectList[position].let {
|
||||
// it.imageVector = imageVector
|
||||
it.isFinish = true
|
||||
}
|
||||
runOnUiThread {
|
||||
collectionAdapter.notifyItemChanged(position)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun searchGoods() {
|
||||
val searchName = binding.etGoodsInput.text.toString().trim()
|
||||
if (searchName.isBlank()) {
|
||||
toast("请输入物品名称")
|
||||
finishRefresh()
|
||||
return
|
||||
}
|
||||
searchGoods(
|
||||
searchName = searchName,
|
||||
pageNo = pageNo
|
||||
) { records ->
|
||||
finishRefresh()
|
||||
binding.etGoodsInput.hideKeyboard()
|
||||
if (pageNo == 1 && records.isEmpty()) {
|
||||
searchGoodsList.clear()
|
||||
searchAdapter.notifyDataSetChanged()
|
||||
loadEmptyView()
|
||||
return@searchGoods
|
||||
}
|
||||
if (pageNo == 1) {
|
||||
searchGoodsList.clear()
|
||||
}
|
||||
searchGoodsList.addAll(records)
|
||||
val enableLoadMore = records.size >= PAGE_SIZE
|
||||
binding.refreshLayout.setEnableLoadMore(enableLoadMore)
|
||||
if (enableLoadMore) {
|
||||
pageNo++
|
||||
}
|
||||
binding.rvSearch.layoutManager =
|
||||
GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false)
|
||||
searchAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private var emptyBinding: LayoutEmptySearchBinding? = null
|
||||
private fun loadEmptyView() {
|
||||
binding.rvSearch.layoutManager =
|
||||
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||
if (emptyBinding == null) {
|
||||
emptyBinding = LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvSearch, false)
|
||||
}
|
||||
emptyBinding?.tvContent?.text = "请检查物品名称,或稍后重新搜索"
|
||||
emptyBinding?.root?.let { layout ->
|
||||
layout.setOnClickListener { layout.hideKeyboard() }
|
||||
searchAdapter.stateView = layout
|
||||
}
|
||||
}
|
||||
|
||||
fun searchGoods(
|
||||
searchName: String,
|
||||
pageNo: Int,
|
||||
pageSize: Int = PAGE_SIZE,
|
||||
callback: (List<SearchGoodsInfo.Record>) -> Unit
|
||||
) {
|
||||
runBlocking {
|
||||
val records = viewModel.searchGoodsInfoList2(
|
||||
goodsName = searchName,
|
||||
pageNo = pageNo,
|
||||
pageSize = pageSize
|
||||
)
|
||||
runOnUiThread {
|
||||
callback(records)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initBox() {
|
||||
if (box == null) {
|
||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
||||
}
|
||||
}
|
||||
|
||||
// private fun uploadAllImage() {
|
||||
// Loading.show(this)
|
||||
// val files = collectList.filter { it.imageFile!=null }.map { it.imageFile!! }
|
||||
// if (files.isEmpty()) {
|
||||
// return
|
||||
// }
|
||||
// val goods = searchGoodsList[clickIndex]
|
||||
// for (index in collectList.indices step 5) {
|
||||
// val end = if(index + 5 < collectList.size - 1) index + 5 else collectList.size - 1
|
||||
// val subList = collectList.subList(index, end)
|
||||
// val subFiles = subList.map { it.imageFile }
|
||||
// viewModel.uploadMultipleImages(goods.id!!, goods.goodsName!!, subFiles) {isSuccess->
|
||||
// Timber.tag(TAG).d("uploadMultipleImages: ${isSuccess}")
|
||||
// subList.filter { it.imageFile!=null }.forEach { it.uploadSuccess = isSuccess }
|
||||
//
|
||||
// val count = collectList.count { it.imageFile!=null && it.uploadSuccess.not() }
|
||||
// runOnUiThread {
|
||||
// binding.btnUploadImage.text = "待上传图片${count}张"
|
||||
// if (count == 0) {
|
||||
// Loading.dismiss()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
private fun upload() {
|
||||
lifecycleScope.launch {
|
||||
val totalFileCount = collectList.count { it.imageFile != null }
|
||||
showWaitingDialog2("图片上传中0/$totalFileCount")
|
||||
val goods = searchGoodsList[clickIndex]
|
||||
val params = HashMap<String, RequestBody>()
|
||||
params["goodsId"] = goods.id!!.toRequestBody()
|
||||
params["goodsName"] = goods.goodsName!!.toRequestBody()
|
||||
//params["foodVector"] = foodVector.toRequestBody()
|
||||
ImageUploader(
|
||||
totalList = collectList,
|
||||
uploadImage = { batch ->
|
||||
val files = batch.map { it.imageFile }
|
||||
viewModel.uploadMultipleImages(files = files, params)
|
||||
},
|
||||
onProgress = { count, batch ->
|
||||
runOnUiThread {
|
||||
showWaitingDialog2("图片上传中$count/$totalFileCount")
|
||||
batch.forEach {
|
||||
it.uploadSuccess = true
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = {
|
||||
runOnUiThread {
|
||||
hideWaitingDialog()
|
||||
toast("上传失败,请稍后重试")
|
||||
}
|
||||
},
|
||||
onComplete = {
|
||||
hideWaitingDialog()
|
||||
vectorThread()
|
||||
}
|
||||
).processUploads()
|
||||
}
|
||||
}
|
||||
|
||||
private val receiptViewModel: ReceiptViewModel by viewModels()
|
||||
|
||||
fun searchGoodsInfoList(goodsName: String, block: (List<SearchGoodsInfo.Record>) -> Unit) {
|
||||
runBlocking {
|
||||
val list = searchGoodsInfoList2(goodsName)
|
||||
block(list)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchGoodsInfoList2(
|
||||
goodsName: String,
|
||||
pageNo: Int = 1,
|
||||
pageSize: Int = 10
|
||||
): List<SearchGoodsInfo.Record> {
|
||||
return receiptViewModel.searchGoodsInfoList2(goodsName, pageNo, pageSize)
|
||||
}
|
||||
|
||||
private var mDialogWaiting: CustomDialog? = null
|
||||
|
||||
/**
|
||||
* 显示等待提示框
|
||||
*/
|
||||
fun showWaitingDialog(tip: String?): Dialog? {
|
||||
hideWaitingDialog()
|
||||
val view = View.inflate(this, R.layout.dialog_waiting, null)
|
||||
if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).text = tip
|
||||
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog)
|
||||
mDialogWaiting!!.show()
|
||||
mDialogWaiting!!.setCancelable(true)
|
||||
return mDialogWaiting
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏等待提示框
|
||||
*/
|
||||
fun hideWaitingDialog() {
|
||||
mDialogWaiting?.dismiss()
|
||||
mDialogWaiting = null
|
||||
}
|
||||
|
||||
fun showWaitingDialog2(tip: String?) {
|
||||
if (mDialogWaiting == null) {
|
||||
hideWaitingDialog()
|
||||
val view = View.inflate(this, R.layout.dialog_waiting, null)
|
||||
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog)
|
||||
mDialogWaiting!!.show()
|
||||
mDialogWaiting!!.setCancelable(true)
|
||||
}
|
||||
val contentView = mDialogWaiting?.findViewById<ViewGroup>(android.R.id.content)
|
||||
val tvTip = contentView?.findViewById<TextView>(R.id.tvTip)
|
||||
tvTip?.text = tip
|
||||
}
|
||||
|
||||
data class RespData(
|
||||
var code: String? = null,
|
||||
var data: String? = null,
|
||||
var msg: String? = null,
|
||||
)
|
||||
|
||||
|
||||
data class GoodsImage(
|
||||
var goodsId: String,
|
||||
var goodsName: String,
|
||||
var startTime: Long,
|
||||
var endTime: Long
|
||||
)
|
||||
//
|
||||
// val goodsImageList = mutableListOf<GoodsImage>().apply {
|
||||
// add(
|
||||
// GoodsImage(
|
||||
// goodsId = "1986353555971297282",
|
||||
// goodsName = "马铃薯[土豆、洋芋]",
|
||||
// startTime = 1763692477786L,
|
||||
// endTime = 1763693153825L
|
||||
// )
|
||||
// )
|
||||
// add(GoodsImage(
|
||||
// goodsId = "1986607476769869825",
|
||||
// goodsName = "辣椒(青辣椒,螺丝椒)",
|
||||
// startTime = 1763693182929L,
|
||||
// endTime = 1763693621664L
|
||||
// ))
|
||||
// add(GoodsImage(
|
||||
// goodsId = "1987880591453859842",
|
||||
// goodsName = "辣椒(红,小)",
|
||||
// startTime = 1763693683837L,
|
||||
// endTime = 1763694154521L
|
||||
// ))
|
||||
// add(GoodsImage(
|
||||
// goodsId = "1986699063868801025",
|
||||
// goodsName = "鸡腿肉",
|
||||
// startTime = 1763694221267L,
|
||||
// endTime = 1763695052924L
|
||||
// ))
|
||||
//
|
||||
// add(GoodsImage(
|
||||
// goodsId = "1986689570300809217",
|
||||
// goodsName = "猪腿肉",
|
||||
// startTime = 1763695212428L,
|
||||
// endTime = 1763695810578L
|
||||
// ))
|
||||
// add(GoodsImage(
|
||||
// goodsId = "1986673470079029249",
|
||||
// goodsName = "西兰花[绿菜花]",
|
||||
// startTime = 1763695924575L,
|
||||
// endTime = 1763696252669L
|
||||
// ))
|
||||
// add(GoodsImage(
|
||||
// goodsId = "1991473021965070338",
|
||||
// goodsName = "姜[黄姜]",
|
||||
// startTime = 1763696258001L,
|
||||
// endTime = 1763696294831L
|
||||
// ))
|
||||
// add(GoodsImage(
|
||||
// goodsId = "00000",
|
||||
// goodsName = "其它",
|
||||
// startTime = 1763696442577L,
|
||||
// endTime = 1763696990541L
|
||||
// ))
|
||||
// }
|
||||
|
||||
// private fun uploadImage(goodsImage: GoodsImage) {
|
||||
// Loading.show(this)
|
||||
// val cropFile = File(this.cacheDir, "crop")
|
||||
// val list = mutableListOf<File>()
|
||||
// val startTime = goodsImage.startTime
|
||||
// val endTime = goodsImage.endTime
|
||||
// cropFile.listFiles()?.forEach {
|
||||
// if (it.isDirectory) {
|
||||
// return@forEach
|
||||
// }
|
||||
// val start = "IMG_CROP_".length
|
||||
// val end = it.name.indexOf(".jpg")
|
||||
// val time = it.name.substring(start, end).toLong()
|
||||
// if (time in startTime..endTime) {
|
||||
// list.add(it)
|
||||
// }
|
||||
// if (list.size < 5) {
|
||||
// return@forEach
|
||||
// }
|
||||
// viewModel.uploadMultipleImages(
|
||||
// goodsId = goodsImage.goodsId,
|
||||
// goodsName = goodsImage.goodsName,
|
||||
// files = list
|
||||
// ) { isSuccess ->
|
||||
//// val resp = data?.toObject<RespData>()
|
||||
//// val isSuccess = resp?.code == "00000"
|
||||
// binding.ivFinish.run {
|
||||
// if (isSuccess) visible() else invisible()
|
||||
// }
|
||||
// }
|
||||
// list.clear()
|
||||
// }
|
||||
// if (list.isNotEmpty()) {
|
||||
// viewModel.uploadMultipleImages(
|
||||
// goodsId = goodsImage.goodsId,
|
||||
// goodsName = goodsImage.goodsName,
|
||||
// files = list
|
||||
// ) { isSuccess ->
|
||||
//// val resp = data?.toObject<RespData>()
|
||||
//// val isSuccess = resp?.code == "00000"
|
||||
// binding.ivFinish.run {
|
||||
// if (isSuccess) visible() else invisible()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Loading.dismiss()
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
package com.sw.inbound.activity
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.sw.inbound.utils.ext.appendText
|
||||
import com.sw.inbound.utils.ext.buildSpannableString
|
||||
import com.sw.inbound.GlobalData
|
||||
import com.sw.inbound.R
|
||||
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.DialogManager
|
||||
import com.sw.inbound.dialog.DropdownPopup
|
||||
import com.sw.inbound.dialog.GoodsRecognizeDialog
|
||||
import com.sw.inbound.dialog.GoodsSearchDialog
|
||||
import com.sw.inbound.dialog.Loading
|
||||
import com.sw.inbound.dialog.WarnDialog
|
||||
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.CameraUtils
|
||||
import com.sw.inbound.utils.ImageUtil
|
||||
import com.sw.inbound.utils.PreciseDelayHandler
|
||||
import com.sw.inbound.utils.ext.dp
|
||||
import com.sw.inbound.utils.ext.gone
|
||||
import com.sw.inbound.utils.ext.hideKeyboard
|
||||
import com.sw.inbound.utils.ext.roundedDecimalPlace
|
||||
import com.sw.inbound.utils.ext.toJsonString
|
||||
import com.sw.inbound.utils.ext.toast
|
||||
import com.sw.inbound.viewmodel.ReceiptViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
||||
typealias RecognizeCallback = (List<SearchGoodsInfo.Record>) -> Unit
|
||||
|
||||
@AndroidEntryPoint
|
||||
abstract class GoodsListActivity : ComponentActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GoodsListActivity"
|
||||
public const val ID = "id"
|
||||
public const val SUPPLIER_ID = "supplierId"
|
||||
|
||||
public const val IS_RECEIPT_PAGE = "isReceiptPage"
|
||||
public const val IS_NEW_RECEIPT = "isNewReceipt"
|
||||
|
||||
const val PAGE_SIZE = 10
|
||||
private const val DELAY_TIME: Long = 3 * 1000
|
||||
}
|
||||
|
||||
var isReceiptPage: Boolean = false
|
||||
var isNewReceipt: Boolean = false
|
||||
var id: String = ""
|
||||
var supplierId: String = ""
|
||||
lateinit var binding: ActivityGoodsListBinding
|
||||
val viewModel: ReceiptViewModel by viewModels()
|
||||
val goodsList: MutableList<GoodsInfo> = mutableListOf()
|
||||
|
||||
abstract fun initRecyclerView()
|
||||
abstract fun initView()
|
||||
abstract fun getWarehouseList(): List<DictType>
|
||||
private lateinit var backPressedCallback: OnBackPressedCallback
|
||||
private lateinit var previewView: PreviewView
|
||||
|
||||
private val cameraUtils: CameraUtils by lazy {
|
||||
CameraUtils(this@GoodsListActivity)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityGoodsListBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
initBackDispatcher()
|
||||
isReceiptPage = intent.getBooleanExtra(IS_RECEIPT_PAGE, true)
|
||||
isNewReceipt = intent.getBooleanExtra(IS_NEW_RECEIPT, false)
|
||||
id = intent.getStringExtra(ID) ?: ""
|
||||
supplierId = intent.getStringExtra(SUPPLIER_ID) ?: ""
|
||||
cameraUtils.initCamera()
|
||||
replaceIncludeContent()
|
||||
initRecyclerView()
|
||||
initView()
|
||||
binding.root.setOnClickListener {
|
||||
it.hideKeyboard()
|
||||
}
|
||||
binding.btnBack.setOnClickListener { handleBackEvent() }
|
||||
|
||||
binding.flWarehouseDropdown.setOnClickListener { v ->
|
||||
if (GlobalData.warehouseTypeList.isEmpty()) {
|
||||
toast("暂无仓库数据")
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (dropdownPopup == null) {
|
||||
dropdownList.clear()
|
||||
dropdownList.addAll(GlobalData.warehouseTypeList)
|
||||
dropdownPopup = DropdownPopup(
|
||||
context = this,
|
||||
list = dropdownList,
|
||||
popHeight = min(355.dp, dropdownList.size * 71.dp)
|
||||
) {
|
||||
binding.tvWareHouse.run {
|
||||
text = it.value
|
||||
tag = it.id
|
||||
}
|
||||
}.also {
|
||||
it.arrowImage = binding.ivDropdown
|
||||
it.bgLayout = binding.flWarehouseDropdown
|
||||
it.defLayoutBgResId = R.drawable.bg_white_radius10_stroke
|
||||
}
|
||||
}
|
||||
dropdownPopup?.showAsDropDown(v)
|
||||
}
|
||||
binding.tvPurchaseNo.setOnClickListener {
|
||||
handleGoodsResult()
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
cameraUtils.setPreviewController(previewView)
|
||||
//startTime = System.currentTimeMillis()
|
||||
binding.root.postDelayed({
|
||||
SensorScaleUtils.addWeightListener {
|
||||
runOnUiThread {
|
||||
try {
|
||||
weightCallback?.invoke(it)
|
||||
recognizeWeight(weight = it)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1 * 1000)
|
||||
}
|
||||
|
||||
private var weightCallback: ((weight: Int) -> Unit)? = null
|
||||
fun readWeightInfo(callback: (weight: Int) -> Unit) {
|
||||
weightCallback = callback
|
||||
}
|
||||
|
||||
private var lastWeight = 0
|
||||
private fun recognizeWeight(weight: Int) {
|
||||
Timber.tag(TAG).d("recognizeWeight,lastWeight=$lastWeight,weight=$weight")
|
||||
//if (this.lastWeight - weight >= 0) {
|
||||
// this.lastWeight = weight
|
||||
// //从秤上取物品中
|
||||
// return
|
||||
//}
|
||||
if (weight < 200) {
|
||||
if (abs(weight) < 10) {
|
||||
// val clazzName = DialogManager.getDialogList()
|
||||
// .map { it.javaClass.simpleName }
|
||||
// .joinToString(",")
|
||||
// Timber.tag(TAG).d(clazzName)
|
||||
if (DialogManager.getDialogList().size == 1 &&
|
||||
DialogManager.contains("GoodsRecognizeDialog") &&
|
||||
goodsRecognizeDialog?.isShowing == true
|
||||
) {
|
||||
goodsRecognizeDialog?.dismiss()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
this.lastWeight = weight
|
||||
if (DialogManager.hasShowDialog()) {
|
||||
return
|
||||
}
|
||||
// val checkResult = isFirstRecognize.not()
|
||||
// //&& System.currentTimeMillis() - startTime <= DELAY_TIME
|
||||
// Timber.tag(TAG).d("recognizeWeight,checkResult=$checkResult")
|
||||
// if (checkResult) {
|
||||
// //Timber.tag(TAG).d("recognizeWeight,等待中:${(System.currentTimeMillis() - startTime)/1000}")
|
||||
// Timber.tag(TAG).d("recognizeWeight, checkResult=true")
|
||||
// return
|
||||
// }
|
||||
Timber.tag(TAG)
|
||||
.d("recognizeWeight,recognizeFood:isShowRecognizeDialog=$isShowRecognizeDialog")
|
||||
recognizeFood()
|
||||
}
|
||||
|
||||
//var startTime = 0L
|
||||
var isFirstRecognize = true
|
||||
|
||||
private val takePhotoSuccessCallback: (Uri) -> Unit = { uri ->
|
||||
Thread {
|
||||
try {
|
||||
searchFood(uri)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private val delayHandler by lazy {
|
||||
PreciseDelayHandler()
|
||||
}
|
||||
|
||||
fun delayRecognizeFood() {
|
||||
if (abs(lastWeight) < 10) return
|
||||
delayHandler.precisePostDelayed(DELAY_TIME) {
|
||||
runOnUiThread {
|
||||
recognizeFood()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun recognizeFood() {
|
||||
if (isShowRecognizeDialog.not()) {
|
||||
if (goodsRecognizeDialog == null) {
|
||||
isShowRecognizeDialog = true
|
||||
}
|
||||
return
|
||||
}
|
||||
Timber.tag(TAG).d("recognizeFood,takePhoto")
|
||||
isShowRecognizeDialog = false
|
||||
//bindCamera()
|
||||
try {
|
||||
cameraUtils.takePhoto(takePhotoSuccessCallback)
|
||||
isFirstRecognize = false
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).d("recognizeFood,takePhoto异常:${e.message}")
|
||||
e.printStackTrace()
|
||||
handleGoodsResult()
|
||||
}
|
||||
}
|
||||
|
||||
private var recognizeCallback: RecognizeCallback? = null
|
||||
var isAgainRecognize = false
|
||||
|
||||
fun againRecognizeFood(callback: RecognizeCallback) {
|
||||
Loading.show(this)
|
||||
isAgainRecognize = true
|
||||
this.recognizeCallback = callback
|
||||
try {
|
||||
cameraUtils.takePhoto(takePhotoSuccessCallback)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).d("againRecognizeFood,takePhoto异常:${e.message}")
|
||||
e.printStackTrace()
|
||||
Loading.dismiss()
|
||||
runOnUiThread {
|
||||
recognizeCallback?.invoke(mutableListOf())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var dropdownPopup: DropdownPopup? = null
|
||||
|
||||
private val dropdownList: MutableList<DictType> = mutableListOf()
|
||||
|
||||
fun getSpannable(text: String, num: Int, color: String): SpannableStringBuilder {
|
||||
return buildSpannableString {
|
||||
appendText(text, ForegroundColorSpan("#FF141428".toColorInt()))
|
||||
appendText(num.toString(), ForegroundColorSpan(color.toColorInt()))
|
||||
}
|
||||
}
|
||||
|
||||
var isShowRecognizeDialog = true
|
||||
private fun searchFood(uri: Uri) {
|
||||
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
|
||||
val newBmp = BitmapCropper.cropCenter(
|
||||
original = bitmap,
|
||||
targetWidth = 1000, targetHeight = 1300,
|
||||
offsetX = 30, offsetY = 100
|
||||
)
|
||||
// val file = BitmapSaver.saveToAppFilesDir(
|
||||
// newBmp,
|
||||
// this,
|
||||
// "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||
// )
|
||||
// Timber.d("${this.javaClass.simpleName}-searchFood-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||
//var foodList = FoodModule.queryFood(newBmp)
|
||||
//物品编号
|
||||
//foodList = foodList.map { "WP"+it.split("WP")[1] }
|
||||
//物品名称
|
||||
//foodList = foodList.map { it.split("WP")[0] }
|
||||
|
||||
val foodList = FoodModule.getFoodScoreList(newBmp)
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
if (newBmp.isRecycled.not()) {
|
||||
newBmp.recycle()
|
||||
}
|
||||
if (foodList.isEmpty()) {
|
||||
recognizeFoodError()
|
||||
return@let
|
||||
}
|
||||
foodList.forEach {
|
||||
it.name = it.name.split("WP")[0]
|
||||
}
|
||||
val filterList =
|
||||
foodList.filter { it.score < FoodModule.BAG_RATE && it.name.contains("黑袋子") }
|
||||
if (filterList.isNotEmpty()) {
|
||||
runOnUiThread {
|
||||
toast("当前物品可能被袋子遮挡,请检查后重试")
|
||||
if (isAgainRecognize) {
|
||||
Loading.dismiss()
|
||||
recognizeCallback?.invoke(mutableListOf())
|
||||
isAgainRecognize = false
|
||||
return@runOnUiThread
|
||||
}
|
||||
handleGoodsResult(mutableListOf())
|
||||
}
|
||||
return@let
|
||||
}
|
||||
viewModel.recognizeGoodsList(
|
||||
//nameList = foodList,
|
||||
scoreList = foodList,
|
||||
) { goodsList ->
|
||||
runOnUiThread {
|
||||
if (isAgainRecognize) {
|
||||
Loading.dismiss()
|
||||
recognizeCallback?.invoke(goodsList)
|
||||
isAgainRecognize = false
|
||||
return@runOnUiThread
|
||||
}
|
||||
handleGoodsResult(
|
||||
goodsList = if (goodsList.size == foodList.size) goodsList else mutableListOf(),
|
||||
// file = null,
|
||||
// name = foodList.joinToString(",")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGoodsResult(
|
||||
goodsList: List<SearchGoodsInfo.Record> = mutableListOf(),
|
||||
file: File? = null,
|
||||
name: String? = null,
|
||||
) {
|
||||
runOnUiThread {
|
||||
if (goodsRecognizeDialog?.isShowing == true) {
|
||||
//isShowRecognizeDialog = false
|
||||
return@runOnUiThread
|
||||
}
|
||||
goodsRecognizeDialog = GoodsRecognizeDialog(
|
||||
context = this,
|
||||
list = goodsList.toMutableList(),
|
||||
goodsFile = file,
|
||||
goodsName = name,
|
||||
) {
|
||||
GoodsSearchDialog(this).show()
|
||||
}.apply {
|
||||
if (isFinishing.not() && isDestroyed.not()) {
|
||||
//isShowRecognizeDialog = false
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var goodsRecognizeDialog: GoodsRecognizeDialog? = null
|
||||
|
||||
private fun replaceIncludeContent() {
|
||||
binding.flContainer.removeAllViews()
|
||||
if (isReceiptPage) {
|
||||
val tempBinding =
|
||||
ListItemReceiptGoodsBinding.inflate(layoutInflater, binding.flContainer, false)
|
||||
binding.flContainer.addView(tempBinding.root)
|
||||
} else {
|
||||
val tempBinding =
|
||||
ListItemSelfProcurementBinding.inflate(layoutInflater, binding.flContainer, false)
|
||||
binding.flContainer.addView(tempBinding.root)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
unBindCamera()
|
||||
}
|
||||
|
||||
fun bindCamera() {
|
||||
cameraUtils.bind()
|
||||
}
|
||||
|
||||
fun unBindCamera() {
|
||||
cameraUtils.unbind()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
try {
|
||||
DialogManager.dismissAll()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
super.onDestroy()
|
||||
//cameraController?.unbind()
|
||||
//isCameraReady = false
|
||||
}
|
||||
|
||||
fun Double?.plus2(value: Double?): Double {
|
||||
return (this ?: 0.0) + (value ?: 0.0)
|
||||
}
|
||||
|
||||
fun addGoods(goods: GoodsInfo) {
|
||||
if (!isReceiptPage) {
|
||||
goods.isSelected = true
|
||||
goodsList.add(goods)
|
||||
binding.rvGoodsList.adapter?.notifyItemInserted(goodsList.size - 1)
|
||||
return
|
||||
}
|
||||
val item = goodsList.firstOrNull { it.goodId == goods.goodId }
|
||||
if (item == null || item.unitName != goods.unitName) {
|
||||
//不存在物品或者物品单位不同
|
||||
goods.isSelected = true
|
||||
goodsList.add(goods)
|
||||
binding.rvGoodsList.adapter?.notifyItemInserted(goodsList.size - 1)
|
||||
return
|
||||
}
|
||||
item.isSelected = true
|
||||
val finalWeight = item.goodsWeight?.toDouble().plus2(goods.goodsWeight?.toDouble())
|
||||
val finalNum = item.receivedNum.plus2(goods.receivedNum)
|
||||
val finalAmount = item.recPriceInItem.plus2(goods.recPriceInItem)
|
||||
val finalPrice =
|
||||
if (finalAmount == 0.toDouble() || finalNum == 0.toDouble()) 0.toDouble() else finalAmount / finalNum
|
||||
item.run {
|
||||
receivedNum = finalNum
|
||||
recUnitPriceTaxIn = finalPrice.roundedDecimalPlace(2)
|
||||
recPriceInItem = finalAmount.roundedDecimalPlace(2)
|
||||
goodsWeight = finalWeight.toBigDecimal()
|
||||
}
|
||||
binding.rvGoodsList.adapter?.notifyItemChanged(goodsList.indexOf(item))
|
||||
if (this is ReceiptActivity) {
|
||||
this.setConfirmNum()
|
||||
}
|
||||
}
|
||||
|
||||
fun searchGoods(
|
||||
searchName: String,
|
||||
pageNo: Int,
|
||||
pageSize: Int = PAGE_SIZE,
|
||||
callback: (List<SearchGoodsInfo.Record>) -> Unit
|
||||
) {
|
||||
runBlocking {
|
||||
val records = searchGoodsBlock(searchName, pageNo, pageSize)
|
||||
runOnUiThread {
|
||||
callback(records)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchGoodsBlock(
|
||||
searchName: String,
|
||||
pageNo: Int,
|
||||
pageSize: Int = PAGE_SIZE
|
||||
): List<SearchGoodsInfo.Record> {
|
||||
return viewModel.searchGoodsInfoList2(
|
||||
goodsName = searchName,
|
||||
pageNo = pageNo,
|
||||
pageSize = pageSize
|
||||
)
|
||||
}
|
||||
|
||||
fun searchMultipleGoods(
|
||||
goodsNameList: List<String>,
|
||||
callback: (MutableList<SearchGoodsInfo.Record>) -> Unit
|
||||
) {
|
||||
runBlocking {
|
||||
val list = mutableListOf<SearchGoodsInfo.Record>()
|
||||
for (goodsName in goodsNameList) {
|
||||
val records = searchGoodsBlock(goodsName, 1, 1)
|
||||
if (records.isNotEmpty()) {
|
||||
list.add(records[0])
|
||||
}
|
||||
}
|
||||
callback(list)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initBackDispatcher() {
|
||||
// 创建回调,true表示初始启用状态
|
||||
backPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
handleBackEvent()
|
||||
}
|
||||
}
|
||||
// 注册回调,使用lifecycleOwner确保生命周期安全
|
||||
onBackPressedDispatcher.addCallback(this, backPressedCallback)
|
||||
}
|
||||
|
||||
private fun handleBackEvent() {
|
||||
val count = goodsList.count { it.isLocalGoods }
|
||||
// 处理返回事件
|
||||
if (count > 0) {
|
||||
WarnDialog(
|
||||
context = this@GoodsListActivity,
|
||||
content = "存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?"
|
||||
) {
|
||||
//onBackPressedDispatcher.onBackPressed()
|
||||
finish()
|
||||
}.show()
|
||||
} else {
|
||||
// 允许默认行为
|
||||
backPressedCallback.isEnabled = false
|
||||
//onBackPressedDispatcher.onBackPressed()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
fun receiptSubmit() {
|
||||
if (binding.tvWareHouse.tag == null) {
|
||||
toast("请选择仓库")
|
||||
binding.flWarehouseDropdown.setBackgroundResource(R.drawable.bg_white_stroke_red)
|
||||
return
|
||||
}
|
||||
goodsList.forEach { goodsInfo ->
|
||||
goodsInfo.warehouseId = binding.tvWareHouse.tag.toString()
|
||||
goodsInfo.warehouseName = binding.tvWareHouse.text.toString()
|
||||
}
|
||||
if (goodsList.isEmpty()) {
|
||||
toast("请选择收货物品")
|
||||
return
|
||||
}
|
||||
val uploadInfo = UploadInfo(
|
||||
id = id,
|
||||
supplierId = supplierId,
|
||||
receiveGoodsInfos = goodsList
|
||||
)
|
||||
Timber.tag("SelfProcurementActivity")
|
||||
.d("receiptSubmit确认收货提交数据:${uploadInfo.toJsonString()}")
|
||||
viewModel.confirmReceipt(uploadInfo) { success ->
|
||||
if (success) {
|
||||
toast("确认收货成功")
|
||||
finish()
|
||||
} else {
|
||||
toast("确认收货失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selfProcurementSubmit() {
|
||||
if (binding.tvWareHouse.tag == null) {
|
||||
toast("请选择仓库")
|
||||
binding.flWarehouseDropdown.setBackgroundResource(R.drawable.bg_white_stroke_red)
|
||||
return
|
||||
}
|
||||
if (goodsList.isEmpty()) {
|
||||
toast("暂无入库物品,请采集后操作")
|
||||
return
|
||||
}
|
||||
val submitData = goodsList.map {
|
||||
PurchaseWarehouseParam(
|
||||
goodsId = it.goodId ?: "",
|
||||
goodsName = it.goodName,
|
||||
kcUnitId = it.kcUnitId,
|
||||
goodsCount = it.goodsCount ?: 0.0,
|
||||
goodsUnitPrice = it.goodsUnitPrice ?: 0.0,
|
||||
goodsPrice = it.goodsPrice ?: 0.0,
|
||||
//goodPurId = it.goodsPurId,
|
||||
warehouseId = binding.tvWareHouse.tag.toString(),
|
||||
buyToInventoryValue = it.buyToInventoryValue ?: ""
|
||||
)
|
||||
}
|
||||
Timber.tag("SelfProcurementActivity")
|
||||
.d("selfProcurementSubmit自采入库提交数据:${submitData.toJsonString()}")
|
||||
viewModel.addToWarehouse(submitData) { success ->
|
||||
if (success) {
|
||||
toast("提交入库成功")
|
||||
finish()
|
||||
} else {
|
||||
toast("提交入库失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fun uploadImage(imageUri: Uri, callback: (String?) -> Unit) {
|
||||
// viewModel.uploadImage(imageUri) { imgUrl ->
|
||||
// if (imgUrl.isNullOrBlank()) {
|
||||
// callback(null)
|
||||
// return@uploadImage
|
||||
// }
|
||||
// callback(imgUrl)
|
||||
// }
|
||||
// }
|
||||
|
||||
fun createNewGoods(file: File, param: GoodsAddParam, callback: (Boolean) -> Unit) {
|
||||
viewModel.uploadImage(file) { imgUrl ->
|
||||
if (imgUrl.isNullOrBlank()) {
|
||||
toast("图片上传失败")
|
||||
return@uploadImage
|
||||
}
|
||||
param.relativeUrl = imgUrl
|
||||
viewModel.createNewGoods(param = param, callback = callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recognizeFoodError() {
|
||||
if (isAgainRecognize) {
|
||||
runOnUiThread {
|
||||
Loading.dismiss()
|
||||
//toast("识别失败,请稍候重试")
|
||||
recognizeCallback?.invoke(mutableListOf())
|
||||
}
|
||||
isAgainRecognize = false
|
||||
} else {
|
||||
handleGoodsResult()
|
||||
}
|
||||
}
|
||||
|
||||
fun uploadImage(goodsId: String, goodsName: String, imageFile: File?) {
|
||||
lifecycleScope.launch {
|
||||
if (imageFile == null) {
|
||||
return@launch
|
||||
}
|
||||
val list = mutableListOf<File>()
|
||||
list.add(imageFile)
|
||||
val map = hashMapOf<String, RequestBody>()
|
||||
map["goodsId"] = goodsId.toRequestBody()
|
||||
map["goodsName"] = goodsName.toRequestBody()
|
||||
viewModel.uploadMultipleImages(files = list, params = map)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.sw.inbound.activity
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import coil.load
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
import com.sw.inbound.databinding.ActivityLocalImagePreviewBinding
|
||||
import com.sw.inbound.databinding.ListItemImagePreviewBinding
|
||||
import com.sw.inbound.dialog.Loading
|
||||
import com.sw.inbound.utils.ext.copyText
|
||||
import java.io.File
|
||||
|
||||
class LocalImagePreviewActivity : ComponentActivity() {
|
||||
|
||||
private lateinit var binding: ActivityLocalImagePreviewBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityLocalImagePreviewBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
binding.recyclerView.let {
|
||||
it.layoutManager = GridLayoutManager(this, 6, GridLayoutManager.VERTICAL, false)
|
||||
it.adapter = imageAdapter
|
||||
}
|
||||
binding.btnBack.setOnClickListener { finish() }
|
||||
loadData()
|
||||
}
|
||||
|
||||
|
||||
fun loadData() {
|
||||
Thread {
|
||||
runOnUiThread {
|
||||
Loading.show(this)
|
||||
}
|
||||
val cropFile = File(this.cacheDir, "crop")
|
||||
cropFile.listFiles()?.forEach {
|
||||
if (it.isDirectory) {
|
||||
return@forEach
|
||||
}
|
||||
val start = "IMG_CROP_".length
|
||||
val end = it.name.indexOf(".jpg")
|
||||
val time = it.name.substring(start, end).toLong()
|
||||
if (time >= 1764086400L) {
|
||||
imageList.add(ImageBean(file = it, name = "$time"))
|
||||
}
|
||||
}
|
||||
imageList.sortBy { it.name.toLong() }
|
||||
runOnUiThread {
|
||||
imageAdapter.notifyDataSetChanged()
|
||||
Loading.dismiss()
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private var isCycle = true
|
||||
|
||||
private val imageList = mutableListOf<ImageBean>()
|
||||
private val imageAdapter: ImageAdapter by lazy {
|
||||
ImageAdapter(imageList).apply {
|
||||
setOnItemClickListener { _, _, position ->
|
||||
imageList[position].name.copyText(this@LocalImagePreviewActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ImageBean(
|
||||
var file: File,
|
||||
var name: String
|
||||
)
|
||||
|
||||
inner class ImageAdapter(var list: MutableList<ImageBean>) :
|
||||
BaseQuickAdapter<ImageBean, ImageAdapter.VH>(list) {
|
||||
|
||||
inner class VH(var binding: ListItemImagePreviewBinding) : QuickViewHolder(binding.root)
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int, item: ImageBean?) {
|
||||
holder.binding.let {
|
||||
it.imageView.load(item!!.file)
|
||||
it.textView.text = item.name
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val binding = ListItemImagePreviewBinding.inflate(inflater, parent, false)
|
||||
return VH(binding)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.sw.inbound.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.sw.inbound.adapter.ReceiptGoodsAdapter
|
||||
import com.sw.inbound.model.response.DictType
|
||||
import com.sw.inbound.utils.ext.gone
|
||||
import com.sw.inbound.utils.ext.toast
|
||||
import com.sw.inbound.utils.ext.visible
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ReceiptActivity : GoodsListActivity() {
|
||||
|
||||
private val adapter by lazy {
|
||||
ReceiptGoodsAdapter(goodsList)
|
||||
}
|
||||
|
||||
override fun initRecyclerView() {
|
||||
binding.rvGoodsList.let {
|
||||
it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||
it.adapter = adapter
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun initView() {
|
||||
//initRvTestData()
|
||||
binding.tvConfirmNum.run {
|
||||
if (isNewReceipt) gone() else visible()
|
||||
}
|
||||
binding.tvNotConfirmNum.run {
|
||||
if (isNewReceipt) gone() else visible()
|
||||
}
|
||||
binding.btnConfirmReceipt.setOnClickListener {
|
||||
if (isNewReceipt) {
|
||||
selfProcurementSubmit()
|
||||
} else {
|
||||
receiptSubmit()
|
||||
}
|
||||
}
|
||||
if (isNewReceipt.not()) {
|
||||
viewModel.getReceiveDetail(id) { detail ->
|
||||
runOnUiThread {
|
||||
if (detail == null) {
|
||||
toast("未查询到数据")
|
||||
return@runOnUiThread
|
||||
}
|
||||
binding.tvPurchaseNo.text = "采购单号:${detail.purCode}"
|
||||
val list = detail.receiveGoodsInfoList
|
||||
list.forEach { goodsInfo ->
|
||||
goodsInfo.recUnitPriceTaxInBak = goodsInfo.recUnitPriceTaxIn
|
||||
goodsInfo.recUnitPriceTaxIn = 0.0
|
||||
goodsInfo.unitNameBak = goodsInfo.unitName
|
||||
goodsInfo.receivedNumBak = goodsInfo.receivedNum
|
||||
}
|
||||
goodsList.addAll(list)
|
||||
adapter.notifyDataSetChanged()
|
||||
setConfirmNum()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.tvPurchaseNo.text = "新增收货"
|
||||
}
|
||||
|
||||
// lifecycleScope.launch {
|
||||
// repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
// launch {
|
||||
// viewModel.adjustedOrders.collect {
|
||||
// val confirmOrderCount = it.size
|
||||
// binding.tvConfirmNum.text = getSpannable("已确认 ", num = confirmOrderCount, color = "#FF009632")
|
||||
// val notConfirmOrderCount = goodsList.size - confirmOrderCount
|
||||
// binding.tvNotConfirmNum.text = getSpannable("未确认 ", num = notConfirmOrderCount, color = "#FF009632")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
override fun getWarehouseList(): List<DictType> {
|
||||
return goodsList.filter {
|
||||
it.warehouseId.isNullOrBlank().not() && it.warehouseName.isNullOrBlank().not()
|
||||
}
|
||||
.distinctBy { it.warehouseId }
|
||||
.map {
|
||||
DictType(id = it.warehouseId!!, value = it.warehouseName!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun setConfirmNum() {
|
||||
val confirmOrderCount = goodsList.count { it.isAdjusted }
|
||||
binding.tvConfirmNum.text =
|
||||
getSpannable("已确认 ", num = confirmOrderCount, color = "#FF009632")
|
||||
val notConfirmOrderCount = goodsList.size - confirmOrderCount
|
||||
binding.tvNotConfirmNum.text =
|
||||
getSpannable("未确认 ", num = notConfirmOrderCount, color = "#FFFF0000")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.sw.inbound.activity
|
||||
|
||||
import android.util.Log
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.sw.inbound.R
|
||||
import com.sw.inbound.adapter.SelfProcurementAdapter
|
||||
import com.sw.inbound.dialog.WarnDialog
|
||||
import com.sw.inbound.model.request.PurchaseWarehouseParam
|
||||
import com.sw.inbound.model.response.DictType
|
||||
import com.sw.inbound.utils.ext.toJsonString
|
||||
import com.sw.inbound.utils.ext.toast
|
||||
import timber.log.Timber
|
||||
import kotlin.getValue
|
||||
|
||||
class SelfProcurementActivity : GoodsListActivity() {
|
||||
|
||||
private val adapter by lazy {
|
||||
SelfProcurementAdapter(goodsList).apply {
|
||||
setOnItemClickListener { adapter, view, position ->
|
||||
goodsList[position].let {
|
||||
it.isSelected = it.isSelected.not()
|
||||
}
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
addOnItemChildClickListener(R.id.ivState) { adapter, view, position ->
|
||||
///删除行数据弹窗
|
||||
showWarnDialog(content = "请确认是否删除该条入库数据,删除后将无法恢复?") {
|
||||
removeAt(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showWarnDialog(content:String, callback:()-> Unit) {
|
||||
WarnDialog(context = this, content = content, confirmBlock = callback).show()
|
||||
}
|
||||
|
||||
override fun initRecyclerView() {
|
||||
binding.rvGoodsList.let {
|
||||
it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||
it.adapter = adapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.tvPurchaseNo.text = "自采入库"
|
||||
binding.btnConfirmReceipt.setOnClickListener {
|
||||
selfProcurementSubmit()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWarehouseList(): List<DictType> {
|
||||
return goodsList.filter {
|
||||
it.warehouseId.isNullOrBlank().not() && it.warehouseName.isNullOrBlank().not()
|
||||
}
|
||||
.distinctBy { it.warehouseId }
|
||||
.map {
|
||||
DictType(id = it.warehouseId!!, value = it.warehouseName!!)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user