结算页面、设置页面

This commit is contained in:
2025-11-19 18:35:53 +08:00
parent 9818dfc438
commit 60b712f216
75 changed files with 2983 additions and 647 deletions
@@ -1,134 +1,134 @@
package com.sw.dualscreen.activity
import android.annotation.SuppressLint
import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.CollectedFoodAdapter
import com.sw.dualscreen.databinding.ActivityCollectedDataBinding
import com.sw.dualscreen.databinding.LayoutEmptySearchBinding
import com.sw.dualscreen.dialog.WarnDialog
import com.sw.dualscreen.ext.addOnActionSearchListener
import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.objbox.CollectedFoodBean
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import kotlin.getValue
class CollectedDataActivity : BaseActivity<ActivityCollectedDataBinding>() {
private val viewModel by viewModels<UserViewModel>()
override fun getViewModel(): BaseViewModel {
return viewModel
}
override fun inflateViewBinding(): ActivityCollectedDataBinding {
return ActivityCollectedDataBinding.inflate(layoutInflater)
}
private val list: MutableList<CollectedFoodBean> = mutableListOf()
private val adapter by lazy {
CollectedFoodAdapter(list).apply {
isStateViewEnable = true
addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position ->
deleteGoods(position)
}
}
}
override fun initialize() {
super.initialize()
binding.rvFoodList.let {
it.layoutManager = LinearLayoutManager(this)
it.adapter = adapter
}
binding.ivBack.setOnClickListener { finish() }
binding.ivGoodsSearch.setOnClickListener {
val searchName = binding.etInputGoods.text.toString().trim()
if (searchName.isBlank()) {
ToastUtils.showToast("请输入物品名称")
return@setOnClickListener
}
getCollectGoods(searchName)
}
binding.etInputGoods.let { v ->
v.addOnActionSearchListener {
val searchName = binding.etInputGoods.text.toString().trim()
if (searchName.isBlank()) {
ToastUtils.showToast("请输入物品名称")
return@addOnActionSearchListener
}
getCollectGoods(searchName)
}
v.addTextChangedListener {
if (it.isNullOrBlank()) {
getCollectGoods()
}
}
}
getCollectGoods()
}
private var box: Box<Food>? = null
@SuppressLint("NotifyDataSetChanged")
private fun getCollectGoods(searchName:String?=null) {
if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class)
}
list.clear()
var queryList = box?.all?.distinctBy { it.name }
if (searchName.isNullOrBlank().not()) {
queryList = queryList?.filter { it.name?.contains(searchName) == true }
}
queryList?.forEach {
list.add(CollectedFoodBean(foodName = it.name))
}
adapter.notifyDataSetChanged()
if (list.isEmpty()) {
loadEmptyView()
}
binding.root.hideKeyboard()
}
private var emptyBinding: LayoutEmptySearchBinding? = null
private fun loadEmptyView() {
if (emptyBinding == null) {
emptyBinding = LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvFoodList, false)
}
emptyBinding?.root?.let { layout ->
layout.setOnClickListener { layout.hideKeyboard() }
adapter.stateView = layout
}
}
private fun deleteGoods(position: Int) {
WarnDialog(
context = context,
content = "请确认是否删除菜品:${list[position].foodName}",
confirmBlock = {
Thread {
runOnUiThread {
showWaitingDialog("加载中……")
}
val name = list[position].foodName
val filterIdList = box?.all?.filter { it.name == name }?.map { it.id }
box?.removeByIds(filterIdList)
runOnUiThread {
hideWaitingDialog()
adapter.removeAt(position)
ToastUtils.showToast("删除成功")
if (list.isEmpty()) {
loadEmptyView()
}
}
}.start()
}).show()
}
}
//package com.sw.dualscreen.activity
//
//import android.annotation.SuppressLint
//import androidx.activity.viewModels
//import androidx.core.widget.addTextChangedListener
//import androidx.recyclerview.widget.LinearLayoutManager
//import com.sw.dualscreen.R
//import com.sw.dualscreen.adapter.CollectedFoodAdapter
//import com.sw.dualscreen.databinding.ActivityCollectedDataBinding
//import com.sw.dualscreen.databinding.LayoutEmptySearchBinding
//import com.sw.dualscreen.dialog.WarnDialog
//import com.sw.dualscreen.ext.addOnActionSearchListener
//import com.sw.dualscreen.ext.hideKeyboard
//import com.sw.dualscreen.objbox.CollectedFoodBean
//import com.sw.dualscreen.objbox.Food
//import com.sw.dualscreen.objbox.ObjectBox
//import com.sw.dualscreen.viewmodel.BaseViewModel
//import com.sw.dualscreen.viewmodel.UserViewModel
//import com.sw.plate.utils.ToastUtils
//import io.objectbox.Box
//import io.objectbox.kotlin.boxFor
//import kotlin.getValue
//
//class CollectedDataActivity : BaseActivity<ActivityCollectedDataBinding>() {
// private val viewModel by viewModels<UserViewModel>()
// override fun getViewModel(): BaseViewModel {
// return viewModel
// }
//
// override fun inflateViewBinding(): ActivityCollectedDataBinding {
// return ActivityCollectedDataBinding.inflate(layoutInflater)
// }
//
// private val list: MutableList<CollectedFoodBean> = mutableListOf()
// private val adapter by lazy {
// CollectedFoodAdapter(list).apply {
// isStateViewEnable = true
// addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position ->
// deleteGoods(position)
// }
// }
// }
//
// override fun initialize() {
// super.initialize()
// binding.rvFoodList.let {
// it.layoutManager = LinearLayoutManager(this)
// it.adapter = adapter
// }
// binding.ivBack.setOnClickListener { finish() }
// binding.ivGoodsSearch.setOnClickListener {
// val searchName = binding.etInputGoods.text.toString().trim()
// if (searchName.isBlank()) {
// ToastUtils.showToast("请输入物品名称")
// return@setOnClickListener
// }
// getCollectGoods(searchName)
// }
// binding.etInputGoods.let { v ->
// v.addOnActionSearchListener {
// val searchName = binding.etInputGoods.text.toString().trim()
// if (searchName.isBlank()) {
// ToastUtils.showToast("请输入物品名称")
// return@addOnActionSearchListener
// }
// getCollectGoods(searchName)
// }
// v.addTextChangedListener {
// if (it.isNullOrBlank()) {
// getCollectGoods()
// }
// }
// }
// getCollectGoods()
// }
//
//
// private var box: Box<Food>? = null
//
// @SuppressLint("NotifyDataSetChanged")
// private fun getCollectGoods(searchName:String?=null) {
// if (box == null) {
// box = ObjectBox.boxStore.boxFor(Food::class)
// }
// list.clear()
// var queryList = box?.all?.distinctBy { it.name }
// if (searchName.isNullOrBlank().not()) {
// queryList = queryList?.filter { it.name?.contains(searchName) == true }
// }
// queryList?.forEach {
// list.add(CollectedFoodBean(foodName = it.name))
// }
// adapter.notifyDataSetChanged()
// if (list.isEmpty()) {
// loadEmptyView()
// }
// binding.root.hideKeyboard()
// }
//
// private var emptyBinding: LayoutEmptySearchBinding? = null
// private fun loadEmptyView() {
// if (emptyBinding == null) {
// emptyBinding = LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvFoodList, false)
// }
// emptyBinding?.root?.let { layout ->
// layout.setOnClickListener { layout.hideKeyboard() }
// adapter.stateView = layout
// }
// }
//
// private fun deleteGoods(position: Int) {
// WarnDialog(
// context = context,
// content = "请确认是否删除菜品:${list[position].foodName}",
// confirmBlock = {
// Thread {
// runOnUiThread {
// showWaitingDialog("加载中……")
// }
// val name = list[position].foodName
// val filterIdList = box?.all?.filter { it.name == name }?.map { it.id }
// box?.removeByIds(filterIdList)
// runOnUiThread {
// hideWaitingDialog()
// adapter.removeAt(position)
// ToastUtils.showToast("删除成功")
// if (list.isEmpty()) {
// loadEmptyView()
// }
// }
// }.start()
// }).show()
// }
//}
@@ -0,0 +1,137 @@
package com.sw.dualscreen.activity
import android.annotation.SuppressLint
import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.CollectedFoodNewAdapter
import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding
import com.sw.dualscreen.databinding.LayoutEmptySearchBinding
import com.sw.dualscreen.dialog.WarnDialog
import com.sw.dualscreen.ext.addOnActionSearchListener
import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.objbox.CollectedFoodBean
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
private val viewModel by viewModels<UserViewModel>()
override fun getViewModel(): BaseViewModel {
return viewModel
}
override fun inflateViewBinding(): ActivityCollectedFoodBinding {
return ActivityCollectedFoodBinding.inflate(layoutInflater)
}
private val list: MutableList<CollectedFoodBean> = mutableListOf()
private val adapter by lazy {
CollectedFoodNewAdapter(list).apply {
isStateViewEnable = true
addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position ->
deleteGoods(position)
}
}
}
override fun initialize() {
super.initialize()
binding.rvFoodList.let {
it.layoutManager = LinearLayoutManager(this)
it.adapter = adapter
}
binding.include.let {
it.tvPageTitle.text = "已采集餐品"
it.ivPageBack.setOnClickListener { finish() }
}
binding.ivFoodSearch.setOnClickListener {
val searchName = binding.etInputFood.text.toString().trim()
if (searchName.isBlank()) {
ToastUtils.showToast("请输入物品名称")
return@setOnClickListener
}
getCollectGoods(searchName)
}
binding.etInputFood.let { v ->
v.addOnActionSearchListener {
val searchName = binding.etInputFood.text.toString().trim()
if (searchName.isBlank()) {
ToastUtils.showToast("请输入物品名称")
return@addOnActionSearchListener
}
getCollectGoods(searchName)
}
v.addTextChangedListener {
if (it.isNullOrBlank()) {
getCollectGoods()
}
}
}
getCollectGoods()
}
private var box: Box<Food>? = null
@SuppressLint("NotifyDataSetChanged")
private fun getCollectGoods(searchName: String? = null) {
if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class)
}
list.clear()
var queryList = box?.all?.distinctBy { it.name }
if (searchName.isNullOrBlank().not()) {
queryList = queryList?.filter { it.name?.contains(searchName) == true }
}
queryList?.forEach {
list.add(CollectedFoodBean(foodName = it.name))
}
adapter.notifyDataSetChanged()
if (list.isEmpty()) {
loadEmptyView()
}
binding.root.hideKeyboard()
}
private var emptyBinding: LayoutEmptySearchBinding? = null
private fun loadEmptyView() {
if (emptyBinding == null) {
emptyBinding =
LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvFoodList, false)
}
emptyBinding?.root?.let { layout ->
layout.setOnClickListener { layout.hideKeyboard() }
adapter.stateView = layout
}
}
private fun deleteGoods(position: Int) {
WarnDialog(
context = context,
content = "请确认是否删除菜品:${list[position].foodName}",
confirmBlock = {
Thread {
runOnUiThread {
showWaitingDialog("加载中……")
}
val name = list[position].foodName
val filterIdList = box?.all?.filter { it.name == name }?.map { it.id }
box?.removeByIds(filterIdList)
runOnUiThread {
hideWaitingDialog()
adapter.removeAt(position)
ToastUtils.showToast("删除成功")
if (list.isEmpty()) {
loadEmptyView()
}
}
}.start()
}).show()
}
}
@@ -1,370 +1,370 @@
package com.sw.dualscreen.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Typeface
import android.net.Uri
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.viewModels
import androidx.camera.view.PreviewView
import androidx.core.net.toUri
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.example.utils.FloatBase64Utils
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.FoodCollectionAdapter
import com.sw.dualscreen.adapter.GenericItemAdapter
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
import com.sw.dualscreen.ext.clickWithDebounce
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodCollectionBean
import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.utils.BitmapCropper
import com.sw.dualscreen.utils.BitmapSaver
import com.sw.dualscreen.utils.CameraHelper
import com.sw.dualscreen.utils.CameraUtils
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.launch
import org.pytorch.IValue
import org.pytorch.Module
import org.pytorch.torchvision.TensorImageUtils
import timber.log.Timber
import kotlin.math.max
class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
companion object {
val MAX_COUNT = 100
}
private val viewModel by viewModels<UserViewModel>()
private var selectedFoodId: String? = ""
private var selectedFoodName: String? = ""
private var box: Box<Food>? = null
private val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf()
// private lateinit var cameraHelper: CameraHelper
private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
private val foodList = mutableListOf<FoodInfo>() // 适配器内部维护的数据列表
private val debouncer = Debouncer(2000)
private lateinit var previewView: PreviewView
private val cameraUtils: CameraUtils by lazy {
CameraUtils(this)
}
private val collectionAdapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(foodCollectionList).apply {
// setOnItemClickListener { _, _, position ->
// if (list[position].isShowCamera) {
//// takePhoto()
// cameraHelper.openCamera()
//package com.sw.dualscreen.activity
//
//import android.annotation.SuppressLint
//import android.content.Context
//import android.content.Intent
//import android.graphics.Bitmap
//import android.graphics.Typeface
//import android.net.Uri
//import android.view.View
//import android.view.inputmethod.EditorInfo
//import android.view.inputmethod.InputMethodManager
//import android.widget.Toast
//import androidx.activity.viewModels
//import androidx.camera.view.PreviewView
//import androidx.core.net.toUri
//import androidx.core.view.updateLayoutParams
//import androidx.lifecycle.lifecycleScope
//import androidx.recyclerview.widget.GridLayoutManager
//import com.example.utils.FloatBase64Utils
//import com.sw.dualscreen.R
//import com.sw.dualscreen.adapter.FoodCollectionAdapter
//import com.sw.dualscreen.adapter.GenericItemAdapter
//import com.sw.dualscreen.adapter.GridSpacingItemDecoration
//import com.sw.dualscreen.adapter.dpToPx
//import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
//import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
//import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
//import com.sw.dualscreen.ext.clickWithDebounce
//import com.sw.dualscreen.ext.dp
//import com.sw.dualscreen.model.response.FoodInfo
//import com.sw.dualscreen.objbox.Food
//import com.sw.dualscreen.objbox.FoodCollectionBean
//import com.sw.dualscreen.objbox.FoodModule
//import com.sw.dualscreen.objbox.ObjectBox
//import com.sw.dualscreen.utils.BitmapCropper
//import com.sw.dualscreen.utils.BitmapSaver
//import com.sw.dualscreen.utils.CameraHelper
//import com.sw.dualscreen.utils.CameraUtils
//import com.sw.dualscreen.utils.Debouncer
//import com.sw.dualscreen.utils.ImageUtil
//import com.sw.dualscreen.viewmodel.BaseViewModel
//import com.sw.dualscreen.viewmodel.UserViewModel
//import com.sw.plate.utils.ToastUtils
//import io.objectbox.Box
//import io.objectbox.kotlin.boxFor
//import kotlinx.coroutines.launch
//import org.pytorch.IValue
//import org.pytorch.Module
//import org.pytorch.torchvision.TensorImageUtils
//import timber.log.Timber
//import kotlin.math.max
//
//class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
// companion object {
// val MAX_COUNT = 100
// }
// private val viewModel by viewModels<UserViewModel>()
// private var selectedFoodId: String? = ""
// private var selectedFoodName: String? = ""
//
// private var box: Box<Food>? = null
// private val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf()
//
//// private lateinit var cameraHelper: CameraHelper
//
// private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
// private val foodList = mutableListOf<FoodInfo>() // 适配器内部维护的数据列表
// private val debouncer = Debouncer(2000)
// private lateinit var previewView: PreviewView
//
// private val cameraUtils: CameraUtils by lazy {
// CameraUtils(this)
// }
//
// private val collectionAdapter: FoodCollectionAdapter by lazy {
// FoodCollectionAdapter(foodCollectionList).apply {
//// setOnItemClickListener { _, _, position ->
//// if (list[position].isShowCamera) {
////// takePhoto()
//// cameraHelper.openCamera()
//// }
//// }
// addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
//// list.removeAt(position)
//// collectionAdapter.notifyItemRemoved(position)
//// collectionAdapter.notifyItemRangeChanged(position, list.size)
// foodCollectionList[position].let {
// it.bitmap = null
// it.isShowCamera = true
// it.isFinish = false
// }
// notifyItemChanged(position)
// }
// }
// }
//
// override fun getViewModel(): BaseViewModel {
// return viewModel
// }
//
// override fun inflateViewBinding(): ActivityFoodCollectionBinding {
// return ActivityFoodCollectionBinding.inflate(layoutInflater)
// }
//
// private val cameraCallback: (Uri) -> Unit = { uri ->
// val index = foodCollectionList.indexOfFirst { it.bitmap == null }
// if (index == -1) {
// ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
// rerurn@ cameraCallback
// }
// ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
//// val cropBitmap = BitmapCropper.cropCenter(
//// original = bitmap,
//// targetWidth = 1300, targetHeight = 900,
//// //offsetX = 30, offsetY = 100
//// )
// val file = BitmapSaver.saveToAppFilesDir(
// bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
// )
// Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
//
// foodCollectionList[index].let {
// it.bitmap = bitmap
// it.isShowCamera = false
// it.imageUri = file?.toUri()
// }
// collectionAdapter.notifyItemChanged(index)
// }
// }
//
// @SuppressLint("NotifyDataSetChanged")
// private fun takePhoto() {
// val count = foodCollectionList.count { it.bitmap != null }
// if (count >= MAX_COUNT) {
// ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
// return
// }
// cameraUtils.takePhoto(cameraCallback)
// }
//
// override fun initialize() {
// cameraUtils.initCamera()
// val previewBinding =
// LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
// previewView = previewBinding.previewView.also {
// it.updateLayoutParams {
// width = 180.dp
// height = 180.dp
// }
// }
// cameraUtils.setPreviewController(previewView)
// // 初始化 CameraHelper
//// cameraHelper = CameraHelper(
//// context = this,
//// caller = this,
//// authority = "${packageName}.fileprovider"
//// ) { uri, path ->
//// //if (foodCollectionList.size < 6) {
//// // val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
//// // foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
//// //} else {
//// // foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
//// //}
//// //collectionAdapter.notifyDataSetChanged()
//// cameraCallback(uri)
//// }
//
// binding.ivBack.setOnClickListener {
// val intent = Intent(this, MainActivity::class.java)
// startActivity(intent)
// finish()
// }
// repeat(MAX_COUNT) {
// foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
// }
// binding.rvFoodCollection.let {
// it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
// it.adapter = collectionAdapter
// }
//
// binding.btnFoodSearch.setOnClickListener {
// searchInfo()
// }
//
// binding.btnSave.setOnClickListener {
// if (selectedFoodName.isNullOrBlank()) {
// Toast.makeText(this, "请选择菜品名称", Toast.LENGTH_SHORT).show()
// return@setOnClickListener
// }
// if (foodCollectionList.size <= 1) {
// Toast.makeText(this, "请拍摄菜品照片", Toast.LENGTH_SHORT).show()
// return@setOnClickListener
// }
// vectorThread()
// }
//
// binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
// if (actionId == EditorInfo.IME_ACTION_SEARCH) {
// searchInfo()
// val imm =
// v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
// imm.hideSoftInputFromWindow(v.windowToken, 0)
// true
// } else {
// false
// }
// }
//
// binding.btnCollectedGoods.setOnClickListener {
// startActivity(Intent(this, CollectedDataActivity::class.java))
// }
// binding.btnTakePhoto.clickWithDebounce {
// binding.btnTakePhoto.text = "拍照"
// val count = foodCollectionList.count { it.bitmap != null }
// if (count >= MAX_COUNT) {
// ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
// return@clickWithDebounce
// }
//// cameraHelper.openCamera()
// takePhoto()
// }
// binding.btnClearData.setOnClickListener { clearData() }
// foodAdapter = createAdapter()
// binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
// // 添加间距装饰(12dp)
// binding.recyclerview.addItemDecoration(
// GridSpacingItemDecoration(
// spanCount = 2,
// spacing = dpToPx(30),
// includeEdge = false // 包含边缘间距
// )
// )
// binding.recyclerview.adapter = foodAdapter
// }
//
// private fun vectorThread() {
// //Loading.show(this)
// showWaitingDialog("加载中……")
// Thread {
// foodCollectionList
// .filter { it.bitmap != null }
// .forEachIndexed { index, it ->
// image2VectorTask(it, index)
// }
// runOnUiThread {
// window.decorView.postDelayed({
// //Loading.dismiss()
// hideWaitingDialog()
// }, 1000)
// }
// }.start()
// }
//
// private fun initBox() {
// if (box == null) {
// box = ObjectBox.boxStore.boxFor(Food::class)
// }
// }
//
// private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
// initBox()
// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
//
// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
// Timber.tag("mzf1").e(base64Str)
//// viewModel.postImageData(
//// context,
//// foodId = selectedFoodId.toString(),
//// foodName = selectedFoodName.toString(),
//// foodVector = base64Str,
//// uri = item.imageUri!!
//// )
// box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector))
// foodCollectionList[position].let {
// it.imageVector = imageVector
// it.isFinish = true
// }
//
// runOnUiThread {
// collectionAdapter.notifyItemChanged(position)
// }
// }
//
//
// private fun searchInfo() {
// debouncer.debounce {
// viewModel.searchByFoodName(binding.editFoodName.text.toString())
// }
// }
//
// override fun registerDataChange() {
// super.registerDataChange()
// lifecycleScope.launch {
// viewModel.searchFoodInfoList.collect {
//// foodList.clear()
//// foodList.addAll(it)
// foodAdapter.updateData(it)
// }
// }
// }
//
//
// private var checkedItem: FoodInfo? = null
// private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
// return GenericItemAdapter(
// items = emptyList(),
// bindingInflater = ItemSearchFoodInfoBinding::inflate,
// bindCallback = { item, position ->
// this.tvName.text = item.foodName
// if (item.id != checkedItem?.id) {
// this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
// this.tvName.setTextColor(resources.getColor(R.color.search_normal))
// this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal)
// } else {
// this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
// this.tvName.setTextColor(resources.getColor(R.color.search_checked))
// this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked)
// }
//
// this.llRoot.setOnClickListener {
// Timber.d("itemClick ${item.foodName}, position = $position")
// checkedItem = item
//// viewModel.updateCurrentItem(item)
//// itemClickCallback(item)
// selectedFoodName = item.foodName
// selectedFoodId = item.id
//
// foodAdapter.notifyDataSetChanged()
// }
// }
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
// list.removeAt(position)
// collectionAdapter.notifyItemRemoved(position)
// collectionAdapter.notifyItemRangeChanged(position, list.size)
foodCollectionList[position].let {
it.bitmap = null
it.isShowCamera = true
it.isFinish = false
}
notifyItemChanged(position)
}
}
}
override fun getViewModel(): BaseViewModel {
return viewModel
}
override fun inflateViewBinding(): ActivityFoodCollectionBinding {
return ActivityFoodCollectionBinding.inflate(layoutInflater)
}
private val cameraCallback: (Uri) -> Unit = { uri ->
val index = foodCollectionList.indexOfFirst { it.bitmap == null }
if (index == -1) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
rerurn@ cameraCallback
}
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
// val cropBitmap = BitmapCropper.cropCenter(
// original = bitmap,
// targetWidth = 1300, targetHeight = 900,
// //offsetX = 30, offsetY = 100
// )
val file = BitmapSaver.saveToAppFilesDir(
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
foodCollectionList[index].let {
it.bitmap = bitmap
it.isShowCamera = false
it.imageUri = file?.toUri()
}
collectionAdapter.notifyItemChanged(index)
}
}
@SuppressLint("NotifyDataSetChanged")
private fun takePhoto() {
val count = foodCollectionList.count { it.bitmap != null }
if (count >= MAX_COUNT) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
return
}
cameraUtils.takePhoto(cameraCallback)
}
override fun initialize() {
cameraUtils.initCamera()
val previewBinding =
LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
previewView = previewBinding.previewView.also {
it.updateLayoutParams {
width = 180.dp
height = 180.dp
}
}
cameraUtils.setPreviewController(previewView)
// 初始化 CameraHelper
// cameraHelper = CameraHelper(
// context = this,
// caller = this,
// authority = "${packageName}.fileprovider"
// ) { uri, path ->
// //if (foodCollectionList.size < 6) {
// // val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
// // foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
// //} else {
// // foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
// //}
// //collectionAdapter.notifyDataSetChanged()
// cameraCallback(uri)
// }
binding.ivBack.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
repeat(MAX_COUNT) {
foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
}
binding.rvFoodCollection.let {
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
it.adapter = collectionAdapter
}
binding.btnFoodSearch.setOnClickListener {
searchInfo()
}
binding.btnSave.setOnClickListener {
if (selectedFoodName.isNullOrBlank()) {
Toast.makeText(this, "请选择菜品名称", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (foodCollectionList.size <= 1) {
Toast.makeText(this, "请拍摄菜品照片", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
vectorThread()
}
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchInfo()
val imm =
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
true
} else {
false
}
}
binding.btnCollectedGoods.setOnClickListener {
startActivity(Intent(this, CollectedDataActivity::class.java))
}
binding.btnTakePhoto.clickWithDebounce {
binding.btnTakePhoto.text = "拍照"
val count = foodCollectionList.count { it.bitmap != null }
if (count >= MAX_COUNT) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
return@clickWithDebounce
}
// cameraHelper.openCamera()
takePhoto()
}
binding.btnClearData.setOnClickListener { clearData() }
foodAdapter = createAdapter()
binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
// 添加间距装饰(12dp)
binding.recyclerview.addItemDecoration(
GridSpacingItemDecoration(
spanCount = 2,
spacing = dpToPx(30),
includeEdge = false // 包含边缘间距
)
)
binding.recyclerview.adapter = foodAdapter
}
private fun vectorThread() {
//Loading.show(this)
showWaitingDialog("加载中……")
Thread {
foodCollectionList
.filter { it.bitmap != null }
.forEachIndexed { index, it ->
image2VectorTask(it, index)
}
runOnUiThread {
window.decorView.postDelayed({
//Loading.dismiss()
hideWaitingDialog()
}, 1000)
}
}.start()
}
private fun initBox() {
if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class)
}
}
private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
initBox()
val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
Timber.tag("mzf1").e(base64Str)
// viewModel.postImageData(
// context,
// foodId = selectedFoodId.toString(),
// foodName = selectedFoodName.toString(),
// foodVector = base64Str,
// uri = item.imageUri!!
// )
box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector))
foodCollectionList[position].let {
it.imageVector = imageVector
it.isFinish = true
}
runOnUiThread {
collectionAdapter.notifyItemChanged(position)
}
}
private fun searchInfo() {
debouncer.debounce {
viewModel.searchByFoodName(binding.editFoodName.text.toString())
}
}
override fun registerDataChange() {
super.registerDataChange()
lifecycleScope.launch {
viewModel.searchFoodInfoList.collect {
// foodList.clear()
// foodList.addAll(it)
foodAdapter.updateData(it)
}
}
}
private var checkedItem: FoodInfo? = null
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
return GenericItemAdapter(
items = emptyList(),
bindingInflater = ItemSearchFoodInfoBinding::inflate,
bindCallback = { item, position ->
this.tvName.text = item.foodName
if (item.id != checkedItem?.id) {
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
this.tvName.setTextColor(resources.getColor(R.color.search_normal))
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal)
} else {
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
this.tvName.setTextColor(resources.getColor(R.color.search_checked))
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked)
}
this.llRoot.setOnClickListener {
Timber.d("itemClick ${item.foodName}, position = $position")
checkedItem = item
// viewModel.updateCurrentItem(item)
// itemClickCallback(item)
selectedFoodName = item.foodName
selectedFoodId = item.id
foodAdapter.notifyDataSetChanged()
}
}
)
}
private var clickIndex = -1
@SuppressLint("NotifyDataSetChanged")
private fun clearData() {
foodCollectionList.forEach {
it.bitmap = null
it.isShowCamera = true
it.isFinish = false
}
collectionAdapter.notifyDataSetChanged()
clickIndex = -1
binding.editFoodName.setText("")
// foodList.clear()
// foodAdapter.updateData(mutableListOf())
// foodAdapter.notifyDataSetChanged()
//loadEmptyView()
}
override fun onResume() {
super.onResume()
cameraUtils.bind()
binding.llCameraFlag.run {
visibility = View.VISIBLE
postDelayed({
visibility = View.GONE
}, 3000)
}
}
override fun onPause() {
super.onPause()
cameraUtils.unbind()
binding.llCameraFlag.visibility = View.VISIBLE
}
}
// }
//
// private var clickIndex = -1
//
// @SuppressLint("NotifyDataSetChanged")
// private fun clearData() {
// foodCollectionList.forEach {
// it.bitmap = null
// it.isShowCamera = true
// it.isFinish = false
// }
// collectionAdapter.notifyDataSetChanged()
//
// clickIndex = -1
// binding.editFoodName.setText("")
//// foodList.clear()
//// foodAdapter.updateData(mutableListOf())
//// foodAdapter.notifyDataSetChanged()
// //loadEmptyView()
// }
//
// override fun onResume() {
// super.onResume()
// cameraUtils.bind()
// binding.llCameraFlag.run {
// visibility = View.VISIBLE
// postDelayed({
// visibility = View.GONE
// }, 3000)
// }
// }
//
// override fun onPause() {
// super.onPause()
// cameraUtils.unbind()
// binding.llCameraFlag.visibility = View.VISIBLE
// }
//
//}
@@ -1,5 +1,6 @@
package com.sw.dualscreen.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Outline
@@ -28,11 +29,13 @@ import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.GenericItemAdapter
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
import com.sw.dualscreen.adapter.SearchFoodAdapter
import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.ActivityMainBinding
import com.sw.dualscreen.databinding.ItemFoodInfoBinding
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.objbox.FoodModule.IdNameScore
@@ -55,6 +58,10 @@ import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.concurrent.Executors
import kotlin.math.roundToInt
@@ -67,7 +74,23 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private var isAnalyzing = true // 控制是否进行图像分析
private var presentation: SecondaryScreenPresentation? = null
private val executor = Executors.newSingleThreadExecutor()
private lateinit var adapter: GenericItemAdapter<FoodInfo, ItemFoodInfoBinding>
private val searchFoodList = mutableListOf<FoodInfo>()
private val adapter by lazy {
SearchFoodAdapter(searchFoodList).apply {
setOnItemClickListener { adapter, view, position ->
searchFoodList.forEachIndexed { index, info ->
searchFoodList[index].isChecked = index == position
}
val item = searchFoodList[position]
Timber.d("itemClick ${item.foodName}, position = $position")
item.photoUri = null
checkedItem = item
notifyDataSetChanged()
// TODO 暂时注释---------------
//updateCurrentFood(item)
}
}
}
private var checkedItem: FoodInfo? = null
private var imageAnalysis: ImageAnalysis? = null
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
@@ -102,11 +125,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun initView() {
viewModel.activeEngine()
binding.foodDisplayLayout.setOnClickListener {
val intent = Intent(this, FoodCollectionActivity::class.java)
startActivity(intent)
updateDateTime()
binding.ivSetting.setOnClickListener {
startActivity(Intent(this, SettingActivity::class.java))
finish()
}
//binding.foodDisplayLayout.setOnClickListener {
// val intent = Intent(this, FoodCollectionActivity::class.java)
// startActivity(intent)
// finish()
//}
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
@@ -121,49 +149,57 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
}
val spinnerAdapter = ArrayAdapter<String>(
this,
R.layout.item_for_custom_spinner,
arrayOf("即放即取", "余量取餐")
)
binding.spinner.adapter = spinnerAdapter
//val spinnerAdapter = ArrayAdapter<String>(
// this,
// R.layout.item_for_custom_spinner,
// arrayOf("即放即取", "余量取餐")
//)
//binding.spinner.adapter = spinnerAdapter
val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
binding.spinner.setSelection(mode ?: 0)
//binding.spinner.setSelection(mode ?: 0)
presentation?.updateMealPickupMode(mode ?: 0)
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, position)
updateCurrentFood(null)
isRecognitionFood = true
presentation?.updateMealPickupMode(position)
SensorScaleUtils.zero()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
//binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
// override fun onItemSelected(
// parent: AdapterView<*>?,
// view: View?,
// position: Int,
// id: Long
// ) {
// SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, position)
// updateCurrentFood(null)
// isRecognitionFood = true
// presentation?.updateMealPickupMode(position)
//
// SensorScaleUtils.zero()
// }
//
// override fun onNothingSelected(parent: AdapterView<*>?) {}
//}
//adapter = createAdapter()
binding.recyclerview.let {
it.layoutManager = GridLayoutManager(this, 2)
it.adapter = adapter
}
adapter = createAdapter()
binding.recyclerview.layoutManager = GridLayoutManager(this, 2)
// 添加间距装饰(12dp)
binding.recyclerview.addItemDecoration(
GridSpacingItemDecoration(
spanCount = 2,
spacing = dpToPx(30),
includeEdge = false // 包含边缘间距
)
)
binding.recyclerview.adapter = adapter
//binding.recyclerview.addItemDecoration(
// GridSpacingItemDecoration(
// spanCount = 2,
// spacing = dpToPx(30),
// includeEdge = false // 包含边缘间距
// )
//)
//binding.recyclerview.adapter = adapter
binding.tvFoodName.setOnClickListener {
SensorScaleUtils.zero()
}
binding.tvRescan.setOnClickListener {
debouncer.debounce { recognizeFood() }
}
binding.btnPay.setOnClickListener {
startActivity(Intent(this, PayActivity::class.java).apply {
putExtra(PayActivity.FOOD_INFO, checkedItem)
})
}
}
fun updateCurrentFood(foodInfo: FoodInfo?) {
@@ -204,7 +240,23 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// }
lifecycleScope.launch {
viewModel.identifiedFoodInfoList.collect { list ->
adapter.updateData(list)
//adapter.updateData(list)
searchFoodList.clear()
searchFoodList.addAll(list)
adapter.notifyDataSetChanged()
//TODO 暂时写死数据测试---------------------------
binding.flPay.visible()
binding.recyclerview.visible()
searchFoodList.clear()
searchFoodList.add(FoodInfo(foodName = "岐山臊子面"))
searchFoodList.add(FoodInfo(foodName = "肉末干拌面"))
searchFoodList.add(FoodInfo(foodName = "雪菜肉丝面"))
searchFoodList.add(FoodInfo(foodName = "康帅博方便面"))
searchFoodList.add(FoodInfo(foodName = "担担面"))
searchFoodList.add(FoodInfo(foodName = "四合一干拌面"))
adapter.notifyDataSetChanged()
if (list.isNotEmpty()) {
checkedItem = list[0]
checkedItem!!.photoUri = lastPhotoUri
@@ -213,7 +265,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
}
SensorScaleUtils.startScale { weight ->
SensorScaleUtils.addWeightListener { weight ->
Timber.d("registerDataChange weight = $weight")
presentation?.updateWeight(weight)
@@ -281,6 +334,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
@SuppressLint("NotifyDataSetChanged")
private fun updateFoodInfo(list: List<FoodInfo>, scoreList: List<IdNameScore>) {
Timber.d("registerDataChange识别后查询接口数据:${GsonUtils.toJson(list)}")
list.forEach { foodInfo ->
@@ -288,7 +342,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val score = scoreItem?.score ?: 0.0
foodInfo.score = ((1 - score) * 10000).roundToInt()
}
adapter.updateData(list)
searchFoodList.clear()
searchFoodList.addAll(list)
adapter.notifyDataSetChanged()
//adapter.updateData(list)
if (list.isNotEmpty()) {
checkedItem = list[0]
checkedItem!!.photoUri = lastPhotoUri
@@ -298,33 +355,33 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> {
return GenericItemAdapter(
items = emptyList(),
bindingInflater = ItemFoodInfoBinding::inflate,
bindCallback = { item, position ->
val score = (item.score / 100.0).format2String(2)
this.tvName.text = "${item.foodName}\n${score}%"
if (item.id != checkedItem?.id) {
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
this.tvName.setTextColor(resources.getColor(R.color.identify_normal))
this.llRoot.setBackgroundResource(R.drawable.grid_item_normal)
} else {
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
this.tvName.setTextColor(resources.getColor(R.color.identify_checked))
this.llRoot.setBackgroundResource(R.drawable.grid_item_checked)
}
//
this.llRoot.setOnClickListener {
Timber.d("itemClick ${item.foodName}, position = $position")
item.photoUri = null
checkedItem = item
adapter.notifyDataSetChanged()
updateCurrentFood(item)
}
}
)
}
// private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> {
// return GenericItemAdapter(
// items = emptyList(),
// bindingInflater = ItemFoodInfoBinding::inflate,
// bindCallback = { item, position ->
// val score = (item.score / 100.0).format2String(2)
// this.tvName.text = "${item.foodName}\n${score}%"
// if (item.id != checkedItem?.id) {
// this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
// this.tvName.setTextColor(resources.getColor(R.color.identify_normal))
// this.llRoot.setBackgroundResource(R.drawable.grid_item_normal)
// } else {
// this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
// this.tvName.setTextColor(resources.getColor(R.color.identify_checked))
// this.llRoot.setBackgroundResource(R.drawable.grid_item_checked)
// }
////
// this.llRoot.setOnClickListener {
// Timber.d("itemClick ${item.foodName}, position = $position")
// item.photoUri = null
// checkedItem = item
// adapter.notifyDataSetChanged()
// updateCurrentFood(item)
// }
// }
// )
// }
private fun setupCamera() {
Timber.d("setupCamera")
@@ -496,15 +553,24 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
@SuppressLint("NotifyDataSetChanged")
fun clearFoodList() {
adapter.updateData(listOf())
//adapter.updateData(listOf())
searchFoodList.clear()
adapter.notifyDataSetChanged()
}
override fun onDestroy() {
SensorScaleUtils.closeScale()
//SensorScaleUtils.closeScale()
presentation?.dismiss()
super.onDestroy()
shutdownCamera()
}
private fun updateDateTime() {
val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE", Locale.CHINA)
val dateTime = formatter.format(LocalDateTime.now(ZoneId.of("Asia/Shanghai")))
binding.tvTitleTime.text = dateTime
}
}
@@ -0,0 +1,128 @@
package com.sw.dualscreen.activity
import android.view.View
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.fragment.CashPayFragment
import com.sw.dualscreen.activity.fragment.FacePayFragment
import com.sw.dualscreen.activity.fragment.NumberPayFragment
import com.sw.dualscreen.activity.fragment.PayResultFragment
import com.sw.dualscreen.activity.fragment.QrCodePayFragment
import com.sw.dualscreen.databinding.ActivityPayBinding
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
class PayActivity : BaseActivity<ActivityPayBinding>() {
companion object {
const val FOOD_INFO = "foodInfo"
const val TAG_PAY_QR_CODE = "tagPayQrCode"
const val TAG_PAY_CASH = "tagPayCash"
const val TAG_PAY_NUMBER = "tagPayNumber"
const val TAG_PAY_FACE = "tagPayFace"
const val TAG_PAY_RESULT = "tagPayResult"
}
private val viewModel by viewModels<UserViewModel>()
override fun getViewModel(): BaseViewModel {
return viewModel
}
override fun inflateViewBinding(): ActivityPayBinding {
return ActivityPayBinding.inflate(layoutInflater)
}
var foodInfo: FoodInfo? = null
private var qrCodePayFragment: QrCodePayFragment? = null
private var cashPayFragment: CashPayFragment? = null
private var numberPayFragment: NumberPayFragment? = null
private var facePayFragment: FacePayFragment? = null
private var payResultFragment: PayResultFragment? = null
@Suppress("DEPRECATION")
override fun initialize() {
super.initialize()
foodInfo = intent.getParcelableExtra(FOOD_INFO)
binding.tvFoodName.text = foodInfo?.foodName
binding.include.ivPageBack.setOnClickListener { finish() }
binding.include.tvPageTitle.text = "下单结算"
val payQrCodeClickListener = View.OnClickListener {
if (qrCodePayFragment == null) {
qrCodePayFragment = QrCodePayFragment()
}
addFragment(qrCodePayFragment!!, TAG_PAY_QR_CODE)
switchButton(true, false, false, false)
}
binding.btnPayQrCode.setOnClickListener(payQrCodeClickListener)
binding.btnPayCash.setOnClickListener {
if (cashPayFragment == null) {
cashPayFragment = CashPayFragment()
}
addFragment(cashPayFragment!!, TAG_PAY_CASH)
switchButton(false, true, false, false)
}
binding.btnPayNumber.setOnClickListener {
if (numberPayFragment == null) {
numberPayFragment = NumberPayFragment()
}
addFragment(numberPayFragment!!, TAG_PAY_NUMBER)
switchButton(false, false, true, false)
}
binding.btnPayFace.setOnClickListener {
showFacePay()
}
payQrCodeClickListener.onClick(binding.btnPayQrCode)
}
private fun switchButton(vararg arr: Boolean) {
binding.btnPayQrCode.isChecked = arr[0]
binding.btnPayCash.isChecked = arr[1]
binding.btnPayNumber.isChecked = arr[2]
binding.btnPayFace.isChecked = arr[3]
}
fun showFacePay() {
if (facePayFragment == null) {
facePayFragment = FacePayFragment()
}
addFragment(facePayFragment!!, TAG_PAY_FACE)
switchButton(false, false, false, true)
}
private fun addFragment(currentFragment: Fragment, tag: String) {
runCatching {
supportFragmentManager.beginTransaction().apply {
val existFragment = supportFragmentManager.findFragmentByTag(tag)
if (existFragment == null) {
add(R.id.fragmentContainerView, currentFragment, tag)
}
val fragments = supportFragmentManager.fragments
fragments.forEach {
it?.let { fragment ->
if (fragment != currentFragment) {
hide(fragment)
}
}
}
show(currentFragment)
commitAllowingStateLoss()
}
}.onFailure {
it.printStackTrace()
}
}
fun showPayResult() {
if (payResultFragment == null) {
payResultFragment = PayResultFragment()
}
addFragment(payResultFragment!!, TAG_PAY_RESULT)
}
}
@@ -0,0 +1,71 @@
package com.sw.dualscreen.activity
import android.content.Intent
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.fragment.BusinessFragment
import com.sw.dualscreen.activity.fragment.CollectFragment
import com.sw.dualscreen.databinding.ActivitySettingBinding
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import kotlin.collections.get
import kotlin.text.replace
class SettingActivity : BaseActivity<ActivitySettingBinding>() {
private val viewModel by viewModels<UserViewModel>()
override fun getViewModel(): BaseViewModel {
return viewModel
}
override fun inflateViewBinding(): ActivitySettingBinding {
return ActivitySettingBinding.inflate(layoutInflater)
}
private val fragmentList = mutableListOf<Fragment>().apply {
add(BusinessFragment())
add(CollectFragment())
}
override fun initialize() {
super.initialize()
binding.include.let {
it.ivPageBack.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
it.tvPageTitle.text = "设置"
}
binding.rgSetting.setOnCheckedChangeListener { group, checkedId ->
if (checkedId == R.id.rbBusiness) {
showFragment(fragmentList[0])
return@setOnCheckedChangeListener
}
if (checkedId == R.id.rbCollect) {
showFragment(fragmentList[1])
return@setOnCheckedChangeListener
}
}
binding.rgSetting.check(R.id.rbBusiness)
}
private fun showFragment(fragment: Fragment) {
runCatching {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragmentContainerView, fragment)
commit()
}
}.onFailure {
it.printStackTrace()
}
}
fun searchByFoodName(foodName:String, action: (List<FoodInfo>) -> Unit) {
viewModel.searchByFoodName(foodName, action)
}
}
@@ -0,0 +1,32 @@
package com.sw.dualscreen.activity.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
abstract class BaseFragment<VB : ViewBinding>: Fragment() {
protected lateinit var binding: VB
protected abstract fun inflateViewBinding(): VB
abstract fun initialize()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = inflateViewBinding()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initialize()
}
}
@@ -0,0 +1,42 @@
package com.sw.dualscreen.activity.fragment
import android.view.LayoutInflater
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.R
import com.sw.dualscreen.databinding.FragmentBusinessBinding
import com.sw.dualscreen.sdk.SensorScaleUtils
import com.sw.dualscreen.utils.SPUtil
import kotlin.math.roundToInt
class BusinessFragment: BaseFragment<FragmentBusinessBinding>() {
override fun inflateViewBinding(): FragmentBusinessBinding {
return FragmentBusinessBinding.inflate(LayoutInflater.from(context))
}
override fun initialize() {
binding.rgCharge.let {
it.setOnCheckedChangeListener { group, checkedId ->
val mode = if (checkedId == R.id.rbChargeYes) 0 else 1
SPUtil.getInstance().put(GlobalKey.KEY_CHARGE_MODE, mode)
}
val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
it.check(if (mode == 0) R.id.rbChargeYes else R.id.rbChargeNot)
}
binding.rgTakeFood.let {
it.setOnCheckedChangeListener { group, checkedId ->
val mode = if (checkedId == R.id.rbPickAndPlace) 0 else 1
SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode)
}
val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
it.check(if (mode == 0) R.id.rbPickAndPlace else R.id.rbSurplusCalculate)
}
binding.btnClearZero.setOnClickListener {
SensorScaleUtils.tare()
}
SensorScaleUtils.addWeightListener { value ->
val realWeight = (value * 1000).roundToInt()
binding.tvFoodWeight.text = "$realWeight"
}
}
}
@@ -0,0 +1,25 @@
package com.sw.dualscreen.activity.fragment
import android.text.Spanned
import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan
import androidx.core.text.buildSpannedString
import com.sw.dualscreen.databinding.FragmentCashPayBinding
class CashPayFragment: BaseFragment<FragmentCashPayBinding>() {
override fun inflateViewBinding(): FragmentCashPayBinding {
return FragmentCashPayBinding.inflate(layoutInflater)
}
override fun initialize() {
binding.tvRealAmount.text = getAmountText("36.8")
}
private fun getAmountText(amount:String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(36, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(60, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
@@ -0,0 +1,285 @@
package com.sw.dualscreen.activity.fragment
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.camera.view.PreviewView
import androidx.core.net.toUri
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.GridLayoutManager
import com.sw.dualscreen.R
import com.sw.dualscreen.activity.CollectedFoodActivity
import com.sw.dualscreen.activity.SettingActivity
import com.sw.dualscreen.adapter.FoodCollectionAdapter
import com.sw.dualscreen.adapter.SearchFoodAdapter
import com.sw.dualscreen.databinding.FragmentCollectBinding
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
import com.sw.dualscreen.ext.clickWithDebounce
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodCollectionBean
import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.utils.BitmapSaver
import com.sw.dualscreen.utils.CameraUtils
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.ImageUtil
import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
import timber.log.Timber
import kotlin.getValue
class CollectFragment : BaseFragment<FragmentCollectBinding>() {
companion object {
const val MAX_COUNT = 5
}
private var selectedFoodId: String? = ""
private var selectedFoodName: String? = ""
private var box: Box<Food>? = null
private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply {
repeat(MAX_COUNT) {
add(FoodCollectionBean(isShowCamera = true))
}
}
private val searchFoodList = mutableListOf<FoodInfo>()
private var settingActivity: SettingActivity?=null
private var checkedItem: FoodInfo? = null
private val searchFoodAdapter by lazy {
SearchFoodAdapter(searchFoodList).apply {
setOnItemClickListener { adapter, view, position ->
checkedItem = searchFoodList[position]
selectedFoodId = checkedItem!!.id
val isChecked = checkedItem!!.isChecked
searchFoodList[position].isChecked = isChecked.not()
notifyItemChanged(position)
}
}
}
private val debouncer = Debouncer(2000)
private lateinit var previewView: PreviewView
private val cameraUtils: CameraUtils by lazy {
CameraUtils(requireActivity())
}
private val collectionAdapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(foodCollectionList).apply {
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
foodCollectionList[position].let {
it.bitmap = null
it.isShowCamera = true
it.isFinish = false
}
notifyItemChanged(position)
}
}
}
override fun inflateViewBinding(): FragmentCollectBinding {
return FragmentCollectBinding.inflate(LayoutInflater.from(context))
}
private val cameraCallback: (Uri) -> Unit = { uri ->
val index = foodCollectionList.indexOfFirst { it.bitmap == null }
if (index == -1) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
rerurn@cameraCallback
}
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
// val cropBitmap = BitmapCropper.cropCenter(
// original = bitmap,
// targetWidth = 1300, targetHeight = 900,
// //offsetX = 30, offsetY = 100
// )
val file = BitmapSaver.saveToAppFilesDir(
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
foodCollectionList[index].let {
it.bitmap = bitmap
it.isShowCamera = false
it.imageUri = file?.toUri()
}
collectionAdapter.notifyItemChanged(index)
}
}
@SuppressLint("NotifyDataSetChanged")
private fun takePhoto() {
val count = foodCollectionList.count { it.bitmap != null }
if (count >= MAX_COUNT) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
return
}
cameraUtils.takePhoto(cameraCallback)
}
override fun initialize() {
settingActivity = activity as SettingActivity
cameraUtils.initCamera()
val previewBinding =
LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
previewView = previewBinding.previewView.also {
it.updateLayoutParams {
width = 456.dp
height = 342.dp
}
}
cameraUtils.setPreviewController(previewView)
binding.rvFoodList.let {
it.layoutManager = GridLayoutManager(requireActivity(), 3, GridLayoutManager.VERTICAL, false)
it.adapter = collectionAdapter
}
binding.btnFoodSearch.setOnClickListener {
searchInfo()
}
binding.btnSave.setOnClickListener {
if (selectedFoodName.isNullOrBlank()) {
Toast.makeText(requireActivity(), "请选择菜品名称", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (foodCollectionList.size <= 1) {
Toast.makeText(requireActivity(), "请拍摄菜品照片", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
vectorThread()
}
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchInfo()
val imm =
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
true
} else {
false
}
}
binding.btnCollectedFood.setOnClickListener {
startActivity(Intent(requireActivity(), CollectedFoodActivity::class.java))
}
binding.btnTakePhoto.clickWithDebounce {
val count = foodCollectionList.count { it.bitmap != null }
if (count >= MAX_COUNT) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
return@clickWithDebounce
}
takePhoto()
}
binding.btnClearData.setOnClickListener { clearData() }
binding.rvSearchFood.let {
it.layoutManager = GridLayoutManager(context, 2)
it.adapter = searchFoodAdapter
}
}
private fun vectorThread() {
settingActivity?.showWaitingDialog("加载中……")
Thread {
foodCollectionList
.filter { it.bitmap != null }
.forEachIndexed { index, it ->
image2VectorTask(it, index)
}
activity?.runOnUiThread {
binding.root.postDelayed({
settingActivity?.hideWaitingDialog()
}, 1000)
}
}.start()
}
private fun initBox() {
if (box == null) {
box = ObjectBox.boxStore.boxFor(Food::class)
}
}
private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
initBox()
val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
// Timber.tag("mzf1").e(base64Str)
//// viewModel.postImageData(
//// context,
//// foodId = selectedFoodId.toString(),
//// foodName = selectedFoodName.toString(),
//// foodVector = base64Str,
//// uri = item.imageUri!!
//// )
box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector))
foodCollectionList[position].let {
it.imageVector = imageVector
it.isFinish = true
}
activity?.runOnUiThread {
collectionAdapter.notifyItemChanged(position)
}
}
@SuppressLint("NotifyDataSetChanged")
private fun searchInfo() {
debouncer.debounce {
settingActivity?.searchByFoodName(binding.editFoodName.text.toString()) {
searchFoodList.clear()
searchFoodList.addAll(it)
searchFoodAdapter.notifyDataSetChanged()
}
}
}
private var clickIndex = -1
@SuppressLint("NotifyDataSetChanged")
private fun clearData() {
foodCollectionList.forEach {
it.bitmap = null
it.isShowCamera = true
it.isFinish = false
}
collectionAdapter.notifyDataSetChanged()
clickIndex = -1
binding.editFoodName.setText("")
// foodList.clear()
// foodAdapter.updateData(mutableListOf())
// foodAdapter.notifyDataSetChanged()
//loadEmptyView()
}
override fun onResume() {
super.onResume()
cameraUtils.bind()
binding.llCameraFlag.run {
visibility = View.VISIBLE
postDelayed({
visibility = View.GONE
}, 3000)
}
}
override fun onPause() {
super.onPause()
cameraUtils.unbind()
binding.llCameraFlag.visibility = View.VISIBLE
}
}
@@ -0,0 +1,19 @@
package com.sw.dualscreen.activity.fragment
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.FragmentFacePayBinding
class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
override fun inflateViewBinding(): FragmentFacePayBinding {
return FragmentFacePayBinding.inflate(layoutInflater)
}
private var payActivity: PayActivity? = null
override fun initialize() {
payActivity = activity as PayActivity
binding.tvFaceState.setOnClickListener {
payActivity?.showPayResult()
}
}
}
@@ -0,0 +1,15 @@
package com.sw.dualscreen.activity.fragment
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
import com.sw.dualscreen.ext.hideKeyboard
class NumberPayFragment: BaseFragment<FragmentNumberPayBinding>() {
override fun inflateViewBinding(): FragmentNumberPayBinding {
return FragmentNumberPayBinding.inflate(layoutInflater)
}
override fun initialize() {
binding.root.hideKeyboard()
}
}
@@ -0,0 +1,28 @@
package com.sw.dualscreen.activity.fragment
import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.FragmentPayResultBinding
class PayResultFragment: BaseFragment<FragmentPayResultBinding>() {
override fun inflateViewBinding(): FragmentPayResultBinding {
return FragmentPayResultBinding.inflate(layoutInflater)
}
private var payActivity: PayActivity? = null
override fun initialize() {
payActivity = activity as PayActivity
binding.btnBack.setOnClickListener {
payActivity?.showFacePay()
}
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
//显示时重新查询接口
if (hidden.not()) {
}
}
}
@@ -0,0 +1,29 @@
package com.sw.dualscreen.activity.fragment
import android.text.Spanned
import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan
import androidx.core.text.buildSpannedString
import com.sw.dualscreen.R
import com.sw.dualscreen.databinding.FragmentQrcodePayBinding
class QrCodePayFragment: BaseFragment<FragmentQrcodePayBinding>() {
override fun inflateViewBinding(): FragmentQrcodePayBinding {
return FragmentQrcodePayBinding.inflate(layoutInflater)
}
override fun initialize() {
binding.tvRealAmount.text = getAmountText("36.8")
binding.ivPayQrCode.setImageResource(R.drawable.ic_qrcode)
binding.tvPayResult.text = "待支付(60s)..."
}
private fun getAmountText(amount:String): SpannedString {
return buildSpannedString {
append("¥", AbsoluteSizeSpan(36, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(60, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}