Compare commits
6
Commits
3b1dc0a03f
...
ef13aa8307
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef13aa8307 | ||
|
|
a9663a21f0 | ||
|
|
d060305417 | ||
|
|
bb319ee4ee | ||
|
|
bb1ecbde43 | ||
|
|
7a03ab8108 |
@@ -5,7 +5,7 @@
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:6465153867413579585",
|
||||
"lastPropertyId": "4:3660727688686055762",
|
||||
"lastPropertyId": "5:7592685238899911344",
|
||||
"name": "Food",
|
||||
"properties": [
|
||||
{
|
||||
@@ -30,6 +30,11 @@
|
||||
"indexId": "1:5497952887792054610",
|
||||
"type": 28,
|
||||
"flags": 8
|
||||
},
|
||||
{
|
||||
"id": "5:7592685238899911344",
|
||||
"name": "version",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.shuwei.dish.match.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.chad.library.adapter4.BaseQuickAdapter
|
||||
import com.chad.library.adapter4.viewholder.QuickViewHolder
|
||||
@@ -24,6 +25,15 @@ class Food3Adapter(list: MutableList<GoodsItem>) :
|
||||
text = item.goodsName
|
||||
isChecked = item.isClicked
|
||||
}
|
||||
// 显示当前选中的净材种类,无数据时隐藏
|
||||
holder.binding.tvRawMaterialsType.run {
|
||||
if (item.rawMaterialsType.isNullOrEmpty()) {
|
||||
visibility = View.GONE
|
||||
} else {
|
||||
visibility = View.VISIBLE
|
||||
text = item.rawMaterialsType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
|
||||
@@ -25,7 +25,7 @@ import com.shuwei.dish.match.entity.SeasoningSlotEntity
|
||||
CookFoodGoodsEntity::class,
|
||||
SeasoningSlotEntity::class,
|
||||
],
|
||||
version = 10,
|
||||
version = 12,
|
||||
exportSchema = true
|
||||
)
|
||||
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")
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本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) {
|
||||
// 单例模式确保全局唯一实例
|
||||
val instance: AppDatabase by lazy {
|
||||
@@ -253,6 +269,8 @@ class DatabaseProvider(private val context: Context) {
|
||||
.addMigrations(MIGRATION_7_8)
|
||||
.addMigrations(MIGRATION_8_9)
|
||||
.addMigrations(MIGRATION_9_10)
|
||||
.addMigrations(MIGRATION_10_11)
|
||||
.addMigrations(MIGRATION_11_12)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.shuwei.dish.match.base.DeviceRole
|
||||
import com.shuwei.dish.match.base.GlobalData
|
||||
@@ -26,9 +27,14 @@ open class CommonDialog(
|
||||
private var positiveText = "确认"
|
||||
private var neutralText: String? = 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 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 }
|
||||
@@ -42,8 +48,17 @@ open class CommonDialog(
|
||||
negativeClick = onClick
|
||||
}
|
||||
|
||||
/** 设置右侧确认按钮文字及点击回调(3按钮模式下对应底部按钮) */
|
||||
/** 设置右侧确认按钮文字及点击回调(3按钮模式下对应底部按钮),点击后自动关闭弹窗 */
|
||||
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
|
||||
positiveClick = onClick
|
||||
}
|
||||
@@ -59,6 +74,16 @@ open class CommonDialog(
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
@@ -88,8 +113,8 @@ open class CommonDialog(
|
||||
dismiss()
|
||||
}
|
||||
binding.btnRight.setOnClickListener {
|
||||
positiveClick?.invoke()
|
||||
dismiss()
|
||||
// 回调返回 false 时阻止关闭,null 或 true 时正常关闭
|
||||
if (positiveClick?.invoke() != false) dismiss()
|
||||
}
|
||||
|
||||
// 3按钮点击事件(negativeClick/positiveClick 与2按钮模式共用)
|
||||
@@ -102,11 +127,17 @@ open class CommonDialog(
|
||||
dismiss()
|
||||
}
|
||||
binding.btnBottom.setOnClickListener {
|
||||
positiveClick?.invoke()
|
||||
dismiss()
|
||||
if (positiveClick?.invoke() != false) dismiss()
|
||||
}
|
||||
|
||||
setOnDismissListener { dismissCallback?.invoke() }
|
||||
|
||||
// 挂载自定义内容 View,并执行 tvContent 的灵活控制回调
|
||||
pendingContentView?.let {
|
||||
binding.llContent.addView(it)
|
||||
binding.llContent.visibility = View.VISIBLE
|
||||
tvContentConfigurator?.invoke(binding.tvContent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
|
||||
@@ -50,27 +50,27 @@ class FoodSearchDialog(
|
||||
|
||||
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
|
||||
private val list = mutableListOf<GoodsItem>()
|
||||
.apply {
|
||||
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 = "洋葱丁"))
|
||||
add(GoodsItem(goodsId = "200012", goodsName = "A"))
|
||||
add(GoodsItem(goodsId = "200013", goodsName = "B"))
|
||||
add(GoodsItem(goodsId = "200014", goodsName = "C"))
|
||||
add(GoodsItem(goodsId = "200015", goodsName = "D"))
|
||||
add(GoodsItem(goodsId = "200016", goodsName = "E"))
|
||||
add(GoodsItem(goodsId = "200017", goodsName = "F"))
|
||||
add(GoodsItem(goodsId = "200018", goodsName = "G"))
|
||||
add(GoodsItem(goodsId = "200019", goodsName = "H"))
|
||||
}
|
||||
// .apply {
|
||||
// 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 = "洋葱丁"))
|
||||
// add(GoodsItem(goodsId = "200012", goodsName = "A"))
|
||||
// add(GoodsItem(goodsId = "200013", goodsName = "B"))
|
||||
// add(GoodsItem(goodsId = "200014", goodsName = "C"))
|
||||
// add(GoodsItem(goodsId = "200015", goodsName = "D"))
|
||||
// add(GoodsItem(goodsId = "200016", goodsName = "E"))
|
||||
// add(GoodsItem(goodsId = "200017", goodsName = "F"))
|
||||
// add(GoodsItem(goodsId = "200018", goodsName = "G"))
|
||||
// add(GoodsItem(goodsId = "200019", goodsName = "H"))
|
||||
// }
|
||||
private val adapter = FoodAdapter(list).apply {
|
||||
isStateViewEnable = true
|
||||
setOnItemClickListener { _, _, position ->
|
||||
@@ -135,13 +135,13 @@ class FoodSearchDialog(
|
||||
|
||||
initObserver()
|
||||
|
||||
// // 若有默认食材名称,自动填充并触发搜索
|
||||
// //if (!defGoodsName.isNullOrBlank()) {
|
||||
// binding.etSheetInput.setText(defGoodsName)
|
||||
// pageNo = 1
|
||||
// getGoodsList()
|
||||
// KeyboardUtil.hideKeyboard(binding.root)
|
||||
// //}
|
||||
// 若有默认食材名称,自动填充并触发搜索
|
||||
//if (!defGoodsName.isNullOrBlank()) {
|
||||
//binding.etSheetInput.setText(defGoodsName)
|
||||
pageNo = 1
|
||||
getGoodsList()
|
||||
KeyboardUtil.hideKeyboard(binding.root)
|
||||
//}
|
||||
}
|
||||
|
||||
/** 弹窗显示时在 Window.Callback 层提前拦截触摸,解决 BottomSheet 拖拽与列表滑动的手势冲突 */
|
||||
|
||||
@@ -186,25 +186,25 @@ class SeasoningSelectDialog(
|
||||
* 请求调料列表
|
||||
*/
|
||||
private fun getGoodsList() {
|
||||
// activity.netViewModel.queryGoodsList(
|
||||
// goodsType = "1",
|
||||
// pageNum = pageNo,
|
||||
// pageSize = PAGE_SIZE,
|
||||
// goodsName = goodsName.takeUnless { it.isNullOrBlank() }
|
||||
// )
|
||||
|
||||
// TODO: 联调时注释掉下方模拟数据,改为真实接口调用
|
||||
val nameList = listOf(
|
||||
"盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
||||
"料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
||||
activity.netViewModel.queryGoodsList(
|
||||
goodsType = "1",
|
||||
pageNum = pageNo,
|
||||
pageSize = PAGE_SIZE,
|
||||
goodsName = goodsName.takeUnless { it.isNullOrBlank() }
|
||||
)
|
||||
|
||||
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)
|
||||
// // TODO: 联调时注释掉下方模拟数据,改为真实接口调用
|
||||
// val nameList = listOf(
|
||||
// "盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
||||
// "料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
||||
// )
|
||||
//
|
||||
// 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 goodsCode: String? = "",
|
||||
|
||||
/** 净材种类 */
|
||||
var rawMaterialsType: String? = null,
|
||||
|
||||
var isDel: Int = 0,
|
||||
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
|
||||
) : BaseEntity, Serializable {
|
||||
@@ -80,10 +86,12 @@ class CookFoodGoodsEntity(
|
||||
popularName = popularName,
|
||||
zjmCode = zjmCode,
|
||||
materId = materId,
|
||||
materCode = goodsCode,
|
||||
materialType = materialType,
|
||||
useWeight = useWeight,
|
||||
relateionType = relateionType,
|
||||
allEdible = allEdible,
|
||||
goodsOrRelationCode = goodsOrRelationCode
|
||||
goodsOrRelationCode = goodsOrRelationCode,
|
||||
rawMaterialsType = rawMaterialsType
|
||||
)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ data class GoodsItem(
|
||||
val popularName: String? = null,
|
||||
val zjmCode: String? = null,
|
||||
val materId: String? = null,
|
||||
val materCode: String? = null,
|
||||
|
||||
// 从 CookFoodGoodsEntity 迁移的数据字段(DishPartAdapter 显示及转换用)
|
||||
var materialType: Int = 0,
|
||||
@@ -30,9 +31,18 @@ data class GoodsItem(
|
||||
// true-原始数据,false-非原始数据(识图新增)
|
||||
var isOriginalData: Boolean = true,
|
||||
// 识图匹配分数,-1 表示不显示
|
||||
var foodScore: Int = -1
|
||||
var foodScore: Int = -1,
|
||||
// 净材种类(当前选中值)
|
||||
var rawMaterialsType: String? = null,
|
||||
// 净材种类候选列表(临时字段,逗号分隔,不持久化)
|
||||
var rawMaterialsTypes: String? = null
|
||||
) : Serializable {
|
||||
|
||||
/**
|
||||
* 物料编码,与查询接口返回的 materCode 同值,统一对外暴露为 goodsCode 供下游使用
|
||||
*/
|
||||
val goodsCode: String? get() = materCode
|
||||
|
||||
/**
|
||||
* 转换为 CookFoodGoodsEntity,用于写入数据库或传给 SubmitFoodActivity
|
||||
*/
|
||||
@@ -42,11 +52,13 @@ data class GoodsItem(
|
||||
popularName = popularName,
|
||||
zjmCode = zjmCode,
|
||||
materId = materId,
|
||||
goodsCode = goodsCode,
|
||||
materialType = materialType,
|
||||
useWeight = useWeight,
|
||||
relateionType = relateionType,
|
||||
allEdible = allEdible,
|
||||
goodsOrRelationCode = goodsOrRelationCode
|
||||
goodsOrRelationCode = goodsOrRelationCode,
|
||||
rawMaterialsType = rawMaterialsType
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -58,9 +70,11 @@ data class GoodsItem(
|
||||
popularName = popularName,
|
||||
zjmCode = zjmCode,
|
||||
materId = materId,
|
||||
goodsCode = goodsCode,
|
||||
materialType = materialType,
|
||||
relateionType = relateionType,
|
||||
allEdible = allEdible,
|
||||
goodsOrRelationCode = goodsOrRelationCode
|
||||
goodsOrRelationCode = goodsOrRelationCode,
|
||||
rawMaterialsType = rawMaterialsType
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,6 +58,12 @@ data class SeasoningEntity(
|
||||
|
||||
var materId: String? = "",
|
||||
|
||||
/** 物料编码 */
|
||||
var goodsCode: String? = "",
|
||||
|
||||
/** 净材种类 */
|
||||
var rawMaterialsType: String? = null,
|
||||
|
||||
var sort: Int = 9999,
|
||||
/**
|
||||
* 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.FoodRecord
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.PartMap
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.Url
|
||||
|
||||
@@ -36,7 +41,7 @@ interface ApiService {
|
||||
*/
|
||||
@POST
|
||||
suspend fun searchFoodList(
|
||||
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryPageList",
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryPageList",
|
||||
@Body param: MutableMap<String, Any>
|
||||
): ApiResponse<MutableList<FoodRecord>?>
|
||||
|
||||
@@ -45,7 +50,7 @@ interface ApiService {
|
||||
*/
|
||||
@POST
|
||||
suspend fun getSamplingList(
|
||||
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryHistoryGoodsInfoList",
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryHistoryGoodsInfoList",
|
||||
@Body param: MutableMap<String, Any>
|
||||
): ApiResponse<MutableList<FoodRecord>?>
|
||||
|
||||
@@ -54,8 +59,18 @@ interface ApiService {
|
||||
*/
|
||||
@POST
|
||||
suspend fun queryGoodsList(
|
||||
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryGoodsInfoList",
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryGoodsInfoList",
|
||||
@Body param: MutableMap<String, Any>
|
||||
): 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.asStateFlow
|
||||
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
|
||||
@@ -126,4 +131,44 @@ class NetViewModel(
|
||||
_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.FoodRecord
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 网络数据仓库,封装所有 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 foodIdx: Int = 0,
|
||||
@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 {
|
||||
if (this === other) return true
|
||||
@@ -23,6 +24,7 @@ data class Food(
|
||||
if (foodIdx != other.foodIdx) return false
|
||||
if (name != other.name) return false
|
||||
if (!foodVector.contentEquals(other.foodVector)) return false
|
||||
if (version != other.version) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -32,6 +34,8 @@ data class Food(
|
||||
result = 31 * result + foodIdx
|
||||
result = 31 * result + (name?.hashCode() ?: 0)
|
||||
result = 31 * result + (foodVector?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (version?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
@@ -86,7 +86,9 @@ class ScaleWebSocketClient {
|
||||
Log.d(TAG, "放弃过期连接任务: $deviceId, version=$version")
|
||||
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 ws = httpClient.newWebSocket(request, object : WebSocketListener() {
|
||||
|
||||
@@ -176,26 +176,26 @@ class FoodRecognizeActivity : BaseActivity() {
|
||||
}
|
||||
currentWeight = intent.getDoubleExtra(EXTRA_CURRENT_WEIGHT, 0.0)
|
||||
binding.tvWeight.text = "${currentWeight}g"
|
||||
val tempList = arrayListOf<GoodsItem>()
|
||||
// val tempList = arrayListOf<GoodsItem>()
|
||||
recognizeNameList?.apply {
|
||||
forEach {
|
||||
val goodsName = it.name.split("WP").first()
|
||||
goodsNameList.add(goodsName)
|
||||
nameScoreMap.put(goodsName, it)
|
||||
|
||||
// TODO: 测试数据,临时使用-------------------------
|
||||
val testData = buildTestData(it)
|
||||
tempList.add(testData)
|
||||
// TODO: 测试数据,临时使用-------------------------
|
||||
// // TODO: 测试数据,临时使用-------------------------
|
||||
// val testData = buildTestData(it)
|
||||
// tempList.add(testData)
|
||||
// // TODO: 测试数据,临时使用-------------------------
|
||||
}
|
||||
// TODO: 测试数据,临时使用-------------------------
|
||||
loadRecognizeList(tempList)
|
||||
// TODO: 测试数据,临时使用-------------------------
|
||||
// // TODO: 测试数据,临时使用-------------------------
|
||||
// loadRecognizeList(tempList)
|
||||
// // TODO: 测试数据,临时使用-------------------------
|
||||
recognizeNameList = null
|
||||
}
|
||||
|
||||
// TODO: 注释掉等接口正常再调用,临时使用测试数据
|
||||
//netViewModel.queryGoodsList(goodsType = "0", goodsNames = goodsNameList)
|
||||
//TODO: 注释掉等接口正常再调用,临时使用测试数据
|
||||
netViewModel.queryGoodsList(goodsType = "0", goodsNames = goodsNameList)
|
||||
}
|
||||
|
||||
private fun buildTestData(item: FoodModule.IdNameScore): GoodsItem {
|
||||
|
||||
@@ -7,13 +7,10 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.addCallback
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.CameraUtils
|
||||
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.WeightUtil
|
||||
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.visible
|
||||
import com.yanzhenjie.recyclerview.SwipeMenuItem
|
||||
import com.yanzhenjie.recyclerview.SwipeRecyclerView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -56,11 +51,11 @@ import kotlin.math.abs
|
||||
class PrepareFoodActivity : BaseActivity() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "CookActivity"
|
||||
const val TAG = "PrepareFoodActivity"
|
||||
const val FOOD_ITEM = "foodItem"
|
||||
const val PAGE_FROM = "pageFrom"
|
||||
const val HOME = "home"
|
||||
const val WEIGHT_CHANGE_VALUE = 15
|
||||
const val WEIGHT_CHANGE_VALUE = 5
|
||||
const val WEIGHT_RECOGNIZE_VALUE = 10
|
||||
|
||||
/** 主材数量上限 */
|
||||
@@ -224,9 +219,9 @@ class PrepareFoodActivity : BaseActivity() {
|
||||
FoodRecognizeActivity.close()
|
||||
return
|
||||
}
|
||||
if (!pageVisible || manualCancelFlag || isTakingPhoto.get() || showRecognizePage || abs(
|
||||
lastWeight - weight
|
||||
) <= WEIGHT_CHANGE_VALUE
|
||||
if (
|
||||
!pageVisible || manualCancelFlag || isTakingPhoto.get() || showRecognizePage ||
|
||||
abs(lastWeight - weight) <= WEIGHT_CHANGE_VALUE
|
||||
) {
|
||||
Log.d(
|
||||
TAG,
|
||||
|
||||
@@ -98,7 +98,7 @@ class SettingActivity : BaseActivity() {
|
||||
.setTitle("数据清除")
|
||||
.setContent("将物理删除全部 4 张表数据(菜品、食材、调料、槽位),此操作不可恢复,同时重置菜品模式和食堂id,确认继续?")
|
||||
.setNegativeButton("取消")
|
||||
.setPositiveButton("确认清除") {
|
||||
.setPositiveButton("确认") {
|
||||
appViewModel.clearAllData {
|
||||
SpTool.cookMode = -1
|
||||
SpTool.canteenId = "0"
|
||||
|
||||
@@ -293,11 +293,6 @@ class SubmitFoodActivity : BaseActivity() {
|
||||
toast("未获取到菜品或构成信息")
|
||||
return
|
||||
}
|
||||
val seasoningData = seasoningAdapter.items
|
||||
// if (seasoningData.isEmpty()) {
|
||||
// toast("未获取到调料信息")
|
||||
// return
|
||||
// }
|
||||
showLoading()
|
||||
|
||||
val isSamplingData = food!!.cookMode == 1
|
||||
@@ -316,23 +311,11 @@ class SubmitFoodActivity : BaseActivity() {
|
||||
else -> it.dinnerType
|
||||
}
|
||||
}
|
||||
// 将调料数据转为 CookFoodGoodsEntity 追加到 goodsList
|
||||
val tempSeasoningList = seasoningData.map { item ->
|
||||
val seasoning = appViewModel.getSeasoningByGoodsId(item.goodsId)
|
||||
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.relateionType = seasoning?.relateionType ?: 0
|
||||
entity.allEdible = seasoning?.allEdible ?: true
|
||||
entity.goodsOrRelationCode = seasoning?.goodsOrRelationCode
|
||||
}
|
||||
}
|
||||
goodsList?.addAll(tempSeasoningList)
|
||||
// 先移除 goodsList 中已有的调料数据(materialType==3),再以 adapter 中最新调料覆盖
|
||||
goodsList?.removeAll { it.materialType == 3 }
|
||||
seasoningAdapter.items
|
||||
.filter { it.useWeight > 0.0 }
|
||||
.forEach { goodsList?.add(buildSeasoningEntity(it)) }
|
||||
appViewModel.saveCookFoodAndGoods(
|
||||
cookMode = food!!.cookMode,
|
||||
entity = cookFoodEntity,
|
||||
@@ -348,28 +331,14 @@ class SubmitFoodActivity : BaseActivity() {
|
||||
toast("未获取到菜品或构成信息")
|
||||
return
|
||||
}
|
||||
val seasoningData = seasoningAdapter.items
|
||||
showLoading()
|
||||
|
||||
lifecycleScope.launch {
|
||||
// 将重量大于0且不重复的调料追加到 goodsList
|
||||
seasoningData.forEach { item ->
|
||||
if (item.useWeight > 0.0 && goodsList?.none { it.goodsId == item.goodsId } == true) {
|
||||
val seasoning = appViewModel.getSeasoningByGoodsId(item.goodsId)
|
||||
goodsList?.add(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.relateionType = seasoning?.relateionType ?: 0
|
||||
entity.allEdible = seasoning?.allEdible ?: true
|
||||
entity.goodsOrRelationCode = seasoning?.goodsOrRelationCode
|
||||
})
|
||||
}
|
||||
}
|
||||
// 先移除 goodsList 中已有的调料数据(materialType==3),再以 adapter 中最新调料覆盖
|
||||
goodsList?.removeAll { it.materialType == 3 }
|
||||
seasoningAdapter.items
|
||||
.filter { it.useWeight > 0.0 }
|
||||
.forEach { goodsList?.add(buildSeasoningEntity(it)) }
|
||||
|
||||
cookFoodEntity.let {
|
||||
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) {
|
||||
if (food?.foodId.isNullOrBlank()) {
|
||||
dismissLoading()
|
||||
|
||||
@@ -11,30 +11,46 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.view.Gravity
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.adapter.VectorCollectionAdapter
|
||||
import com.shuwei.dish.match.adapter.Food3Adapter
|
||||
import com.shuwei.dish.match.base.BaseFragment
|
||||
import com.shuwei.dish.match.databinding.FragmentVectorCollectionBinding
|
||||
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.entity.FoodCollectionBean
|
||||
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.ObjectBox
|
||||
import com.shuwei.dish.match.ui.CollectedFoodActivity
|
||||
import com.shuwei.dish.match.ui.SingleFragmentActivity
|
||||
import com.shuwei.dish.match.utils.BitmapSaver
|
||||
import com.shuwei.dish.match.utils.CameraUtils
|
||||
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.LogSaveUtil
|
||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||
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 okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import androidx.core.content.withStyledAttributes
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>() {
|
||||
@@ -53,30 +69,15 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
}
|
||||
}
|
||||
|
||||
private val searchList = mutableListOf<GoodsItem>().apply {
|
||||
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 val searchList = mutableListOf<GoodsItem>()
|
||||
private var checkedItem: GoodsItem? = null
|
||||
private val searchAdapter by lazy {
|
||||
Food3Adapter(searchList).apply {
|
||||
setOnItemClickListener { adapter, view, position ->
|
||||
searchList.forEachIndexed { index, item -> item.isClicked = index == position }
|
||||
checkedItem = searchList[position]
|
||||
notifyDataSetChanged()
|
||||
checkedItem?.let {
|
||||
selectedFoodId = it.goodsId
|
||||
selectedFoodName = it.goodsName
|
||||
}
|
||||
setOnItemClickListener { _, _, position ->
|
||||
val item = searchList[position]
|
||||
val types = item.rawMaterialsTypes
|
||||
// 弹出净材种类单选弹窗
|
||||
showRawMaterialsDialog(item, position, types)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,15 +112,16 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
|
||||
//typealias CameraCallback = (Uri) -> Unit
|
||||
|
||||
private val cameraCallback: (Uri) -> Unit = cameraCallback@{ uri ->
|
||||
try {
|
||||
val index = vectorList.indexOfFirst { it.imageFile == null }
|
||||
if (index == -1) {
|
||||
toast("每次只允许保存${MAX_COUNT}条数据")
|
||||
hideWaitingDialog()
|
||||
return@cameraCallback
|
||||
}
|
||||
activity?.runOnUiThread {
|
||||
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val index = vectorList.indexOfFirst { it.imageFile == null }
|
||||
if (index == -1) {
|
||||
toast("每次只允许保存${MAX_COUNT}条数据")
|
||||
hideWaitingDialog()
|
||||
return@launch
|
||||
}
|
||||
// 主线程更新 UI
|
||||
vectorList[index].let {
|
||||
it.imageVector = null
|
||||
it.bitmap = null
|
||||
@@ -128,53 +130,50 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
it.imageUri = uri
|
||||
}
|
||||
vectorAdapter.notifyItemChanged(index)
|
||||
}
|
||||
// hideWaitingDialog()
|
||||
Thread {
|
||||
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
|
||||
getImageVector(index, bitmap)
|
||||
// 切到 IO 线程执行耗时的 Bitmap 解码
|
||||
val bitmap = withContext(Dispatchers.IO) {
|
||||
ImageUtil.uriToBitmap(requireActivity(), uri)
|
||||
}
|
||||
}.start()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
hideWaitingDialog()
|
||||
toast("程序异常${e.message}")
|
||||
log("程序异常${e.message}")
|
||||
bitmap?.let { getImageVector(index, it) }
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
hideWaitingDialog()
|
||||
toast("程序异常${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()
|
||||
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(
|
||||
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||
)
|
||||
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||
vector to file
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
log("操作失败:${e.message}")
|
||||
null to null
|
||||
}
|
||||
}
|
||||
// 回到主线程处理结果
|
||||
if (imageVector == null) {
|
||||
toast("操作失败")
|
||||
log("操作失败:${e.message}")
|
||||
hideWaitingDialog()
|
||||
return
|
||||
}
|
||||
val file = BitmapSaver.saveToAppFilesDir(
|
||||
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||
)
|
||||
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||
|
||||
activity?.runOnUiThread {
|
||||
vectorList[index].let {
|
||||
it.imageVector = imageVector
|
||||
it.bitmap = null
|
||||
it.isShowCamera = false
|
||||
it.imageFile = file
|
||||
}
|
||||
vectorAdapter.notifyItemChanged(index)
|
||||
}
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
vectorList[index].let {
|
||||
it.imageVector = imageVector
|
||||
it.bitmap = null
|
||||
it.isShowCamera = false
|
||||
it.imageFile = file
|
||||
}
|
||||
vectorAdapter.notifyItemChanged(index)
|
||||
if (bitmap.isRecycled.not()) bitmap.recycle()
|
||||
hideWaitingDialog()
|
||||
}
|
||||
|
||||
@@ -235,16 +234,16 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
}
|
||||
|
||||
binding.btnSave.setOnClickListener {
|
||||
// if (checkedItem == null || checkedItem!!.isChecked.not()) {
|
||||
// toast("请选择菜品名称")
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
// val count = foodCollectionList.count { it.imageFile != null }
|
||||
// if (count == 0) {
|
||||
// toast("请拍摄菜品照片")
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
// upload()
|
||||
if (checkedItem == null || checkedItem!!.isClicked.not()) {
|
||||
toast("请选择菜品名称")
|
||||
return@setOnClickListener
|
||||
}
|
||||
val count = vectorList.count { it.imageFile != null }
|
||||
if (count == 0) {
|
||||
toast("请拍摄菜品照片")
|
||||
return@setOnClickListener
|
||||
}
|
||||
upload()
|
||||
}
|
||||
|
||||
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
|
||||
@@ -279,80 +278,197 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
searchFood()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun upload() {
|
||||
// lifecycleScope.launch {
|
||||
// val totalFileCount = foodCollectionList.count { it.imageFile != null }
|
||||
// showWaitingDialog2("图片上传中0/$totalFileCount")
|
||||
// val params = HashMap<String, RequestBody>()
|
||||
// //params["placeId"] = restId.toRequestBody()
|
||||
// params["foodId"] = checkedItem!!.foodId.toRequestBody()
|
||||
// params["foodName"] = checkedItem!!.foodName!!.toRequestBody()
|
||||
// params["version"] = GlobalData.foodModelVersion.toRequestBody()
|
||||
//
|
||||
// ImageUploader(totalList = foodCollectionList, uploadImage = { batch ->
|
||||
// val files = batch.map { it.imageFile }
|
||||
// val foodVectorList = batch.filter { it.imageVector != null }.map {
|
||||
// it.imageVector!!.joinToString(
|
||||
// separator = ",", prefix = "[", postfix = "]"
|
||||
// )
|
||||
// }
|
||||
// val foodVectorJson =
|
||||
// foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||
// log("json=$foodVectorJson")
|
||||
// params["foodVector"] = foodVectorJson.toRequestBody()
|
||||
// viewModel?.uploadCollectFoodPics(files, params)
|
||||
// }, onProgress = { count, batch, idList ->
|
||||
// //batch.forEach {
|
||||
// // it.uploadSuccess = true
|
||||
// //}
|
||||
// runBlocking {
|
||||
// activity?.runOnUiThread {
|
||||
// showWaitingDialog("图片上传中$count/$totalFileCount")
|
||||
// }
|
||||
// val foodList = batch.mapIndexed { index, it ->
|
||||
// Food(
|
||||
// collectId = if (index < idList.size) idList[index] else null,
|
||||
// foodId = checkedItem!!.foodId,
|
||||
// foodName = checkedItem!!.foodName,
|
||||
// foodVector = it.imageVector,
|
||||
// version = GlobalData.foodModelVersion
|
||||
// )
|
||||
// }
|
||||
// ObjectBox.putAll(foodList)
|
||||
// activity?.runOnUiThread {
|
||||
// batch.forEach { it.isFinish = true }
|
||||
// collectionAdapter.notifyDataSetChanged()
|
||||
// }
|
||||
// }
|
||||
// }, onError = {
|
||||
// activity?.runOnUiThread {
|
||||
// binding.root.postDelayed({
|
||||
// hideWaitingDialog()
|
||||
// toast("上传失败,请稍后重试")
|
||||
// }, 1000)
|
||||
// }
|
||||
// }, onComplete = {
|
||||
// //vectorThread()
|
||||
// binding.root.postDelayed({
|
||||
// hideWaitingDialog()
|
||||
// toast("上传成功")
|
||||
// }, 1000)
|
||||
// }).processUploads()
|
||||
// }
|
||||
lifecycleScope.launch {
|
||||
val totalFileCount = vectorList.count { it.imageFile != null }
|
||||
showWaitingDialog("图片上传中0/$totalFileCount")
|
||||
val params = mutableMapOf<String, RequestBody>()
|
||||
params["goodsId"] = checkedItem!!.goodsId.toRequestBody()
|
||||
params["goodsName"] = checkedItem!!.goodsName!!.toRequestBody()
|
||||
//净材种类,逗号分隔
|
||||
// params["rawMaterialsTypes"] = "".toRequestBody()
|
||||
//params["version"] = "1.0.0".toRequestBody()
|
||||
|
||||
ImageUploader(totalList = vectorList, uploadImage = { batch ->
|
||||
val files = batch.mapNotNull { it.imageFile }
|
||||
val foodVectorList = batch.filter { it.imageVector != null }.map {
|
||||
it.imageVector!!.joinToString(
|
||||
separator = ",", prefix = "[", postfix = "]"
|
||||
)
|
||||
}
|
||||
val foodVectorJson =
|
||||
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||
log("json=$foodVectorJson")
|
||||
params["foodVector"] = foodVectorJson.toRequestBody()
|
||||
// 直接 await 单批上传结果,成功返回 id 列表,失败返回 null 触发 onError
|
||||
val result = currentActivity.netViewModel.uploadFoodVectorDataBatch(files, params)
|
||||
if (result is UiState.Success) result.data else null
|
||||
}, onProgress = { count, batch, idList ->
|
||||
showWaitingDialog("图片上传中$count/$totalFileCount")
|
||||
val foodList = batch.mapIndexed { index, it ->
|
||||
Food(
|
||||
name = checkedItem!!.goodsName,
|
||||
foodVector = it.imageVector,
|
||||
)
|
||||
}
|
||||
ObjectBox.putAll(foodList)
|
||||
batch.forEach { it.isFinish = true }
|
||||
vectorAdapter.notifyDataSetChanged()
|
||||
}, onError = {
|
||||
binding.root.postDelayed({
|
||||
hideWaitingDialog()
|
||||
toast("上传失败,请稍后重试")
|
||||
}, 1000)
|
||||
}, onComplete = {
|
||||
binding.root.postDelayed({
|
||||
hideWaitingDialog()
|
||||
toast("上传成功")
|
||||
}, 1000)
|
||||
}).processUploads()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun searchFood() {
|
||||
debouncer.debounce {
|
||||
searchByFoodName(binding.editFoodName.text.toString()) {
|
||||
// searchFoodList.clear()
|
||||
// searchFoodList.addAll(it)
|
||||
// searchFoodAdapter.notifyDataSetChanged()
|
||||
val keyword = binding.editFoodName.text.toString().trim()
|
||||
// 通过 NetViewModel 查询净材数据(goodsType=0 食材)
|
||||
currentActivity.netViewModel.queryGoodsList(
|
||||
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
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@@ -396,7 +512,6 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
LogSaveUtil.saveLogFile(msg)
|
||||
}
|
||||
|
||||
private fun searchByFoodName(name: String, block: (String) -> Unit) {}
|
||||
private fun hideWaitingDialog() {
|
||||
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"?>
|
||||
<!-- 绿色圆角背景 + 白色垃圾桶图标,替代红色的 ic_delete_red.png -->
|
||||
<!-- 绿色圆形背景 + 白色垃圾桶图标,替代红色的 ic_delete_red.png -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- 绿色圆角背景 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#00BC71" />
|
||||
<corners android:radius="10dp" />
|
||||
<padding
|
||||
android:bottom="8dp"
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:top="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
android:id="@+id/flListContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="260dp"
|
||||
android:layout_marginTop="12dp">
|
||||
android:layout_marginTop="20dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvRecognizeList"
|
||||
@@ -85,7 +85,7 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/shape_white_12_corners"
|
||||
android:orientation="vertical">
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
android:id="@+id/btnChangeFood"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:background="@drawable/shape_green_bg"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
@@ -134,7 +134,7 @@
|
||||
android:id="@+id/btnCancel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
android:layout_marginVertical="30dp"
|
||||
android:layout_marginVertical="40dp"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:background="@drawable/shape_white_12_corners"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
|
||||
@@ -37,6 +37,16 @@
|
||||
android:textSize="26sp"
|
||||
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
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:text="点击分格配置调料"
|
||||
android:includeFontPadding="false"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp" />
|
||||
</FrameLayout>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:background="@drawable/shape_white_f6_10_corners"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="32sp"
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
<?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"
|
||||
android:id="@+id/tvFoodName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:background="@drawable/bg_item_search"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:layout_marginHorizontal="14dp"
|
||||
android:layout_marginVertical="14dp"
|
||||
android:background="@drawable/bg_item_search"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/color_item_search"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="土豆丝" />
|
||||
android:paddingVertical="10dp">
|
||||
|
||||
<!-- 食材名称,选中状态通过 CheckedTextView 的 checked 驱动背景/文字色变化 -->
|
||||
<CheckedTextView
|
||||
android:id="@+id/tvFoodName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/color_item_search"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
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"?>
|
||||
<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"
|
||||
android:layout_width="212dp"
|
||||
android:layout_height="159dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@drawable/bg_gray2">
|
||||
android:background="@drawable/bg_gray2"
|
||||
tools:ignore="ContentDescription">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
@@ -27,5 +29,6 @@
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_finish" />
|
||||
android:src="@drawable/ic_finish"
|
||||
app:tint="@color/dish_green" />
|
||||
</FrameLayout>
|
||||
Reference in New Issue
Block a user