Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef13aa8307 | ||
|
|
a9663a21f0 | ||
|
|
d060305417 | ||
|
|
bb319ee4ee | ||
|
|
bb1ecbde43 | ||
|
|
7a03ab8108 |
@@ -5,7 +5,7 @@
|
|||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"id": "1:6465153867413579585",
|
"id": "1:6465153867413579585",
|
||||||
"lastPropertyId": "4:3660727688686055762",
|
"lastPropertyId": "5:7592685238899911344",
|
||||||
"name": "Food",
|
"name": "Food",
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
@@ -30,6 +30,11 @@
|
|||||||
"indexId": "1:5497952887792054610",
|
"indexId": "1:5497952887792054610",
|
||||||
"type": 28,
|
"type": 28,
|
||||||
"flags": 8
|
"flags": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5:7592685238899911344",
|
||||||
|
"name": "version",
|
||||||
|
"type": 9
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": []
|
"relations": []
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.shuwei.dish.match.adapter
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.chad.library.adapter4.BaseQuickAdapter
|
import com.chad.library.adapter4.BaseQuickAdapter
|
||||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||||
@@ -24,6 +25,15 @@ class Food3Adapter(list: MutableList<GoodsItem>) :
|
|||||||
text = item.goodsName
|
text = item.goodsName
|
||||||
isChecked = item.isClicked
|
isChecked = item.isClicked
|
||||||
}
|
}
|
||||||
|
// 显示当前选中的净材种类,无数据时隐藏
|
||||||
|
holder.binding.tvRawMaterialsType.run {
|
||||||
|
if (item.rawMaterialsType.isNullOrEmpty()) {
|
||||||
|
visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = item.rawMaterialsType
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
|||||||
CookFoodGoodsEntity::class,
|
CookFoodGoodsEntity::class,
|
||||||
SeasoningSlotEntity::class,
|
SeasoningSlotEntity::class,
|
||||||
],
|
],
|
||||||
version = 10,
|
version = 12,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
)
|
)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
@@ -232,6 +232,22 @@ val MIGRATION_9_10 = MigrationImpl(9, 10) { db ->
|
|||||||
db.execSQL("DROP INDEX IF EXISTS dm_cook_food_foodId_IDX")
|
db.execSQL("DROP INDEX IF EXISTS dm_cook_food_foodId_IDX")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本10→11:为 dm_cook_food_goods 和 dm_seasoning 表新增 goodsCode(物料编码)字段
|
||||||
|
*/
|
||||||
|
val MIGRATION_10_11 = MigrationImpl(10, 11) { db ->
|
||||||
|
db.execSQL("ALTER TABLE dm_cook_food_goods ADD COLUMN goodsCode TEXT DEFAULT ''")
|
||||||
|
db.execSQL("ALTER TABLE dm_seasoning ADD COLUMN goodsCode TEXT DEFAULT ''")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本11→12:为 dm_cook_food_goods 和 dm_seasoning 表新增 rawMaterialsType(净材种类)字段
|
||||||
|
*/
|
||||||
|
val MIGRATION_11_12 = MigrationImpl(11, 12) { db ->
|
||||||
|
db.execSQL("ALTER TABLE dm_cook_food_goods ADD COLUMN rawMaterialsType TEXT DEFAULT NULL")
|
||||||
|
db.execSQL("ALTER TABLE dm_seasoning ADD COLUMN rawMaterialsType TEXT DEFAULT NULL")
|
||||||
|
}
|
||||||
|
|
||||||
class DatabaseProvider(private val context: Context) {
|
class DatabaseProvider(private val context: Context) {
|
||||||
// 单例模式确保全局唯一实例
|
// 单例模式确保全局唯一实例
|
||||||
val instance: AppDatabase by lazy {
|
val instance: AppDatabase by lazy {
|
||||||
@@ -253,6 +269,8 @@ class DatabaseProvider(private val context: Context) {
|
|||||||
.addMigrations(MIGRATION_7_8)
|
.addMigrations(MIGRATION_7_8)
|
||||||
.addMigrations(MIGRATION_8_9)
|
.addMigrations(MIGRATION_8_9)
|
||||||
.addMigrations(MIGRATION_9_10)
|
.addMigrations(MIGRATION_9_10)
|
||||||
|
.addMigrations(MIGRATION_10_11)
|
||||||
|
.addMigrations(MIGRATION_11_12)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.shuwei.dish.match.base.DeviceRole
|
import com.shuwei.dish.match.base.DeviceRole
|
||||||
import com.shuwei.dish.match.base.GlobalData
|
import com.shuwei.dish.match.base.GlobalData
|
||||||
@@ -26,9 +27,14 @@ open class CommonDialog(
|
|||||||
private var positiveText = "确认"
|
private var positiveText = "确认"
|
||||||
private var neutralText: String? = null
|
private var neutralText: String? = null
|
||||||
private var negativeClick: (() -> Unit)? = null
|
private var negativeClick: (() -> Unit)? = null
|
||||||
private var positiveClick: (() -> Unit)? = null
|
// 返回 true 时关闭弹窗,返回 false 时保持弹窗显示(用于校验不通过的场景)
|
||||||
|
private var positiveClick: (() -> Boolean)? = null
|
||||||
private var neutralClick: (() -> Unit)? = null
|
private var neutralClick: (() -> Unit)? = null
|
||||||
private var dismissCallback: (() -> Unit)? = null
|
private var dismissCallback: (() -> Unit)? = null
|
||||||
|
// 待添加到 llContent 的自定义 View,在 onCreate 后挂载
|
||||||
|
private var pendingContentView: View? = null
|
||||||
|
// pendingContentView 非空时对 tvContent 的灵活控制回调(可调整可见性、间距等)
|
||||||
|
private var tvContentConfigurator: ((TextView) -> Unit)? = null
|
||||||
|
|
||||||
/** 设置标题,为空时隐藏 */
|
/** 设置标题,为空时隐藏 */
|
||||||
fun setTitle(text: String): CommonDialog = apply { titleText = text }
|
fun setTitle(text: String): CommonDialog = apply { titleText = text }
|
||||||
@@ -42,8 +48,17 @@ open class CommonDialog(
|
|||||||
negativeClick = onClick
|
negativeClick = onClick
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置右侧确认按钮文字及点击回调(3按钮模式下对应底部按钮) */
|
/** 设置右侧确认按钮文字及点击回调(3按钮模式下对应底部按钮),点击后自动关闭弹窗 */
|
||||||
fun setPositiveButton(text: String, onClick: (() -> Unit)? = null): CommonDialog = apply {
|
fun setPositiveButton(text: String, onClick: (() -> Unit)? = null): CommonDialog = apply {
|
||||||
|
positiveText = text
|
||||||
|
positiveClick = onClick?.let { { it(); true } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置右侧确认按钮文字及可拦截的点击回调
|
||||||
|
* 回调返回 true 时关闭弹窗,返回 false 时保持弹窗显示(用于校验不通过的场景)
|
||||||
|
*/
|
||||||
|
fun setPositiveButtonInterceptable(text: String, onClick: () -> Boolean): CommonDialog = apply {
|
||||||
positiveText = text
|
positiveText = text
|
||||||
positiveClick = onClick
|
positiveClick = onClick
|
||||||
}
|
}
|
||||||
@@ -59,6 +74,16 @@ open class CommonDialog(
|
|||||||
dismissCallback = callback
|
dismissCallback = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向弹窗自定义内容区域添加 View,调用后 llContent 自动显示
|
||||||
|
* @param view 要嵌入的自定义布局(如单选列表)
|
||||||
|
* @param tvContentConfig 可选回调,用于在 pendingContentView 非空时灵活控制 tvContent 的可见性及间距等属性
|
||||||
|
*/
|
||||||
|
fun addContentView(view: View, tvContentConfig: ((TextView) -> Unit)? = null): CommonDialog = apply {
|
||||||
|
pendingContentView = view
|
||||||
|
tvContentConfigurator = tvContentConfig
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
@@ -88,8 +113,8 @@ open class CommonDialog(
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
binding.btnRight.setOnClickListener {
|
binding.btnRight.setOnClickListener {
|
||||||
positiveClick?.invoke()
|
// 回调返回 false 时阻止关闭,null 或 true 时正常关闭
|
||||||
dismiss()
|
if (positiveClick?.invoke() != false) dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3按钮点击事件(negativeClick/positiveClick 与2按钮模式共用)
|
// 3按钮点击事件(negativeClick/positiveClick 与2按钮模式共用)
|
||||||
@@ -102,11 +127,17 @@ open class CommonDialog(
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
binding.btnBottom.setOnClickListener {
|
binding.btnBottom.setOnClickListener {
|
||||||
positiveClick?.invoke()
|
if (positiveClick?.invoke() != false) dismiss()
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnDismissListener { dismissCallback?.invoke() }
|
setOnDismissListener { dismissCallback?.invoke() }
|
||||||
|
|
||||||
|
// 挂载自定义内容 View,并执行 tvContent 的灵活控制回调
|
||||||
|
pendingContentView?.let {
|
||||||
|
binding.llContent.addView(it)
|
||||||
|
binding.llContent.visibility = View.VISIBLE
|
||||||
|
tvContentConfigurator?.invoke(binding.tvContent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun show() {
|
override fun show() {
|
||||||
|
|||||||
@@ -50,27 +50,27 @@ class FoodSearchDialog(
|
|||||||
|
|
||||||
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
|
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
|
||||||
private val list = mutableListOf<GoodsItem>()
|
private val list = mutableListOf<GoodsItem>()
|
||||||
.apply {
|
// .apply {
|
||||||
add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
|
// add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
|
||||||
add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
|
// add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
|
||||||
add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
|
// add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
|
||||||
add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
|
// add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
|
||||||
add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
|
// add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
|
||||||
add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
|
// add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
|
||||||
add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
|
// add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
|
||||||
add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
|
// add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
|
||||||
add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
|
// add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
|
||||||
add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
|
// add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
|
||||||
add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
|
// add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
|
||||||
add(GoodsItem(goodsId = "200012", goodsName = "A"))
|
// add(GoodsItem(goodsId = "200012", goodsName = "A"))
|
||||||
add(GoodsItem(goodsId = "200013", goodsName = "B"))
|
// add(GoodsItem(goodsId = "200013", goodsName = "B"))
|
||||||
add(GoodsItem(goodsId = "200014", goodsName = "C"))
|
// add(GoodsItem(goodsId = "200014", goodsName = "C"))
|
||||||
add(GoodsItem(goodsId = "200015", goodsName = "D"))
|
// add(GoodsItem(goodsId = "200015", goodsName = "D"))
|
||||||
add(GoodsItem(goodsId = "200016", goodsName = "E"))
|
// add(GoodsItem(goodsId = "200016", goodsName = "E"))
|
||||||
add(GoodsItem(goodsId = "200017", goodsName = "F"))
|
// add(GoodsItem(goodsId = "200017", goodsName = "F"))
|
||||||
add(GoodsItem(goodsId = "200018", goodsName = "G"))
|
// add(GoodsItem(goodsId = "200018", goodsName = "G"))
|
||||||
add(GoodsItem(goodsId = "200019", goodsName = "H"))
|
// add(GoodsItem(goodsId = "200019", goodsName = "H"))
|
||||||
}
|
// }
|
||||||
private val adapter = FoodAdapter(list).apply {
|
private val adapter = FoodAdapter(list).apply {
|
||||||
isStateViewEnable = true
|
isStateViewEnable = true
|
||||||
setOnItemClickListener { _, _, position ->
|
setOnItemClickListener { _, _, position ->
|
||||||
@@ -135,13 +135,13 @@ class FoodSearchDialog(
|
|||||||
|
|
||||||
initObserver()
|
initObserver()
|
||||||
|
|
||||||
// // 若有默认食材名称,自动填充并触发搜索
|
// 若有默认食材名称,自动填充并触发搜索
|
||||||
// //if (!defGoodsName.isNullOrBlank()) {
|
//if (!defGoodsName.isNullOrBlank()) {
|
||||||
//binding.etSheetInput.setText(defGoodsName)
|
//binding.etSheetInput.setText(defGoodsName)
|
||||||
// pageNo = 1
|
pageNo = 1
|
||||||
// getGoodsList()
|
getGoodsList()
|
||||||
// KeyboardUtil.hideKeyboard(binding.root)
|
KeyboardUtil.hideKeyboard(binding.root)
|
||||||
// //}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 弹窗显示时在 Window.Callback 层提前拦截触摸,解决 BottomSheet 拖拽与列表滑动的手势冲突 */
|
/** 弹窗显示时在 Window.Callback 层提前拦截触摸,解决 BottomSheet 拖拽与列表滑动的手势冲突 */
|
||||||
|
|||||||
@@ -186,25 +186,25 @@ class SeasoningSelectDialog(
|
|||||||
* 请求调料列表
|
* 请求调料列表
|
||||||
*/
|
*/
|
||||||
private fun getGoodsList() {
|
private fun getGoodsList() {
|
||||||
// activity.netViewModel.queryGoodsList(
|
activity.netViewModel.queryGoodsList(
|
||||||
// goodsType = "1",
|
goodsType = "1",
|
||||||
// pageNum = pageNo,
|
pageNum = pageNo,
|
||||||
// pageSize = PAGE_SIZE,
|
pageSize = PAGE_SIZE,
|
||||||
// goodsName = goodsName.takeUnless { it.isNullOrBlank() }
|
goodsName = goodsName.takeUnless { it.isNullOrBlank() }
|
||||||
// )
|
|
||||||
|
|
||||||
// TODO: 联调时注释掉下方模拟数据,改为真实接口调用
|
|
||||||
val nameList = listOf(
|
|
||||||
"盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
|
||||||
"料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val mockData = mutableListOf<GoodsItem>()
|
// // TODO: 联调时注释掉下方模拟数据,改为真实接口调用
|
||||||
nameList.forEachIndexed { index, name ->
|
// val nameList = listOf(
|
||||||
val padString = "${index + 1}".padStart(2, '0')
|
// "盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
||||||
mockData.add(GoodsItem(goodsId = "1000$padString", goodsName = name))
|
// "料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
||||||
}
|
// )
|
||||||
loadGoodsList(mockData)
|
//
|
||||||
|
// val mockData = mutableListOf<GoodsItem>()
|
||||||
|
// nameList.forEachIndexed { index, name ->
|
||||||
|
// val padString = "${index + 1}".padStart(2, '0')
|
||||||
|
// mockData.add(GoodsItem(goodsId = "1000$padString", goodsName = name))
|
||||||
|
// }
|
||||||
|
// loadGoodsList(mockData)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ class CookFoodGoodsEntity(
|
|||||||
|
|
||||||
var materId: String? = "",
|
var materId: String? = "",
|
||||||
|
|
||||||
|
/** 物料编码 */
|
||||||
|
var goodsCode: String? = "",
|
||||||
|
|
||||||
|
/** 净材种类 */
|
||||||
|
var rawMaterialsType: String? = null,
|
||||||
|
|
||||||
var isDel: Int = 0,
|
var isDel: Int = 0,
|
||||||
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
|
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
|
||||||
) : BaseEntity, Serializable {
|
) : BaseEntity, Serializable {
|
||||||
@@ -80,10 +86,12 @@ class CookFoodGoodsEntity(
|
|||||||
popularName = popularName,
|
popularName = popularName,
|
||||||
zjmCode = zjmCode,
|
zjmCode = zjmCode,
|
||||||
materId = materId,
|
materId = materId,
|
||||||
|
materCode = goodsCode,
|
||||||
materialType = materialType,
|
materialType = materialType,
|
||||||
useWeight = useWeight,
|
useWeight = useWeight,
|
||||||
relateionType = relateionType,
|
relateionType = relateionType,
|
||||||
allEdible = allEdible,
|
allEdible = allEdible,
|
||||||
goodsOrRelationCode = goodsOrRelationCode
|
goodsOrRelationCode = goodsOrRelationCode,
|
||||||
|
rawMaterialsType = rawMaterialsType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ data class GoodsItem(
|
|||||||
val popularName: String? = null,
|
val popularName: String? = null,
|
||||||
val zjmCode: String? = null,
|
val zjmCode: String? = null,
|
||||||
val materId: String? = null,
|
val materId: String? = null,
|
||||||
|
val materCode: String? = null,
|
||||||
|
|
||||||
// 从 CookFoodGoodsEntity 迁移的数据字段(DishPartAdapter 显示及转换用)
|
// 从 CookFoodGoodsEntity 迁移的数据字段(DishPartAdapter 显示及转换用)
|
||||||
var materialType: Int = 0,
|
var materialType: Int = 0,
|
||||||
@@ -30,9 +31,18 @@ data class GoodsItem(
|
|||||||
// true-原始数据,false-非原始数据(识图新增)
|
// true-原始数据,false-非原始数据(识图新增)
|
||||||
var isOriginalData: Boolean = true,
|
var isOriginalData: Boolean = true,
|
||||||
// 识图匹配分数,-1 表示不显示
|
// 识图匹配分数,-1 表示不显示
|
||||||
var foodScore: Int = -1
|
var foodScore: Int = -1,
|
||||||
|
// 净材种类(当前选中值)
|
||||||
|
var rawMaterialsType: String? = null,
|
||||||
|
// 净材种类候选列表(临时字段,逗号分隔,不持久化)
|
||||||
|
var rawMaterialsTypes: String? = null
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物料编码,与查询接口返回的 materCode 同值,统一对外暴露为 goodsCode 供下游使用
|
||||||
|
*/
|
||||||
|
val goodsCode: String? get() = materCode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换为 CookFoodGoodsEntity,用于写入数据库或传给 SubmitFoodActivity
|
* 转换为 CookFoodGoodsEntity,用于写入数据库或传给 SubmitFoodActivity
|
||||||
*/
|
*/
|
||||||
@@ -42,11 +52,13 @@ data class GoodsItem(
|
|||||||
popularName = popularName,
|
popularName = popularName,
|
||||||
zjmCode = zjmCode,
|
zjmCode = zjmCode,
|
||||||
materId = materId,
|
materId = materId,
|
||||||
|
goodsCode = goodsCode,
|
||||||
materialType = materialType,
|
materialType = materialType,
|
||||||
useWeight = useWeight,
|
useWeight = useWeight,
|
||||||
relateionType = relateionType,
|
relateionType = relateionType,
|
||||||
allEdible = allEdible,
|
allEdible = allEdible,
|
||||||
goodsOrRelationCode = goodsOrRelationCode
|
goodsOrRelationCode = goodsOrRelationCode,
|
||||||
|
rawMaterialsType = rawMaterialsType
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,9 +70,11 @@ data class GoodsItem(
|
|||||||
popularName = popularName,
|
popularName = popularName,
|
||||||
zjmCode = zjmCode,
|
zjmCode = zjmCode,
|
||||||
materId = materId,
|
materId = materId,
|
||||||
|
goodsCode = goodsCode,
|
||||||
materialType = materialType,
|
materialType = materialType,
|
||||||
relateionType = relateionType,
|
relateionType = relateionType,
|
||||||
allEdible = allEdible,
|
allEdible = allEdible,
|
||||||
goodsOrRelationCode = goodsOrRelationCode
|
goodsOrRelationCode = goodsOrRelationCode,
|
||||||
|
rawMaterialsType = rawMaterialsType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ data class SeasoningEntity(
|
|||||||
|
|
||||||
var materId: String? = "",
|
var materId: String? = "",
|
||||||
|
|
||||||
|
/** 物料编码 */
|
||||||
|
var goodsCode: String? = "",
|
||||||
|
|
||||||
|
/** 净材种类 */
|
||||||
|
var rawMaterialsType: String? = null,
|
||||||
|
|
||||||
var sort: Int = 9999,
|
var sort: Int = 9999,
|
||||||
/**
|
/**
|
||||||
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
|
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
|
||||||
|
|||||||
@@ -4,9 +4,14 @@ import com.shuwei.dish.match.base.GlobalData
|
|||||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
import com.shuwei.dish.match.entity.FoodRecord
|
||||||
import com.shuwei.dish.match.entity.GoodsItem
|
import com.shuwei.dish.match.entity.GoodsItem
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Multipart
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Part
|
||||||
|
import retrofit2.http.PartMap
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
import retrofit2.http.Url
|
import retrofit2.http.Url
|
||||||
|
|
||||||
@@ -58,4 +63,14 @@ interface ApiService {
|
|||||||
@Body param: MutableMap<String, Any>
|
@Body param: MutableMap<String, Any>
|
||||||
): ApiResponse<MutableList<GoodsItem>?>
|
): ApiResponse<MutableList<GoodsItem>?>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交采集图片数据
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST
|
||||||
|
suspend fun uploadFoodVectorData(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/addWeighingCollectionData",
|
||||||
|
@PartMap params: Map<String, RequestBody>,
|
||||||
|
@Part foodPics: List<MultipartBody.Part>
|
||||||
|
): ApiResponse<List<String>?>
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络请求 ViewModel,所有网络操作通过 RemoteRepository 发起,不直接依赖 apiService
|
* 网络请求 ViewModel,所有网络操作通过 RemoteRepository 发起,不直接依赖 apiService
|
||||||
@@ -126,4 +131,44 @@ class NetViewModel(
|
|||||||
_goodsListState.value = repository.queryGoodsList(param)
|
_goodsListState.value = repository.queryGoodsList(param)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传采集菜品信息 UI 状态流,UI 层通过 collect 监听
|
||||||
|
*/
|
||||||
|
private val _uploadCollectState = MutableStateFlow<UiState<List<String>?>>(UiState.Idle)
|
||||||
|
val uploadCollectState: StateFlow<UiState<List<String>?>> = _uploadCollectState.asStateFlow()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传采集菜品信息(触发 StateFlow,适合单次上传场景)
|
||||||
|
*/
|
||||||
|
fun uploadFoodVectorData(
|
||||||
|
fileList: List<File>,
|
||||||
|
params: MutableMap<String, RequestBody>
|
||||||
|
) {
|
||||||
|
val fileParts = fileList.map { file ->
|
||||||
|
file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
|
||||||
|
.let { MultipartBody.Part.createFormData("foodPics", file.name, it) }
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uploadCollectState.value = UiState.Loading
|
||||||
|
_uploadCollectState.value = repository.uploadFoodVectorData(params, fileParts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传采集菜品信息(直接返回结果,适合分批循环上传场景)
|
||||||
|
* @param fileList 图片文件列表
|
||||||
|
* @param params 附加参数
|
||||||
|
* @return UiState 包装的结果
|
||||||
|
*/
|
||||||
|
suspend fun uploadFoodVectorDataBatch(
|
||||||
|
fileList: List<File>,
|
||||||
|
params: MutableMap<String, RequestBody>
|
||||||
|
): UiState<List<String>?> {
|
||||||
|
val fileParts = fileList.map { file ->
|
||||||
|
file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
|
||||||
|
.let { MultipartBody.Part.createFormData("foodPics", file.name, it) }
|
||||||
|
}
|
||||||
|
return repository.uploadFoodVectorData(params, fileParts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package com.shuwei.dish.match.net
|
|||||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
import com.shuwei.dish.match.entity.FoodRecord
|
||||||
import com.shuwei.dish.match.entity.GoodsItem
|
import com.shuwei.dish.match.entity.GoodsItem
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络数据仓库,封装所有 ApiService 调用
|
* 网络数据仓库,封装所有 ApiService 调用
|
||||||
@@ -90,4 +93,24 @@ class RemoteRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上次采集数据
|
||||||
|
* @param params 参数
|
||||||
|
* @param foodPics 图片
|
||||||
|
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
|
||||||
|
*/
|
||||||
|
suspend fun uploadFoodVectorData(
|
||||||
|
params: Map<String, RequestBody>,
|
||||||
|
foodPics: List<MultipartBody.Part>
|
||||||
|
): UiState<List<String>?> {
|
||||||
|
return try {
|
||||||
|
val resp = apiService.uploadFoodVectorData(params = params, foodPics = foodPics)
|
||||||
|
if (resp.isSuccess()) UiState.Success(resp.data)
|
||||||
|
else UiState.Error(resp.code, resp.msg ?: "")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val ex = getApiException(e)
|
||||||
|
UiState.Error("-1", ex.errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ data class Food(
|
|||||||
var name: String? = null,
|
var name: String? = null,
|
||||||
var foodIdx: Int = 0,
|
var foodIdx: Int = 0,
|
||||||
@HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT)
|
@HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT)
|
||||||
var foodVector: FloatArray? = null
|
var foodVector: FloatArray? = null,
|
||||||
|
var version: String? = null
|
||||||
) {
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@@ -23,6 +24,7 @@ data class Food(
|
|||||||
if (foodIdx != other.foodIdx) return false
|
if (foodIdx != other.foodIdx) return false
|
||||||
if (name != other.name) return false
|
if (name != other.name) return false
|
||||||
if (!foodVector.contentEquals(other.foodVector)) return false
|
if (!foodVector.contentEquals(other.foodVector)) return false
|
||||||
|
if (version != other.version) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -32,6 +34,8 @@ data class Food(
|
|||||||
result = 31 * result + foodIdx
|
result = 31 * result + foodIdx
|
||||||
result = 31 * result + (name?.hashCode() ?: 0)
|
result = 31 * result + (name?.hashCode() ?: 0)
|
||||||
result = 31 * result + (foodVector?.contentHashCode() ?: 0)
|
result = 31 * result + (foodVector?.contentHashCode() ?: 0)
|
||||||
|
result = 31 * result + (version?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,9 @@ class ScaleWebSocketClient {
|
|||||||
Log.d(TAG, "放弃过期连接任务: $deviceId, version=$version")
|
Log.d(TAG, "放弃过期连接任务: $deviceId, version=$version")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val url = "ws://$host:$port"
|
// IPv6 地址在 URL 中需用方括号包裹,否则 OkHttp 会将冒号后内容误解析为端口
|
||||||
|
val formattedHost = if (host.contains(':')) "[$host]" else host
|
||||||
|
val url = "ws://$formattedHost:$port"
|
||||||
val request = Request.Builder().url(url).build()
|
val request = Request.Builder().url(url).build()
|
||||||
|
|
||||||
val ws = httpClient.newWebSocket(request, object : WebSocketListener() {
|
val ws = httpClient.newWebSocket(request, object : WebSocketListener() {
|
||||||
|
|||||||
@@ -176,26 +176,26 @@ class FoodRecognizeActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
currentWeight = intent.getDoubleExtra(EXTRA_CURRENT_WEIGHT, 0.0)
|
currentWeight = intent.getDoubleExtra(EXTRA_CURRENT_WEIGHT, 0.0)
|
||||||
binding.tvWeight.text = "${currentWeight}g"
|
binding.tvWeight.text = "${currentWeight}g"
|
||||||
val tempList = arrayListOf<GoodsItem>()
|
// val tempList = arrayListOf<GoodsItem>()
|
||||||
recognizeNameList?.apply {
|
recognizeNameList?.apply {
|
||||||
forEach {
|
forEach {
|
||||||
val goodsName = it.name.split("WP").first()
|
val goodsName = it.name.split("WP").first()
|
||||||
goodsNameList.add(goodsName)
|
goodsNameList.add(goodsName)
|
||||||
nameScoreMap.put(goodsName, it)
|
nameScoreMap.put(goodsName, it)
|
||||||
|
|
||||||
// TODO: 测试数据,临时使用-------------------------
|
// // TODO: 测试数据,临时使用-------------------------
|
||||||
val testData = buildTestData(it)
|
// val testData = buildTestData(it)
|
||||||
tempList.add(testData)
|
// tempList.add(testData)
|
||||||
// TODO: 测试数据,临时使用-------------------------
|
// // TODO: 测试数据,临时使用-------------------------
|
||||||
}
|
}
|
||||||
// TODO: 测试数据,临时使用-------------------------
|
// // TODO: 测试数据,临时使用-------------------------
|
||||||
loadRecognizeList(tempList)
|
// loadRecognizeList(tempList)
|
||||||
// TODO: 测试数据,临时使用-------------------------
|
// // TODO: 测试数据,临时使用-------------------------
|
||||||
recognizeNameList = null
|
recognizeNameList = null
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: 注释掉等接口正常再调用,临时使用测试数据
|
//TODO: 注释掉等接口正常再调用,临时使用测试数据
|
||||||
//netViewModel.queryGoodsList(goodsType = "0", goodsNames = goodsNameList)
|
netViewModel.queryGoodsList(goodsType = "0", goodsNames = goodsNameList)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildTestData(item: FoodModule.IdNameScore): GoodsItem {
|
private fun buildTestData(item: FoodModule.IdNameScore): GoodsItem {
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.core.content.IntentCompat
|
import androidx.core.content.IntentCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
@@ -31,7 +28,6 @@ import com.shuwei.dish.match.objbox.FoodModule
|
|||||||
import com.shuwei.dish.match.utils.AddressUtil
|
import com.shuwei.dish.match.utils.AddressUtil
|
||||||
import com.shuwei.dish.match.utils.CameraUtils
|
import com.shuwei.dish.match.utils.CameraUtils
|
||||||
import com.shuwei.dish.match.utils.ImageUtil
|
import com.shuwei.dish.match.utils.ImageUtil
|
||||||
import com.shuwei.dish.match.utils.KeyboardUtil
|
|
||||||
import com.shuwei.dish.match.utils.SpTool
|
import com.shuwei.dish.match.utils.SpTool
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||||
@@ -43,7 +39,6 @@ import com.shuwei.dish.match.utils.ext.toJsonString
|
|||||||
import com.shuwei.dish.match.utils.ext.toast
|
import com.shuwei.dish.match.utils.ext.toast
|
||||||
import com.shuwei.dish.match.utils.ext.visible
|
import com.shuwei.dish.match.utils.ext.visible
|
||||||
import com.yanzhenjie.recyclerview.SwipeMenuItem
|
import com.yanzhenjie.recyclerview.SwipeMenuItem
|
||||||
import com.yanzhenjie.recyclerview.SwipeRecyclerView
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -56,11 +51,11 @@ import kotlin.math.abs
|
|||||||
class PrepareFoodActivity : BaseActivity() {
|
class PrepareFoodActivity : BaseActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "CookActivity"
|
const val TAG = "PrepareFoodActivity"
|
||||||
const val FOOD_ITEM = "foodItem"
|
const val FOOD_ITEM = "foodItem"
|
||||||
const val PAGE_FROM = "pageFrom"
|
const val PAGE_FROM = "pageFrom"
|
||||||
const val HOME = "home"
|
const val HOME = "home"
|
||||||
const val WEIGHT_CHANGE_VALUE = 15
|
const val WEIGHT_CHANGE_VALUE = 5
|
||||||
const val WEIGHT_RECOGNIZE_VALUE = 10
|
const val WEIGHT_RECOGNIZE_VALUE = 10
|
||||||
|
|
||||||
/** 主材数量上限 */
|
/** 主材数量上限 */
|
||||||
@@ -224,9 +219,9 @@ class PrepareFoodActivity : BaseActivity() {
|
|||||||
FoodRecognizeActivity.close()
|
FoodRecognizeActivity.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!pageVisible || manualCancelFlag || isTakingPhoto.get() || showRecognizePage || abs(
|
if (
|
||||||
lastWeight - weight
|
!pageVisible || manualCancelFlag || isTakingPhoto.get() || showRecognizePage ||
|
||||||
) <= WEIGHT_CHANGE_VALUE
|
abs(lastWeight - weight) <= WEIGHT_CHANGE_VALUE
|
||||||
) {
|
) {
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class SettingActivity : BaseActivity() {
|
|||||||
.setTitle("数据清除")
|
.setTitle("数据清除")
|
||||||
.setContent("将物理删除全部 4 张表数据(菜品、食材、调料、槽位),此操作不可恢复,同时重置菜品模式和食堂id,确认继续?")
|
.setContent("将物理删除全部 4 张表数据(菜品、食材、调料、槽位),此操作不可恢复,同时重置菜品模式和食堂id,确认继续?")
|
||||||
.setNegativeButton("取消")
|
.setNegativeButton("取消")
|
||||||
.setPositiveButton("确认清除") {
|
.setPositiveButton("确认") {
|
||||||
appViewModel.clearAllData {
|
appViewModel.clearAllData {
|
||||||
SpTool.cookMode = -1
|
SpTool.cookMode = -1
|
||||||
SpTool.canteenId = "0"
|
SpTool.canteenId = "0"
|
||||||
|
|||||||
@@ -293,11 +293,6 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
toast("未获取到菜品或构成信息")
|
toast("未获取到菜品或构成信息")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val seasoningData = seasoningAdapter.items
|
|
||||||
// if (seasoningData.isEmpty()) {
|
|
||||||
// toast("未获取到调料信息")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
showLoading()
|
showLoading()
|
||||||
|
|
||||||
val isSamplingData = food!!.cookMode == 1
|
val isSamplingData = food!!.cookMode == 1
|
||||||
@@ -316,23 +311,11 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
else -> it.dinnerType
|
else -> it.dinnerType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 将调料数据转为 CookFoodGoodsEntity 追加到 goodsList
|
// 先移除 goodsList 中已有的调料数据(materialType==3),再以 adapter 中最新调料覆盖
|
||||||
val tempSeasoningList = seasoningData.map { item ->
|
goodsList?.removeAll { it.materialType == 3 }
|
||||||
val seasoning = appViewModel.getSeasoningByGoodsId(item.goodsId)
|
seasoningAdapter.items
|
||||||
CookFoodGoodsEntity().also { entity ->
|
.filter { it.useWeight > 0.0 }
|
||||||
entity.goodsId = item.goodsId
|
.forEach { goodsList?.add(buildSeasoningEntity(it)) }
|
||||||
entity.goodsName = item.goodsName
|
|
||||||
entity.useWeight = item.useWeight
|
|
||||||
entity.materialType = 3
|
|
||||||
entity.popularName = seasoning?.popularName
|
|
||||||
entity.zjmCode = seasoning?.zjmCode
|
|
||||||
entity.materId = seasoning?.materId
|
|
||||||
entity.relateionType = seasoning?.relateionType ?: 0
|
|
||||||
entity.allEdible = seasoning?.allEdible ?: true
|
|
||||||
entity.goodsOrRelationCode = seasoning?.goodsOrRelationCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goodsList?.addAll(tempSeasoningList)
|
|
||||||
appViewModel.saveCookFoodAndGoods(
|
appViewModel.saveCookFoodAndGoods(
|
||||||
cookMode = food!!.cookMode,
|
cookMode = food!!.cookMode,
|
||||||
entity = cookFoodEntity,
|
entity = cookFoodEntity,
|
||||||
@@ -348,28 +331,14 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
toast("未获取到菜品或构成信息")
|
toast("未获取到菜品或构成信息")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val seasoningData = seasoningAdapter.items
|
|
||||||
showLoading()
|
showLoading()
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
// 将重量大于0且不重复的调料追加到 goodsList
|
// 先移除 goodsList 中已有的调料数据(materialType==3),再以 adapter 中最新调料覆盖
|
||||||
seasoningData.forEach { item ->
|
goodsList?.removeAll { it.materialType == 3 }
|
||||||
if (item.useWeight > 0.0 && goodsList?.none { it.goodsId == item.goodsId } == true) {
|
seasoningAdapter.items
|
||||||
val seasoning = appViewModel.getSeasoningByGoodsId(item.goodsId)
|
.filter { it.useWeight > 0.0 }
|
||||||
goodsList?.add(CookFoodGoodsEntity().also { entity ->
|
.forEach { goodsList?.add(buildSeasoningEntity(it)) }
|
||||||
entity.goodsId = item.goodsId
|
|
||||||
entity.goodsName = item.goodsName
|
|
||||||
entity.useWeight = item.useWeight
|
|
||||||
entity.materialType = 3
|
|
||||||
entity.popularName = seasoning?.popularName
|
|
||||||
entity.zjmCode = seasoning?.zjmCode
|
|
||||||
entity.materId = seasoning?.materId
|
|
||||||
entity.relateionType = seasoning?.relateionType ?: 0
|
|
||||||
entity.allEdible = seasoning?.allEdible ?: true
|
|
||||||
entity.goodsOrRelationCode = seasoning?.goodsOrRelationCode
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookFoodEntity.let {
|
cookFoodEntity.let {
|
||||||
it.matchingConstituteInfoList = this@SubmitFoodActivity.goodsList
|
it.matchingConstituteInfoList = this@SubmitFoodActivity.goodsList
|
||||||
@@ -392,6 +361,27 @@ class SubmitFoodActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 adapter 中的调料 item 转换为 CookFoodGoodsEntity
|
||||||
|
* 从本地 dm_seasoning 表补充完整字段(popularName、zjmCode、materId、goodsCode 等)
|
||||||
|
*/
|
||||||
|
private suspend fun buildSeasoningEntity(item: SeasoningWeightAdapter.Item): CookFoodGoodsEntity {
|
||||||
|
val seasoning = appViewModel.getSeasoningByGoodsId(item.goodsId)
|
||||||
|
return CookFoodGoodsEntity().also { entity ->
|
||||||
|
entity.goodsId = item.goodsId
|
||||||
|
entity.goodsName = item.goodsName
|
||||||
|
entity.useWeight = item.useWeight
|
||||||
|
entity.materialType = 3
|
||||||
|
entity.popularName = seasoning?.popularName
|
||||||
|
entity.zjmCode = seasoning?.zjmCode
|
||||||
|
entity.materId = seasoning?.materId
|
||||||
|
entity.goodsCode = seasoning?.goodsCode
|
||||||
|
entity.relateionType = seasoning?.relateionType ?: 0
|
||||||
|
entity.allEdible = seasoning?.allEdible ?: true
|
||||||
|
entity.goodsOrRelationCode = seasoning?.goodsOrRelationCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun submitSuccess(isSamplingData: Boolean) {
|
private fun submitSuccess(isSamplingData: Boolean) {
|
||||||
if (food?.foodId.isNullOrBlank()) {
|
if (food?.foodId.isNullOrBlank()) {
|
||||||
dismissLoading()
|
dismissLoading()
|
||||||
|
|||||||
@@ -11,30 +11,46 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.widget.RadioButton
|
||||||
|
import android.widget.RadioGroup
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.core.view.doOnLayout
|
import androidx.core.view.doOnLayout
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
import com.shuwei.dish.match.adapter.VectorCollectionAdapter
|
import com.shuwei.dish.match.adapter.VectorCollectionAdapter
|
||||||
import com.shuwei.dish.match.adapter.Food3Adapter
|
import com.shuwei.dish.match.adapter.Food3Adapter
|
||||||
import com.shuwei.dish.match.base.BaseFragment
|
import com.shuwei.dish.match.base.BaseFragment
|
||||||
import com.shuwei.dish.match.databinding.FragmentVectorCollectionBinding
|
import com.shuwei.dish.match.databinding.FragmentVectorCollectionBinding
|
||||||
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
||||||
|
import com.shuwei.dish.match.dialog.CommonDialog
|
||||||
import com.shuwei.dish.match.dialog.Loading
|
import com.shuwei.dish.match.dialog.Loading
|
||||||
import com.shuwei.dish.match.entity.FoodCollectionBean
|
import com.shuwei.dish.match.entity.FoodCollectionBean
|
||||||
import com.shuwei.dish.match.entity.GoodsItem
|
import com.shuwei.dish.match.entity.GoodsItem
|
||||||
|
import com.shuwei.dish.match.net.UiState
|
||||||
|
import com.shuwei.dish.match.objbox.Food
|
||||||
import com.shuwei.dish.match.objbox.FoodModule
|
import com.shuwei.dish.match.objbox.FoodModule
|
||||||
|
import com.shuwei.dish.match.objbox.ObjectBox
|
||||||
import com.shuwei.dish.match.ui.CollectedFoodActivity
|
import com.shuwei.dish.match.ui.CollectedFoodActivity
|
||||||
import com.shuwei.dish.match.ui.SingleFragmentActivity
|
import com.shuwei.dish.match.ui.SingleFragmentActivity
|
||||||
import com.shuwei.dish.match.utils.BitmapSaver
|
import com.shuwei.dish.match.utils.BitmapSaver
|
||||||
import com.shuwei.dish.match.utils.CameraUtils
|
import com.shuwei.dish.match.utils.CameraUtils
|
||||||
import com.shuwei.dish.match.utils.Debouncer
|
import com.shuwei.dish.match.utils.Debouncer
|
||||||
|
import com.shuwei.dish.match.utils.ImageUploader
|
||||||
import com.shuwei.dish.match.utils.ImageUtil
|
import com.shuwei.dish.match.utils.ImageUtil
|
||||||
import com.shuwei.dish.match.utils.LogSaveUtil
|
import com.shuwei.dish.match.utils.LogSaveUtil
|
||||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||||
import com.shuwei.dish.match.utils.ext.dp
|
import com.shuwei.dish.match.utils.ext.dp
|
||||||
|
import com.shuwei.dish.match.utils.ext.gone
|
||||||
import com.shuwei.dish.match.utils.ext.toast
|
import com.shuwei.dish.match.utils.ext.toast
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import androidx.core.content.withStyledAttributes
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>() {
|
class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>() {
|
||||||
@@ -53,30 +69,15 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val searchList = mutableListOf<GoodsItem>().apply {
|
private val searchList = mutableListOf<GoodsItem>()
|
||||||
add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
|
|
||||||
add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
|
|
||||||
add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
|
|
||||||
add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
|
|
||||||
add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
|
|
||||||
add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
|
|
||||||
add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
|
|
||||||
add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
|
|
||||||
add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
|
|
||||||
add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
|
|
||||||
add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
|
|
||||||
}
|
|
||||||
private var checkedItem: GoodsItem? = null
|
private var checkedItem: GoodsItem? = null
|
||||||
private val searchAdapter by lazy {
|
private val searchAdapter by lazy {
|
||||||
Food3Adapter(searchList).apply {
|
Food3Adapter(searchList).apply {
|
||||||
setOnItemClickListener { adapter, view, position ->
|
setOnItemClickListener { _, _, position ->
|
||||||
searchList.forEachIndexed { index, item -> item.isClicked = index == position }
|
val item = searchList[position]
|
||||||
checkedItem = searchList[position]
|
val types = item.rawMaterialsTypes
|
||||||
notifyDataSetChanged()
|
// 弹出净材种类单选弹窗
|
||||||
checkedItem?.let {
|
showRawMaterialsDialog(item, position, types)
|
||||||
selectedFoodId = it.goodsId
|
|
||||||
selectedFoodName = it.goodsName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,15 +112,16 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
|
|
||||||
//typealias CameraCallback = (Uri) -> Unit
|
//typealias CameraCallback = (Uri) -> Unit
|
||||||
|
|
||||||
private val cameraCallback: (Uri) -> Unit = cameraCallback@{ uri ->
|
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||||
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val index = vectorList.indexOfFirst { it.imageFile == null }
|
val index = vectorList.indexOfFirst { it.imageFile == null }
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
toast("每次只允许保存${MAX_COUNT}条数据")
|
toast("每次只允许保存${MAX_COUNT}条数据")
|
||||||
hideWaitingDialog()
|
hideWaitingDialog()
|
||||||
return@cameraCallback
|
return@launch
|
||||||
}
|
}
|
||||||
activity?.runOnUiThread {
|
// 主线程更新 UI
|
||||||
vectorList[index].let {
|
vectorList[index].let {
|
||||||
it.imageVector = null
|
it.imageVector = null
|
||||||
it.bitmap = null
|
it.bitmap = null
|
||||||
@@ -128,13 +130,11 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
it.imageUri = uri
|
it.imageUri = uri
|
||||||
}
|
}
|
||||||
vectorAdapter.notifyItemChanged(index)
|
vectorAdapter.notifyItemChanged(index)
|
||||||
|
// 切到 IO 线程执行耗时的 Bitmap 解码
|
||||||
|
val bitmap = withContext(Dispatchers.IO) {
|
||||||
|
ImageUtil.uriToBitmap(requireActivity(), uri)
|
||||||
}
|
}
|
||||||
// hideWaitingDialog()
|
bitmap?.let { getImageVector(index, it) }
|
||||||
Thread {
|
|
||||||
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
|
|
||||||
getImageVector(index, bitmap)
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
hideWaitingDialog()
|
hideWaitingDialog()
|
||||||
@@ -142,28 +142,30 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
log("程序异常${e.message}")
|
log("程序异常${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getImageVector(index: Int, bitmap: Bitmap) {
|
|
||||||
// val bitmap = BitmapCropper.cropCenter(
|
|
||||||
// original = srcBmp,
|
|
||||||
// targetWidth = 900, targetHeight = 900,
|
|
||||||
//// offsetX = 30, offsetY = 100
|
|
||||||
// )
|
|
||||||
val imageVector = try {
|
|
||||||
FoodModule.bitmap2FloatArray(bitmap, false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
toast("操作失败")
|
|
||||||
log("操作失败:${e.message}")
|
|
||||||
hideWaitingDialog()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getImageVector(index: Int, bitmap: Bitmap) {
|
||||||
|
// 切到 IO 线程执行耗时的向量计算和文件保存
|
||||||
|
val (imageVector, file) = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val vector = FoodModule.bitmap2FloatArray(bitmap, false)
|
||||||
val file = BitmapSaver.saveToAppFilesDir(
|
val file = BitmapSaver.saveToAppFilesDir(
|
||||||
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
)
|
)
|
||||||
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||||
|
vector to file
|
||||||
activity?.runOnUiThread {
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
log("操作失败:${e.message}")
|
||||||
|
null to null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 回到主线程处理结果
|
||||||
|
if (imageVector == null) {
|
||||||
|
toast("操作失败")
|
||||||
|
hideWaitingDialog()
|
||||||
|
return
|
||||||
|
}
|
||||||
vectorList[index].let {
|
vectorList[index].let {
|
||||||
it.imageVector = imageVector
|
it.imageVector = imageVector
|
||||||
it.bitmap = null
|
it.bitmap = null
|
||||||
@@ -171,10 +173,7 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
it.imageFile = file
|
it.imageFile = file
|
||||||
}
|
}
|
||||||
vectorAdapter.notifyItemChanged(index)
|
vectorAdapter.notifyItemChanged(index)
|
||||||
}
|
if (bitmap.isRecycled.not()) bitmap.recycle()
|
||||||
if (bitmap.isRecycled.not()) {
|
|
||||||
bitmap.recycle()
|
|
||||||
}
|
|
||||||
hideWaitingDialog()
|
hideWaitingDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,16 +234,16 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.btnSave.setOnClickListener {
|
binding.btnSave.setOnClickListener {
|
||||||
// if (checkedItem == null || checkedItem!!.isChecked.not()) {
|
if (checkedItem == null || checkedItem!!.isClicked.not()) {
|
||||||
// toast("请选择菜品名称")
|
toast("请选择菜品名称")
|
||||||
// return@setOnClickListener
|
return@setOnClickListener
|
||||||
// }
|
}
|
||||||
// val count = foodCollectionList.count { it.imageFile != null }
|
val count = vectorList.count { it.imageFile != null }
|
||||||
// if (count == 0) {
|
if (count == 0) {
|
||||||
// toast("请拍摄菜品照片")
|
toast("请拍摄菜品照片")
|
||||||
// return@setOnClickListener
|
return@setOnClickListener
|
||||||
// }
|
}
|
||||||
// upload()
|
upload()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
|
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
|
||||||
@@ -279,79 +278,196 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
searchFood()
|
searchFood()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
private fun upload() {
|
private fun upload() {
|
||||||
// lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
// val totalFileCount = foodCollectionList.count { it.imageFile != null }
|
val totalFileCount = vectorList.count { it.imageFile != null }
|
||||||
// showWaitingDialog2("图片上传中0/$totalFileCount")
|
showWaitingDialog("图片上传中0/$totalFileCount")
|
||||||
// val params = HashMap<String, RequestBody>()
|
val params = mutableMapOf<String, RequestBody>()
|
||||||
// //params["placeId"] = restId.toRequestBody()
|
params["goodsId"] = checkedItem!!.goodsId.toRequestBody()
|
||||||
// params["foodId"] = checkedItem!!.foodId.toRequestBody()
|
params["goodsName"] = checkedItem!!.goodsName!!.toRequestBody()
|
||||||
// params["foodName"] = checkedItem!!.foodName!!.toRequestBody()
|
//净材种类,逗号分隔
|
||||||
// params["version"] = GlobalData.foodModelVersion.toRequestBody()
|
// params["rawMaterialsTypes"] = "".toRequestBody()
|
||||||
//
|
//params["version"] = "1.0.0".toRequestBody()
|
||||||
// ImageUploader(totalList = foodCollectionList, uploadImage = { batch ->
|
|
||||||
// val files = batch.map { it.imageFile }
|
ImageUploader(totalList = vectorList, uploadImage = { batch ->
|
||||||
// val foodVectorList = batch.filter { it.imageVector != null }.map {
|
val files = batch.mapNotNull { it.imageFile }
|
||||||
// it.imageVector!!.joinToString(
|
val foodVectorList = batch.filter { it.imageVector != null }.map {
|
||||||
// separator = ",", prefix = "[", postfix = "]"
|
it.imageVector!!.joinToString(
|
||||||
// )
|
separator = ",", prefix = "[", postfix = "]"
|
||||||
// }
|
)
|
||||||
// val foodVectorJson =
|
}
|
||||||
// foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
val foodVectorJson =
|
||||||
// log("json=$foodVectorJson")
|
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||||
// params["foodVector"] = foodVectorJson.toRequestBody()
|
log("json=$foodVectorJson")
|
||||||
// viewModel?.uploadCollectFoodPics(files, params)
|
params["foodVector"] = foodVectorJson.toRequestBody()
|
||||||
// }, onProgress = { count, batch, idList ->
|
// 直接 await 单批上传结果,成功返回 id 列表,失败返回 null 触发 onError
|
||||||
// //batch.forEach {
|
val result = currentActivity.netViewModel.uploadFoodVectorDataBatch(files, params)
|
||||||
// // it.uploadSuccess = true
|
if (result is UiState.Success) result.data else null
|
||||||
// //}
|
}, onProgress = { count, batch, idList ->
|
||||||
// runBlocking {
|
showWaitingDialog("图片上传中$count/$totalFileCount")
|
||||||
// activity?.runOnUiThread {
|
val foodList = batch.mapIndexed { index, it ->
|
||||||
// showWaitingDialog("图片上传中$count/$totalFileCount")
|
Food(
|
||||||
// }
|
name = checkedItem!!.goodsName,
|
||||||
// val foodList = batch.mapIndexed { index, it ->
|
foodVector = it.imageVector,
|
||||||
// Food(
|
)
|
||||||
// collectId = if (index < idList.size) idList[index] else null,
|
}
|
||||||
// foodId = checkedItem!!.foodId,
|
ObjectBox.putAll(foodList)
|
||||||
// foodName = checkedItem!!.foodName,
|
batch.forEach { it.isFinish = true }
|
||||||
// foodVector = it.imageVector,
|
vectorAdapter.notifyDataSetChanged()
|
||||||
// version = GlobalData.foodModelVersion
|
}, onError = {
|
||||||
// )
|
binding.root.postDelayed({
|
||||||
// }
|
hideWaitingDialog()
|
||||||
// ObjectBox.putAll(foodList)
|
toast("上传失败,请稍后重试")
|
||||||
// activity?.runOnUiThread {
|
}, 1000)
|
||||||
// batch.forEach { it.isFinish = true }
|
}, onComplete = {
|
||||||
// collectionAdapter.notifyDataSetChanged()
|
binding.root.postDelayed({
|
||||||
// }
|
hideWaitingDialog()
|
||||||
// }
|
toast("上传成功")
|
||||||
// }, onError = {
|
}, 1000)
|
||||||
// activity?.runOnUiThread {
|
}).processUploads()
|
||||||
// binding.root.postDelayed({
|
}
|
||||||
// hideWaitingDialog()
|
|
||||||
// toast("上传失败,请稍后重试")
|
|
||||||
// }, 1000)
|
|
||||||
// }
|
|
||||||
// }, onComplete = {
|
|
||||||
// //vectorThread()
|
|
||||||
// binding.root.postDelayed({
|
|
||||||
// hideWaitingDialog()
|
|
||||||
// toast("上传成功")
|
|
||||||
// }, 1000)
|
|
||||||
// }).processUploads()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun searchFood() {
|
fun searchFood() {
|
||||||
debouncer.debounce {
|
debouncer.debounce {
|
||||||
searchByFoodName(binding.editFoodName.text.toString()) {
|
val keyword = binding.editFoodName.text.toString().trim()
|
||||||
// searchFoodList.clear()
|
// 通过 NetViewModel 查询净材数据(goodsType=0 食材)
|
||||||
// searchFoodList.addAll(it)
|
currentActivity.netViewModel.queryGoodsList(
|
||||||
// searchFoodAdapter.notifyDataSetChanged()
|
goodsType = "0",
|
||||||
|
goodsName = keyword.ifEmpty { null }
|
||||||
|
)
|
||||||
|
// 监听查询结果(一次性收集,避免重复订阅)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
currentActivity.netViewModel.goodsListState.collect { state ->
|
||||||
|
when (state) {
|
||||||
|
is UiState.Success -> {
|
||||||
|
var list: List<GoodsItem> = state.data ?: emptyList()
|
||||||
|
// TODO: 接口暂无数据时使用假数据进行效果测试,联调完成后删除
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
list = mutableListOf(
|
||||||
|
GoodsItem(goodsId = "1", goodsName = "土豆", rawMaterialsType = "土豆丝,土豆条,土豆片,土豆丁"),
|
||||||
|
GoodsItem(goodsId = "2", goodsName = "胡萝卜", rawMaterialsType = "胡萝卜丝,胡萝卜片,胡萝卜丁"),
|
||||||
|
GoodsItem(goodsId = "3", goodsName = "黄瓜", rawMaterialsType = "黄瓜丝,黄瓜片,黄瓜丁,黄瓜块"),
|
||||||
|
GoodsItem(goodsId = "4", goodsName = "洋葱", rawMaterialsType = "洋葱丝,洋葱丁,洋葱圈"),
|
||||||
|
GoodsItem(goodsId = "5", goodsName = "白菜", rawMaterialsType = "白菜丝,白菜块,白菜叶"),
|
||||||
|
GoodsItem(goodsId = "6", goodsName = "豆腐", rawMaterialsType = "豆腐块,豆腐丁"),
|
||||||
|
GoodsItem(goodsId = "7", goodsName = "猪肉", rawMaterialsType = "猪肉丝,猪肉片,猪肉丁,猪肉块"),
|
||||||
|
GoodsItem(goodsId = "8", goodsName = "鸡胸肉", rawMaterialsType = "鸡胸肉丝,鸡胸肉片,鸡胸肉丁"),
|
||||||
|
GoodsItem(goodsId = "9", goodsName = "牛肉", rawMaterialsType = "牛肉丝,牛肉片,牛肉块"),
|
||||||
|
GoodsItem(goodsId = "10", goodsName = "虾仁", rawMaterialsType = "整虾仁,切段虾仁"),
|
||||||
|
GoodsItem(goodsId = "11", goodsName = "茄子", rawMaterialsType = "茄子丝,茄子片,茄子块,茄子条"),
|
||||||
|
GoodsItem(goodsId = "12", goodsName = "青椒", rawMaterialsType = "青椒丝,青椒片,青椒块"),
|
||||||
|
GoodsItem(goodsId = "13", goodsName = "西红柿", rawMaterialsType = "西红柿片,西红柿块,西红柿丁"),
|
||||||
|
GoodsItem(goodsId = "14", goodsName = "芹菜", rawMaterialsType = "芹菜段,芹菜丝"),
|
||||||
|
GoodsItem(goodsId = "15", goodsName = "莲藕", rawMaterialsType = "莲藕片,莲藕丁,莲藕丝"),
|
||||||
|
// 以下5条 rawMaterialsType 为空,测试无净材数据场景
|
||||||
|
GoodsItem(goodsId = "16", goodsName = "菠菜"),
|
||||||
|
GoodsItem(goodsId = "17", goodsName = "生菜"),
|
||||||
|
GoodsItem(goodsId = "18", goodsName = "香菇"),
|
||||||
|
GoodsItem(goodsId = "19", goodsName = "木耳"),
|
||||||
|
GoodsItem(goodsId = "20", goodsName = "豆芽"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 将 rawMaterialsType 转存到 rawMaterialsTypes,并清空 rawMaterialsType
|
||||||
|
list.forEach { item ->
|
||||||
|
item.rawMaterialsTypes = item.rawMaterialsType
|
||||||
|
item.rawMaterialsType = null
|
||||||
|
}
|
||||||
|
searchList.clear()
|
||||||
|
searchList.addAll(list)
|
||||||
|
searchAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出净材种类单选弹窗
|
||||||
|
* @param item 当前点击的食材项
|
||||||
|
* @param position 在 searchList 中的位置
|
||||||
|
* @param types 净材种类字符串(逗号分隔),为空时提示无数据
|
||||||
|
*/
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun showRawMaterialsDialog(item: GoodsItem, position: Int, types: String?) {
|
||||||
|
val typeList = types?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() } ?: emptyList()
|
||||||
|
|
||||||
|
val dialog = CommonDialog(requireContext())
|
||||||
|
.setTitle("选择净材种类")
|
||||||
|
.setNegativeButton("取消")
|
||||||
|
|
||||||
|
if (typeList.isEmpty()) {
|
||||||
|
// 无净材数据时仅展示提示文字
|
||||||
|
dialog.setContent("无净材数据")
|
||||||
|
.setPositiveButton("确认") {}
|
||||||
|
} else {
|
||||||
|
// 构建单选 RadioGroup 作为自定义内容
|
||||||
|
val radioGroup = RadioGroup(requireContext()).apply {
|
||||||
|
orientation = RadioGroup.VERTICAL
|
||||||
|
// RadioGroup 本身居中,子项 wrap_content 宽度,整体图标+文字自然居中
|
||||||
|
gravity = Gravity.CENTER_HORIZONTAL
|
||||||
|
typeList.forEachIndexed { index, typeName ->
|
||||||
|
val rb = RadioButton(requireContext()).apply {
|
||||||
|
id = index
|
||||||
|
text = typeName
|
||||||
|
textSize = 26f
|
||||||
|
layoutParams = RadioGroup.LayoutParams(
|
||||||
|
RadioGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
RadioGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
setPadding(30.dp, 15.dp, 30.dp, 15.dp)
|
||||||
|
// 移除默认点击水波纹背景,改用 foreground 实现 selectableItemBackground 效果
|
||||||
|
setBackgroundResource(0)
|
||||||
|
val attrs = intArrayOf(android.R.attr.selectableItemBackground)
|
||||||
|
context.withStyledAttributes(null, attrs) {
|
||||||
|
foreground = getDrawable(0)
|
||||||
|
}
|
||||||
|
// 若当前项已有选中值则默认勾选对应选项
|
||||||
|
if (typeName == item.rawMaterialsType) isChecked = true
|
||||||
|
// 通过 InsetDrawable 在 buttonDrawable 右侧追加 80dp 间距,实现图标与文字的间距控制
|
||||||
|
buttonDrawable?.let { orig ->
|
||||||
|
buttonDrawable = android.graphics.drawable.InsetDrawable(
|
||||||
|
orig, 30.dp, 0, 30.dp, 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addView(rb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.addContentView(radioGroup) { tvContent ->
|
||||||
|
tvContent.gone()
|
||||||
|
}
|
||||||
|
dialog.setPositiveButtonInterceptable("确认") {
|
||||||
|
val checkedId = radioGroup.checkedRadioButtonId
|
||||||
|
if (checkedId == -1) {
|
||||||
|
toast("请选择净材种类")
|
||||||
|
// 返回 false 阻止弹窗关闭
|
||||||
|
return@setPositiveButtonInterceptable false
|
||||||
|
}
|
||||||
|
val selectedType = typeList[checkedId]
|
||||||
|
// 清空其他已选项的状态
|
||||||
|
searchList.forEachIndexed { index, it ->
|
||||||
|
if (index != position) {
|
||||||
|
it.isClicked = false
|
||||||
|
it.rawMaterialsType = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置当前选中项
|
||||||
|
item.isClicked = true
|
||||||
|
item.rawMaterialsType = selectedType
|
||||||
|
checkedItem = item
|
||||||
|
selectedFoodId = item.goodsId
|
||||||
|
selectedFoodName = item.goodsName
|
||||||
|
searchAdapter.notifyDataSetChanged()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
var clickIndex = -1
|
var clickIndex = -1
|
||||||
|
|
||||||
@@ -396,7 +512,6 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
|||||||
LogSaveUtil.saveLogFile(msg)
|
LogSaveUtil.saveLogFile(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchByFoodName(name: String, block: (String) -> Unit) {}
|
|
||||||
private fun hideWaitingDialog() {
|
private fun hideWaitingDialog() {
|
||||||
currentActivity.dismissLoading()
|
currentActivity.dismissLoading()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.shuwei.dish.match.utils
|
||||||
|
|
||||||
|
import com.shuwei.dish.match.entity.FoodCollectionBean
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
class ImageUploader(
|
||||||
|
private val totalList: List<FoodCollectionBean>,
|
||||||
|
private val uploadImage: suspend (List<FoodCollectionBean>) -> List<String>?,
|
||||||
|
private val onProgress: suspend (Int, List<FoodCollectionBean>, List<String>) -> Unit,
|
||||||
|
private val onError: (List<FoodCollectionBean>) -> Unit,
|
||||||
|
private val onComplete: () -> Unit
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val BATCH_SIZE = 3
|
||||||
|
}
|
||||||
|
private val uploadedCount = AtomicInteger(0)
|
||||||
|
|
||||||
|
suspend fun processUploads() {
|
||||||
|
val batches = totalList.filter { it.imageVector!=null }.chunked(BATCH_SIZE)
|
||||||
|
|
||||||
|
for (batch in batches) {
|
||||||
|
val idList = uploadImage(batch)
|
||||||
|
if (idList.isNullOrEmpty()) {
|
||||||
|
//println("上传失败,终止流程")
|
||||||
|
onError(batch)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val fileCount = batch.count { it.imageFile!=null }
|
||||||
|
uploadedCount.addAndGet(fileCount)
|
||||||
|
onProgress(uploadedCount.get(), batch, idList)
|
||||||
|
//println("已上传 ${uploadedCount.get()}/$totalImages")
|
||||||
|
}
|
||||||
|
|
||||||
|
onComplete()
|
||||||
|
//println("流程完成,总计上传 ${uploadedCount.get()} 张图片")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- 绿色圆角背景 + 白色垃圾桶图标,替代红色的 ic_delete_red.png -->
|
<!-- 绿色圆形背景 + 白色垃圾桶图标,替代红色的 ic_delete_red.png -->
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<!-- 绿色圆角背景 -->
|
<!-- 绿色圆角背景 -->
|
||||||
<item>
|
<item>
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="oval">
|
||||||
<solid android:color="#00BC71" />
|
<solid android:color="#00BC71" />
|
||||||
<corners android:radius="10dp" />
|
<padding
|
||||||
|
android:bottom="8dp"
|
||||||
|
android:left="8dp"
|
||||||
|
android:right="8dp"
|
||||||
|
android:top="8dp" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
android:id="@+id/flListContainer"
|
android:id="@+id/flListContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="260dp"
|
android:layout_height="260dp"
|
||||||
android:layout_marginTop="12dp">
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rvRecognizeList"
|
android:id="@+id/rvRecognizeList"
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="20dp"
|
||||||
android:background="@drawable/shape_white_12_corners"
|
android:background="@drawable/shape_white_12_corners"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
android:id="@+id/btnChangeFood"
|
android:id="@+id/btnChangeFood"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="90dp"
|
android:layout_height="90dp"
|
||||||
android:layout_marginTop="30dp"
|
android:layout_marginTop="40dp"
|
||||||
android:layout_marginHorizontal="15dp"
|
android:layout_marginHorizontal="15dp"
|
||||||
android:background="@drawable/shape_green_bg"
|
android:background="@drawable/shape_green_bg"
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
android:id="@+id/btnCancel"
|
android:id="@+id/btnCancel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="90dp"
|
android:layout_height="90dp"
|
||||||
android:layout_marginVertical="30dp"
|
android:layout_marginVertical="40dp"
|
||||||
android:layout_marginHorizontal="15dp"
|
android:layout_marginHorizontal="15dp"
|
||||||
android:background="@drawable/shape_white_12_corners"
|
android:background="@drawable/shape_white_12_corners"
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
|
|||||||
@@ -37,6 +37,16 @@
|
|||||||
android:textSize="26sp"
|
android:textSize="26sp"
|
||||||
tools:text="这是弹窗的内容信息,请确认"/>
|
tools:text="这是弹窗的内容信息,请确认"/>
|
||||||
|
|
||||||
|
<!-- 自定义内容区域,默认隐藏,通过 CommonDialog.addContentView 动态填充 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="40dp"
|
||||||
|
android:layout_marginEnd="40dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<!-- 分割线 -->
|
<!-- 分割线 -->
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:text="点击分格配置调料"
|
android:text="点击分格配置调料"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
|
android:textStyle="bold"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp" />
|
android:textSize="20sp" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginBottom="10dp"
|
android:layout_marginBottom="10dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
android:background="@drawable/shape_white_f6_10_corners"
|
android:background="@drawable/shape_white_f6_10_corners"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="32sp"
|
android:textSize="32sp"
|
||||||
|
|||||||
@@ -1,18 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/tvFoodName"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
|
android:layout_marginHorizontal="14dp"
|
||||||
|
android:layout_marginVertical="14dp"
|
||||||
android:background="@drawable/bg_item_search"
|
android:background="@drawable/bg_item_search"
|
||||||
|
android:paddingHorizontal="15dp"
|
||||||
|
android:paddingVertical="10dp">
|
||||||
|
|
||||||
|
<!-- 食材名称,选中状态通过 CheckedTextView 的 checked 驱动背景/文字色变化 -->
|
||||||
|
<CheckedTextView
|
||||||
|
android:id="@+id/tvFoodName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:layout_marginHorizontal="14dp"
|
|
||||||
android:layout_marginVertical="14dp"
|
|
||||||
android:paddingHorizontal="15dp"
|
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textColor="@color/color_item_search"
|
android:textColor="@color/color_item_search"
|
||||||
android:textSize="30sp"
|
android:textSize="28sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="土豆丝" />
|
tools:text="土豆丝" />
|
||||||
|
|
||||||
|
<!-- 当前选中的净材种类,小字显示,无数据时隐藏 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRawMaterialsType"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_gravity="center|bottom"
|
||||||
|
android:textColor="@color/black999"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="切丝" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="212dp"
|
android:layout_width="212dp"
|
||||||
android:layout_height="159dp"
|
android:layout_height="159dp"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:background="@drawable/bg_gray2">
|
android:background="@drawable/bg_gray2"
|
||||||
|
tools:ignore="ContentDescription">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageView"
|
||||||
@@ -27,5 +29,6 @@
|
|||||||
android:layout_height="36dp"
|
android:layout_height="36dp"
|
||||||
android:layout_gravity="end|bottom"
|
android:layout_gravity="end|bottom"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:src="@drawable/ic_finish" />
|
android:src="@drawable/ic_finish"
|
||||||
|
app:tint="@color/dish_green" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
Reference in New Issue
Block a user