Files
Inbound/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt
T
2025-11-12 20:13:00 +08:00

348 lines
11 KiB
Kotlin

package com.sw.inbound.activity
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.camera.view.PreviewView
import androidx.core.view.updateLayoutParams
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.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.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.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.runBlocking
import timber.log.Timber
@SuppressLint("NotifyDataSetChanged")
@AndroidEntryPoint
class FoodCollectionActivity : ComponentActivity() {
private val TAG = "FoodCollectionActivity"
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()
val PAGE_SIZE = 30
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.bitmap = null
it.isFinish = false
it.isShowCamera = true
}
collectionAdapter.notifyItemChanged(position)
}
}
}
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.btnSave.setOnClickListener {
if (clickIndex == -1) {
Toast.makeText(this, "请选择物品名称", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val count = collectList.count {it.bitmap!=null}
if (count == 0) {
Toast.makeText(this, "请拍摄物品照片", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
vectorThread()
}
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()
}
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)
}
}
}
val MAX_COUNT = 100
@SuppressLint("NotifyDataSetChanged")
private fun takePhoto() {
val count = collectList.count { it.bitmap != null }
if (count == MAX_COUNT) {
toast("每次只允许保存${MAX_COUNT}条数据")
return
}
cameraUtils.takePhoto(cameraCallback)
}
@SuppressLint("NotifyDataSetChanged")
private fun clearData() {
collectList.forEach {
it.bitmap = null
it.isShowCamera = true
it.isFinish = false
}
collectionAdapter.notifyDataSetChanged()
clickIndex = -1
binding.etGoodsInput.setText("")
searchGoodsList.clear()
searchAdapter.notifyDataSetChanged()
loadEmptyView()
}
private val cameraCallback:(Uri) -> Unit = { uri->
val index = collectList.indexOfFirst { it.bitmap == null }
if (index == -1) {
toast("每次只允许保存6条数据")
rerurn@cameraCallback
}
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
val cropBitmap = BitmapCropper.cropCenter(
original = bitmap,
targetWidth = 1000, targetHeight = 1300,
offsetX = 30, offsetY = 100
)
val file = BitmapSaver.saveToAppFilesDir(
cropBitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-searchFood-裁剪bitmap保存文件路径:${file?.absolutePath}")
collectList[index].let {
it.bitmap = cropBitmap
it.isShowCamera = false
}
collectionAdapter.notifyItemChanged(index)
}
}
private fun vectorThread() {
Loading.show(this)
Thread {
collectList.filter { it.bitmap != null }
.forEachIndexed { index, it ->
image2VectorTask(it.bitmap!!, index)
}
runOnUiThread {
window.decorView.postDelayed({
Loading.dismiss()
toast("保存成功")
}, 1000)
}
}.start()
}
private fun image2VectorTask(bitmap: Bitmap, position: Int) {
initBox()
val imageVector = FoodModule.bitmap2FloatArray(bitmap)
box?.put(Food(name = searchGoodsList[clickIndex].goodsName, foodIdx = 0, foodVector = imageVector))
collectList[position].let {
it.imageVector = imageVector
it.isFinish = true
}
runOnUiThread {
collectionAdapter.notifyItemChanged(position)
}
}
@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) {
searchGoodsList.clear()
}
searchGoodsList.addAll(records)
if (searchGoodsList.isEmpty()) {
loadEmptyView()
return@searchGoods
}
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)
}
}
}