From 32ec1d9b7495c97b054ce8bb7c2004dc43ddd037 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Tue, 2 Jun 2026 18:05:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(pack):=20=E6=B7=BB=E5=8A=A0=E5=87=80?= =?UTF-8?q?=E8=8F=9C=E5=8C=85=E8=A3=85=E5=8A=9F=E8=83=BD=E5=92=8C=E9=A3=9F?= =?UTF-8?q?=E6=9D=90=E8=BF=BD=E6=BA=AF=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在activity_pack.xml中新增rvFormList RecyclerView用于表单展示 - 在TaskDetailActivity中实现食材明细的增删改查功能 - 添加InboundIngredientSearchDialog用于食材溯源码搜索 - 更新NetViewModelV2中addComboTask方法参数类型 - 添加NutCleanInboundIngredientVO数据模型用于已入库净菜食材 - 在PackActivity中实现包装表单动态加载和设备选择功能 - 添加用量计算和重量识别相关UI组件 - 优化FormFieldAdapter适配器去除Activity上下文依赖 - 新增formatDecimal扩展函数用于格式化用量字符串 - 实现TaskDetailAdapter中食材项的选中和清除逻辑 - 添加组合约束的API接口定义和数据传输对象修改 - 重构表单字段配置为动态加载模式支持多种输入类型 - 添加删除确认对话框提升用户体验和数据安全性 - 优化布局文件结构调整控件间距和显示效果 - 添加设备选项加载回调机制确保数据实时更新 - 实现重量监听器的生命周期管理防止内存泄漏 - 更新界面跳转逻辑支持餐品选择和详情展示 - 修复多个布局和代码中的空格及格式问题 - 添加必要的依赖导入和常量定义完善功能模块 --- .../dish/match/adapter/FormFieldAdapter.kt | 6 +- .../match/adapter/InboundIngredientAdapter.kt | 40 ++++ .../dish/match/adapter/TaskDetailAdapter.kt | 55 +++-- .../dialog/InboundIngredientSearchDialog.kt | 214 ++++++++++++++++++ .../dish/match/model/NutritionModels.kt | 39 ++-- .../com/shuwei/dish/match/net/ApiServiceV2.kt | 12 +- .../shuwei/dish/match/net/NetViewModelV2.kt | 76 +++++-- .../dish/match/net/RemoteRepositoryV2.kt | 6 +- .../shuwei/dish/match/ui/CleanPackActivity.kt | 28 ++- .../com/shuwei/dish/match/ui/PackActivity.kt | 199 +++++++++++++++- .../dish/match/ui/TaskDetailActivity.kt | 189 +++++++++++++--- .../com/shuwei/dish/match/utils/ext/Common.kt | 12 + app/src/main/res/layout/activity_pack.xml | 13 ++ .../main/res/layout/activity_task_detail.xml | 115 +++++++++- .../dialog_inbound_ingredient_search.xml | 98 ++++++++ .../res/layout/list_item_food_material.xml | 2 +- .../layout/list_item_inbound_ingredient.xml | 49 ++++ .../main/res/layout/list_item_task_detail.xml | 104 +++++---- 18 files changed, 1104 insertions(+), 153 deletions(-) create mode 100644 app/src/main/java/com/shuwei/dish/match/adapter/InboundIngredientAdapter.kt create mode 100644 app/src/main/java/com/shuwei/dish/match/dialog/InboundIngredientSearchDialog.kt create mode 100644 app/src/main/res/layout/dialog_inbound_ingredient_search.xml create mode 100644 app/src/main/res/layout/list_item_inbound_ingredient.xml diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/FormFieldAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/FormFieldAdapter.kt index a513332..8ee85c4 100644 --- a/app/src/main/java/com/shuwei/dish/match/adapter/FormFieldAdapter.kt +++ b/app/src/main/java/com/shuwei/dish/match/adapter/FormFieldAdapter.kt @@ -32,11 +32,9 @@ import kotlin.math.min * 动态表单字段 Adapter,配合 FlexboxLayoutManager 实现两列自动换行布局 * 单一 item 布局,通过 visibility 控制 TEXT/INTEGER/DECIMAL/FIXED/DROPDOWN/DATE_PICKER 六种类型渲染 * - * @param activityContext Activity context,用于创建 DatePickerDialog * @param fields 表单字段列表(hidden=false 的字段,由外部过滤后传入) */ class FormFieldAdapter( - private val activityContext: Context, fields: MutableList ) : BaseQuickAdapter(fields) { @@ -131,7 +129,7 @@ class FormFieldAdapter( v.hideKeyboard() val popHeight = min(item.options.size.coerceAtLeast(1), 4) * 71.dp DropdownPopup( - context = activityContext, + context = context, list = item.options.toMutableList(), popWidth = v.width, popHeight = popHeight @@ -162,7 +160,7 @@ class FormFieldAdapter( v.hideKeyboard() val cal = Calendar.getInstance() DatePickerDialog( - activityContext, + context, { _, year, month, dayOfMonth -> val selected = Calendar.getInstance().apply { set(year, month, dayOfMonth, 0, 0, 0) diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/InboundIngredientAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/InboundIngredientAdapter.kt new file mode 100644 index 0000000..dec882a --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/adapter/InboundIngredientAdapter.kt @@ -0,0 +1,40 @@ +package com.shuwei.dish.match.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import com.chad.library.adapter4.BaseQuickAdapter +import com.chad.library.adapter4.viewholder.QuickViewHolder +import com.shuwei.dish.match.R +import com.shuwei.dish.match.databinding.ListItemInboundIngredientBinding +import com.shuwei.dish.match.model.NutCleanInboundIngredientVO +import com.shuwei.dish.match.utils.ext.formatDecimal + +/** + * 已入库净菜食材列表适配器 + */ +class InboundIngredientAdapter(list: MutableList) : + BaseQuickAdapter(list) { + + inner class VH(var binding: ListItemInboundIngredientBinding) : QuickViewHolder(binding.root) + + override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH { + val binding = ListItemInboundIngredientBinding.inflate( + LayoutInflater.from(context), parent, false + ) + return VH(binding) + } + + override fun onBindViewHolder(holder: VH, position: Int, item: NutCleanInboundIngredientVO?) { + item ?: return + holder.binding.run { + tvIngredientName.text = item.ingredientName ?: "-" + + val traceCode = item.traceCode.takeUnless { it.isNullOrBlank() } ?: "-" + tvTraceCode.text = "溯源码:$traceCode" + + val stock = item.currentStock.takeUnless { it.isNullOrBlank() } ?: "0" + tvStock.text = "库存:${stock.formatDecimal()}kg" + } + } +} diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/TaskDetailAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/TaskDetailAdapter.kt index 6e99a78..0ae6a40 100644 --- a/app/src/main/java/com/shuwei/dish/match/adapter/TaskDetailAdapter.kt +++ b/app/src/main/java/com/shuwei/dish/match/adapter/TaskDetailAdapter.kt @@ -1,47 +1,50 @@ package com.shuwei.dish.match.adapter import android.content.Context +import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.graphics.toColorInt import com.chad.library.adapter4.BaseQuickAdapter import com.chad.library.adapter4.viewholder.QuickViewHolder import com.shuwei.dish.match.R import com.shuwei.dish.match.databinding.ListItemTaskDetailBinding import com.shuwei.dish.match.model.NutComboTaskItemDetail +import com.shuwei.dish.match.utils.ext.formatDecimal import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.visible import java.math.BigDecimal /** * 组配任务详情食材列表适配器 + * Adapter for displaying ingredient list in task details */ class TaskDetailAdapter(list: MutableList) : BaseQuickAdapter(list) { + // ViewHolder class for holding the view bindings inner class VH(var binding: ListItemTaskDetailBinding) : QuickViewHolder(binding.root) /** - * 格式化用量字符串,去掉小数点后末尾的0 + * Called by RecyclerView to display the item at the specified position. + * This method updates the ViewHolder's contents with the item's data. + * + * @param holder The ViewHolder to be updated + * @param position The position of the item in the adapter + * @param item The NutComboTaskItemDetail to be displayed */ - private fun formatQty(value: String?): String { - if (value.isNullOrBlank()) return "0" - return try { - BigDecimal(value).stripTrailingZeros().toPlainString() - } catch (e: Exception) { - value - } - } - override fun onBindViewHolder( holder: VH, position: Int, item: NutComboTaskItemDetail? ) { + item?:return holder.binding.apply { - tvIngredientName.text = item?.ingredientName + // Set ingredient name + tvIngredientName.text = item.ingredientName // 食材类别 - val classText = when (item?.ingredientClass) { + val classText = when (item.ingredientClass) { 1 -> "主材" 2 -> "辅材" 3 -> "调料" @@ -49,16 +52,32 @@ class TaskDetailAdapter(list: MutableList) : } tvIngredientClass.text = classText - val unit = item?.unit ?: "g" - tvPerPortionQty.text = "每份用量:${formatQty(item?.perPortionQty)}${unit}" - tvActualQty.text = "实际用量:${formatQty(item?.actualQty)}${unit}" - tvTotalQty.text = "合计用量:${formatQty(item?.totalQty)}${unit}" + val unit = item.unit ?: "g" + tvPerPortionQty.text = "每份用量:${item.perPortionQty.formatDecimal()}${unit}" + tvActualQty.run { + if (item.actualQty.isNullOrBlank()) { + setTextColor("#999999".toColorInt()) + text = "-" + } else { + setTextColor("#00BC71".toColorInt()) + text = item.actualQty.formatDecimal() + unit + } + } +// tvTotalQty.text = "合计用量:${item?.totalQty.formatDecimal()}${unit}" tvTraceCode.visible() - tvTraceCode.text = "溯源码:${(item?.traceCode ?:"").ifBlank { "-" }}" + tvTraceCode.text = "溯源码:${(item.traceCode ?:"").ifBlank { "-" }}" + +// val hasWeight = item.actualQty.isNullOrBlank().not() +// ivClearIcon.imageTintList = if (hasWeight) null else ColorStateList.valueOf("#BFBFBF".toColorInt()) + + ivOperateIcon.setImageResource( + if (item.isSetFinished == true) R.drawable.ic_dish_selected + else R.drawable.ic_dish_unselected + ) root.setBackgroundResource( - if (item?.isClicked == true) R.drawable.shape_item_cook_dish + if (item.isClicked == true) R.drawable.shape_item_cook_dish else R.drawable.shape_white_fb_15_corners ) } diff --git a/app/src/main/java/com/shuwei/dish/match/dialog/InboundIngredientSearchDialog.kt b/app/src/main/java/com/shuwei/dish/match/dialog/InboundIngredientSearchDialog.kt new file mode 100644 index 0000000..106bf0a --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/dialog/InboundIngredientSearchDialog.kt @@ -0,0 +1,214 @@ +package com.shuwei.dish.match.dialog + +import android.annotation.SuppressLint +import android.graphics.Rect +import android.text.Editable +import android.view.KeyboardShortcutGroup +import android.view.LayoutInflater +import android.view.Menu +import android.view.MotionEvent +import android.view.View +import android.view.Window +import android.widget.FrameLayout +import androidx.core.widget.addTextChangedListener +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import com.chad.library.adapter4.util.setOnDebouncedItemClick +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.shuwei.dish.match.R +import com.shuwei.dish.match.adapter.InboundIngredientAdapter +import com.shuwei.dish.match.base.BaseActivity +import com.shuwei.dish.match.databinding.DialogInboundIngredientSearchBinding +import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding +import com.shuwei.dish.match.model.NutCleanInboundIngredientVO +import com.shuwei.dish.match.net.NetViewModelV2 +import com.shuwei.dish.match.net.UiState +import com.shuwei.dish.match.utils.KeyboardUtil +import com.shuwei.dish.match.utils.ext.addOnActionSearchListener +import com.shuwei.dish.match.utils.ext.toast + +/** + * 已入库净菜食材搜索弹窗 + * @param activity 宿主 Activity + * @param defIngredientName 默认填充的食材名称(可选) + * @param onItemSelected 用户点击某项食材时的回调 + */ +class InboundIngredientSearchDialog( + private val activity: BaseActivity, + private val viewModelV2: NetViewModelV2, + private val defIngredientName: String? = null, + private val onItemSelected: (item: NutCleanInboundIngredientVO) -> Unit +) : BottomSheetDialog(activity, R.style.BottomSheet) { + + private val binding = DialogInboundIngredientSearchBinding.inflate(LayoutInflater.from(activity)) + private val list = mutableListOf() + private val adapter = InboundIngredientAdapter(list).apply { + isStateViewEnable = true + setOnDebouncedItemClick { _, _, position -> + val item = list.getOrNull(position) ?: return@setOnDebouncedItemClick + onItemSelected(item) + dismiss() + } + } + + private var keyword: String? = null + + init { + setContentView(binding.root) + setCancelable(true) + behavior.skipCollapsed = false + setOnDismissListener { activity.hideStatusBar() } + + binding.tvSheetName.text = "已入库净菜检索" + + // 输入框:清空时重置列表 + binding.etSheetInput.run { + hint = "输入食材名称" + addTextChangedListener( + onTextChanged = { text, _, _, _ -> keyword = text.toString() }, + afterTextChanged = { it: Editable? -> + if (it.isNullOrBlank()) { + list.clear() + adapter.notifyDataSetChanged() + } + } + ) + addOnActionSearchListener { searchIngredient(this) } + } + + // RecyclerView 初始化 + binding.recyclerView.run { + layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false) + adapter = this@InboundIngredientSearchDialog.adapter + } + + binding.refreshLayout.run { + setEnableRefresh(true) + setEnableLoadMore(false) + setOnRefreshListener { getIngredientList() } + } + + binding.ivSearch.setOnClickListener { searchIngredient(it) } + binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) } + + // 若有默认食材名称,自动填充并触发搜索 + if (!defIngredientName.isNullOrBlank()) { + binding.etSheetInput.setText(defIngredientName) + getIngredientList() + KeyboardUtil.hideKeyboard(binding.root) + } + } + + /** 弹窗显示时在 Window.Callback 层提前拦截触摸,解决 BottomSheet 拖拽与列表滑动的手势冲突 */ + override fun show() { + super.show() + val originalCallback = window?.callback ?: return + window?.callback = object : Window.Callback by originalCallback { + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + val rect = Rect() + binding.refreshLayout.getGlobalVisibleRect(rect) + behavior.isDraggable = !rect.contains(event.rawX.toInt(), event.rawY.toInt()) + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + behavior.isDraggable = true + } + } + return originalCallback.dispatchTouchEvent(event) + } + + override fun onPointerCaptureChanged(hasCapture: Boolean) { + originalCallback.onPointerCaptureChanged(hasCapture) + } + + override fun onProvideKeyboardShortcuts( + data: List?, + menu: Menu?, + deviceId: Int + ) { + originalCallback.onProvideKeyboardShortcuts(data, menu, deviceId) + } + } + } + + /** + * 触发搜索:校验输入、隐藏键盘、发起请求 + */ + private fun searchIngredient(v: View) { + if (keyword.isNullOrBlank()) { + activity.toast("请输入食材名称") + return + } + getIngredientList() + KeyboardUtil.hideKeyboard(v) + } + + /** + * 请求食材列表,使用回调方式发起 + */ + private fun getIngredientList() { + viewModelV2.getInboundIngredientOptionsWithCallback( + keyword = keyword.takeUnless { it.isNullOrBlank() }, + onLoading = {}, + onResult = { state -> + when (state) { + is UiState.Success -> loadIngredientList(state.data) + is UiState.Error -> { + activity.toast(state.msg) + binding.refreshLayout.finishRefresh() + } + else -> {} + } + } + ) + } + + /** + * 将返回数据填充到列表 + */ + @SuppressLint("NotifyDataSetChanged") + private fun loadIngredientList(records: List?) { + binding.refreshLayout.finishRefresh() + if (records.isNullOrEmpty()) { + loadEmptyView() + return + } + binding.recyclerView.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false) + list.clear() + list.addAll(records) + adapter.notifyDataSetChanged() + } + + private var emptyViewBinding: LayoutEmptyViewBinding? = null + + @SuppressLint("NotifyDataSetChanged") + private fun loadEmptyView() { + try { + list.clear() + adapter.notifyDataSetChanged() + if (emptyViewBinding == null) { + emptyViewBinding = LayoutEmptyViewBinding.inflate( + LayoutInflater.from(activity), + binding.recyclerView, + false + ) + } + emptyViewBinding?.let { + it.tvContent.text = "暂无数据" + it.tvSubContent.text = "未查询到已入库净菜信息" + it.root.layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ) + binding.recyclerView.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false) + binding.recyclerView.post { + it.root.minimumHeight = binding.recyclerView.height + adapter.stateView = it.root + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } +} diff --git a/app/src/main/java/com/shuwei/dish/match/model/NutritionModels.kt b/app/src/main/java/com/shuwei/dish/match/model/NutritionModels.kt index 6749a1e..91a0543 100644 --- a/app/src/main/java/com/shuwei/dish/match/model/NutritionModels.kt +++ b/app/src/main/java/com/shuwei/dish/match/model/NutritionModels.kt @@ -207,18 +207,18 @@ data class NutComboTaskVO( */ @Parcelize data class NutComboTaskDetailVO( - val id: Long?, - val taskNo: String?, - val planDate: String?, - val canteenId: Long?, - val mealType: Int?, - val foodId: Long?, - val targetDish: String?, - val spec: String?, - val portions: Int?, - val comboStatus: Int?, - val ownerName: String?, - val items: List? + var id: Long? = null, + var taskNo: String? = null, + var planDate: String? = null, + var canteenId: Long? = null, + var mealType: Int? = null, + var foodId: Long? = null, + var targetDish: String? = null, + var spec: String? = null, + var portions: Int? = null, + var comboStatus: Int? = null, + var ownerName: String? = null, + var items: List? = null, ) : Parcelable /** @@ -232,10 +232,23 @@ data class NutComboTaskItemDetail( val ingredientClass: Int?, var traceCode: String?, val perPortionQty: String?, - val actualQty: String?, + var actualQty: String?, val totalQty: String?, val unit: String?, var isClicked: Boolean? = false, + var isSetFinished: Boolean? = false, +) : Parcelable + +/** + * 已入库净菜食材VO - 按食材名称查询已入库有库存的净菜记录 + */ +@Parcelize +data class NutCleanInboundIngredientVO( + val id: Long?, + val materId: Long?, + val ingredientName: String?, + val traceCode: String?, + val currentStock: String? ) : Parcelable /** diff --git a/app/src/main/java/com/shuwei/dish/match/net/ApiServiceV2.kt b/app/src/main/java/com/shuwei/dish/match/net/ApiServiceV2.kt index 180c88c..d845815 100644 --- a/app/src/main/java/com/shuwei/dish/match/net/ApiServiceV2.kt +++ b/app/src/main/java/com/shuwei/dish/match/net/ApiServiceV2.kt @@ -10,6 +10,7 @@ import com.shuwei.dish.match.model.NutSupHygieneIngredientOptionVO import com.shuwei.dish.match.model.NutProdComboVideoVO import com.shuwei.dish.match.model.NutProdComboTaskDTO import com.shuwei.dish.match.model.IdDTO +import com.shuwei.dish.match.model.NutCleanInboundIngredientVO import com.shuwei.dish.match.model.NutComboTaskDetailVO import com.shuwei.dish.match.model.NutComboTaskVO import retrofit2.http.Body @@ -77,6 +78,15 @@ interface ApiServiceV2 { @Query("traceCode") traceCode: String ): ApiResponse> + /** + * 按食材名称查询已入库有库存的净菜记录 + */ + @GET + suspend fun getInboundIngredientOptions( + @Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/inbound-ingredient-options", + @Query("keyword") keyword: String? = null + ): ApiResponse> + // ========== 二、净菜组配接口 ========== /** @@ -85,7 +95,7 @@ interface ApiServiceV2 { @POST suspend fun addComboTask( @Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/combo-task/add", - @Body param: NutProdComboTaskDTO + @Body param: NutComboTaskDetailVO ): ApiResponse /** diff --git a/app/src/main/java/com/shuwei/dish/match/net/NetViewModelV2.kt b/app/src/main/java/com/shuwei/dish/match/net/NetViewModelV2.kt index 35eb7b8..cc46329 100644 --- a/app/src/main/java/com/shuwei/dish/match/net/NetViewModelV2.kt +++ b/app/src/main/java/com/shuwei/dish/match/net/NetViewModelV2.kt @@ -3,6 +3,7 @@ package com.shuwei.dish.match.net import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.shuwei.dish.match.model.NutCanteenVO +import com.shuwei.dish.match.model.NutCleanInboundIngredientVO import com.shuwei.dish.match.model.NutComboTaskDetailVO import com.shuwei.dish.match.model.NutComboTaskVO import com.shuwei.dish.match.model.NutDictItemVO @@ -28,7 +29,8 @@ class NetViewModelV2( // 食堂下拉列表 private val _canteenOptionsState = MutableStateFlow?>>(UiState.Idle) - val canteenOptionsState: StateFlow?>> = _canteenOptionsState.asStateFlow() + val canteenOptionsState: StateFlow?>> = + _canteenOptionsState.asStateFlow() fun getCanteenOptions(keyword: String? = null) { viewModelScope.launch { @@ -49,8 +51,10 @@ class NetViewModelV2( // } // 食材下拉列表 - private val _materOptionsState = MutableStateFlow?>>(UiState.Idle) - val materOptionsState: StateFlow?>> = _materOptionsState.asStateFlow() + private val _materOptionsState = + MutableStateFlow?>>(UiState.Idle) + val materOptionsState: StateFlow?>> = + _materOptionsState.asStateFlow() fun getMaterOptions(keyword: String? = null) { viewModelScope.launch { @@ -60,8 +64,10 @@ class NetViewModelV2( } // 包装设备下拉 - private val _deviceOptionsState = MutableStateFlow?>>(UiState.Idle) - val deviceOptionsState: StateFlow?>> = _deviceOptionsState.asStateFlow() + private val _deviceOptionsState = + MutableStateFlow?>>(UiState.Idle) + val deviceOptionsState: StateFlow?>> = + _deviceOptionsState.asStateFlow() fun getDeviceOptions() { viewModelScope.launch { @@ -72,7 +78,8 @@ class NetViewModelV2( // 餐品下拉搜索 private val _foodOptionsState = MutableStateFlow?>>(UiState.Idle) - val foodOptionsState: StateFlow?>> = _foodOptionsState.asStateFlow() + val foodOptionsState: StateFlow?>> = + _foodOptionsState.asStateFlow() // fun getFoodOptions(keyword: String? = null, pageNum: Int = 1, pageSize: Int = 10) { // viewModelScope.launch { @@ -125,8 +132,10 @@ class NetViewModelV2( } // 按溯源码查食材信息 - private val _ingredientByTraceState = MutableStateFlow?>>(UiState.Idle) - val ingredientByTraceState: StateFlow?>> = _ingredientByTraceState.asStateFlow() + private val _ingredientByTraceState = + MutableStateFlow?>>(UiState.Idle) + val ingredientByTraceState: StateFlow?>> = + _ingredientByTraceState.asStateFlow() fun getIngredientByTrace(traceCode: String) { viewModelScope.launch { @@ -141,12 +150,29 @@ class NetViewModelV2( private val _addComboTaskState = MutableStateFlow>(UiState.Idle) val addComboTaskState: StateFlow> = _addComboTaskState.asStateFlow() - fun addComboTask(param: NutProdComboTaskDTO) { + fun addComboTask(param: NutComboTaskDetailVO, block: (Boolean, String) -> Unit) { viewModelScope.launch { - _addComboTaskState.value = UiState.Loading - _addComboTaskState.value = repository.addComboTask(param) + val uiState = repository.addComboTask(param) + when (uiState) { + is UiState.Success -> { + block(true, "") + } + + is UiState.Error -> { + block(false, uiState.msg) + } + + is UiState.Loading -> {} + UiState.Idle -> {} + } } } +// fun addComboTask(param: NutProdComboTaskDTO) { +// viewModelScope.launch { +// _addComboTaskState.value = UiState.Loading +// _addComboTaskState.value = repository.addComboTask(param) +// } +// } // 开始组配 private val _startComboTaskState = MutableStateFlow>(UiState.Idle) @@ -172,7 +198,8 @@ class NetViewModelV2( // 获取组配任务视频监控地址 private val _comboTaskVideoState = MutableStateFlow>(UiState.Idle) - val comboTaskVideoState: StateFlow> = _comboTaskVideoState.asStateFlow() + val comboTaskVideoState: StateFlow> = + _comboTaskVideoState.asStateFlow() fun getComboTaskVideo(taskNo: String) { viewModelScope.launch { @@ -220,7 +247,14 @@ class NetViewModelV2( ) { viewModelScope.launch { onLoading() - onResult(repository.getIncompleteComboTasks(deviceCode = deviceCode, keyword = keyword, pageNum = pageNum, pageSize = pageSize)) + onResult( + repository.getIncompleteComboTasks( + deviceCode = deviceCode, + keyword = keyword, + pageNum = pageNum, + pageSize = pageSize + ) + ) } } @@ -238,4 +272,20 @@ class NetViewModelV2( } } + // ========== 已入库净菜食材接口 ========== + + /** + * 按食材名称查询已入库有库存的净菜记录(回调版本) + */ + fun getInboundIngredientOptionsWithCallback( + keyword: String? = null, + onLoading: () -> Unit = {}, + onResult: (UiState?>) -> Unit + ) { + viewModelScope.launch { + onLoading() + onResult(repository.getInboundIngredientOptions(keyword)) + } + } + } diff --git a/app/src/main/java/com/shuwei/dish/match/net/RemoteRepositoryV2.kt b/app/src/main/java/com/shuwei/dish/match/net/RemoteRepositoryV2.kt index bd1e8c1..6f0a417 100644 --- a/app/src/main/java/com/shuwei/dish/match/net/RemoteRepositoryV2.kt +++ b/app/src/main/java/com/shuwei/dish/match/net/RemoteRepositoryV2.kt @@ -2,6 +2,7 @@ package com.shuwei.dish.match.net import com.shuwei.dish.match.model.IdDTO import com.shuwei.dish.match.model.NutCanteenVO +import com.shuwei.dish.match.model.NutCleanInboundIngredientVO import com.shuwei.dish.match.model.NutComboTaskDetailVO import com.shuwei.dish.match.model.NutComboTaskVO import com.shuwei.dish.match.model.NutDictItemVO @@ -65,9 +66,12 @@ class RemoteRepositoryV2 { suspend fun getIngredientByTrace(traceCode: String): UiState?> = safeApiCall { apiService.getIngredientByTrace(traceCode = traceCode) } + suspend fun getInboundIngredientOptions(keyword: String? = null): UiState?> = + safeApiCall { apiService.getInboundIngredientOptions(keyword = keyword) } + // ========== 净菜组配接口 ========== - suspend fun addComboTask(param: NutProdComboTaskDTO): UiState = + suspend fun addComboTask(param: NutComboTaskDetailVO): UiState = safeApiCall { apiService.addComboTask(param = param) } suspend fun startComboTask(id: Long): UiState = diff --git a/app/src/main/java/com/shuwei/dish/match/ui/CleanPackActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/CleanPackActivity.kt index cc5b3d8..dbaf860 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/CleanPackActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/CleanPackActivity.kt @@ -37,7 +37,7 @@ class CleanPackActivity : BaseActivity() { private lateinit var binding: ActivityCleanPackBinding private val fields = mutableListOf() - private val formAdapter by lazy { FormFieldAdapter(this, fields) } + private val formAdapter by lazy { FormFieldAdapter(fields) } private val viewModelV2 by lazy { NetViewModelV2() } private var packType: Int = TYPE_CLEAN @@ -241,17 +241,13 @@ class CleanPackActivity : BaseActivity() { fields.add(packagingSpecField!!) // 包装方式(下拉) - fields.add( + packageMethodField = FormField( label = "包装方式", type = FieldType.DROPDOWN, extraApiKeys = mapOf("packageMethodId" to "id", "packageMethod" to "value"), - required = true, - options = mutableListOf( - DictType(id = "1", value = "真空包装", type = "真空包装"), - DictType(id = "2", value = "气调包装", type = "气调包装"), - ) + required = true ) - ) + fields.add(packageMethodField!!) // 包装份数 fields.add( @@ -277,14 +273,15 @@ class CleanPackActivity : BaseActivity() { required = true ), FormField( - label = "分类", type = FieldType.DROPDOWN, - extraApiKeys = mapOf("classify" to "value"), + label = "分类", type = FieldType.FIXED, +// extraApiKeys = mapOf("classify" to "value"), + apiKey = "classify", required = true, - options = mutableListOf( - DictType(id = "1", value = "蔬菜类", type = "蔬菜类"), - DictType(id = "2", value = "肉禽类", type = "肉禽类"), - DictType(id = "3", value = "水产类", type = "水产类"), - ) +// options = mutableListOf( +// DictType(id = "1", value = "蔬菜类", type = "蔬菜类"), +// DictType(id = "2", value = "肉禽类", type = "肉禽类"), +// DictType(id = "3", value = "水产类", type = "水产类"), +// ) ), FormField( label = "每份用量", @@ -401,6 +398,7 @@ class CleanPackActivity : BaseActivity() { // 加载下拉选项 loadFoodOptionsWithKeyword() + loadDictItemsWithCallback("sup_package_method", packageMethodField!!) loadDictItemsWithCallback("sup_package_spec", packagingSpecField!!) loadDeviceOptionsWithCallback(deviceField) } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/PackActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/PackActivity.kt index 3cd6a63..662febf 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/PackActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/PackActivity.kt @@ -10,17 +10,23 @@ import android.view.ViewGroup import androidx.activity.addCallback import androidx.core.content.IntentCompat import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.shuwei.dish.match.R import com.shuwei.dish.match.adapter.FoodMaterialAdapter +import com.shuwei.dish.match.adapter.FormFieldAdapter import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.databinding.ActivityPackBinding import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding import com.shuwei.dish.match.dialog.CommonDialog import com.shuwei.dish.match.model.CookFoodDTO import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity +import com.shuwei.dish.match.dialog.InboundIngredientSearchDialog +import com.shuwei.dish.match.model.DictType +import com.shuwei.dish.match.model.FieldType import com.shuwei.dish.match.model.FoodRecord +import com.shuwei.dish.match.model.FormField import com.shuwei.dish.match.model.GoodsItem import com.shuwei.dish.match.model.NutFoodOptionVO import com.shuwei.dish.match.net.UiState @@ -127,6 +133,7 @@ class PackActivity : BaseActivity() { } } } + private var currentWeight = 0.0 private fun addViewClickListener() { // ========== 重量识别相关功能已注释 ========== @@ -198,7 +205,7 @@ class PackActivity : BaseActivity() { } binding.ivAddWeight.clickWithDebounce { - if(currentWeight <= 0.0) { + if (currentWeight <= 0.0) { toast("当前秤重量无效") return@clickWithDebounce } @@ -209,8 +216,17 @@ class PackActivity : BaseActivity() { } } binding.btnGetTraceCode.clickWithDebounce { - val item = list.getOrNull(clickIndex)?:return@clickWithDebounce - //viewModelV2.getCode + val item = list.getOrNull(clickIndex) ?: return@clickWithDebounce + InboundIngredientSearchDialog( + activity = this, + viewModelV2 = viewModelV2, + defIngredientName = item.goodsName + ) { vo -> + materialAdapter.getItem(clickIndex)?.let { + it.materCode = vo.traceCode + materialAdapter.notifyItemChanged(clickIndex) + } + }.show() } binding.btnSubmit.clickWithDebounce { @@ -316,6 +332,7 @@ class PackActivity : BaseActivity() { } loadDishDetail(detail) } + is UiState.Error -> toast(state.msg) else -> {} } @@ -367,6 +384,13 @@ class PackActivity : BaseActivity() { private var firstReqSize = 0 private val list = mutableListOf() private var clickIndex = -1 + + private val fields = mutableListOf() + + private val formAdapter by lazy { + FormFieldAdapter(fields) + } + private val materialAdapter by lazy { FoodMaterialAdapter(list).apply { onItemClick = onItemClick@{ positon -> @@ -409,12 +433,153 @@ class PackActivity : BaseActivity() { } } } + private var packageMethodField: FormField? = null + private var deviceField: FormField? = null + private fun initFormList() { + packageMethodField = + FormField( + label = "包装方式", type = FieldType.DROPDOWN, + extraApiKeys = mapOf("packageMethodId" to "id", "packageMethod" to "value"), + required = true + ) + fields.add(packageMethodField!!) + fields.add( + FormField( + label = "包装份数", type = FieldType.INTEGER, + apiKey = "quantity", required = true, hint = "请输入份数" + ) + ) +// fields.add( +// FormField( +// label = "包装日期", +// type = FieldType.DATE_PICKER, +// apiKey = "packageDate", +// required = true +// ) +// ) + fields.add( + FormField( + label = "保质期至", + type = FieldType.DATE_PICKER, + apiKey = "expiryDate", + required = true + ) + ) + + fields.add( + FormField( + label = "存放温度(℃)", + type = FieldType.DECIMAL, + apiKey = "storageTemp", + required = false, + hint = "请输入小数" + ) + ) + + fields.add( + FormField( + label = "包装人", + type = FieldType.TEXT, + apiKey = "operator", + required = false + ) + ) + deviceField = FormField( + label = "终端设备", + type = FieldType.DROPDOWN, + extraApiKeys = mapOf("deviceId" to "id"), + required = false + ) + fields.add(deviceField!!) +// fields.add( +// FormField( +// label = "操作类型", type = FieldType.FIXED, +// apiKey = "opType", +// required = true, +// value = "设备自动" +// ) +// ) + loadDictItemsWithCallback("sup_package_method", packageMethodField!!) + loadDeviceOptionsWithCallback(deviceField) + } + + /** + * 加载字典项下拉(回调版本),适合多次不同 dictType 的场景 + */ + private fun loadDictItemsWithCallback(dictType: String, targetField: FormField) { + viewModelV2.getDictItemsWithCallback( + dictType = dictType, + onLoading = {}, + onResult = { state -> + when (state) { + is UiState.Success -> { + val options = state.data?.map { vo -> + DictType( + id = vo.id?.toString() ?: "", + value = vo.dictLabel ?: "", + type = vo.dictCode ?: "" + ) + } ?: emptyList() + targetField.options.clear() + targetField.options.addAll(options) + val index = fields.indexOf(targetField) + if (index >= 0) formAdapter.notifyItemChanged(index) + } + + is UiState.Error -> { + toast("加载字典项失败:${state.msg}") + } + + else -> {} + } + } + ) + } + + /** + * 加载设备下拉(回调版本),用于餐品净菜包装 + */ + private fun loadDeviceOptionsWithCallback(deviceField: FormField? = null) { + val field = deviceField ?: return + viewModelV2.getDeviceOptionsWithCallback( + onLoading = {}, + onResult = { state -> + when (state) { + is UiState.Success -> { + val options = state.data?.map { vo -> + DictType( + id = vo.id?.toString() ?: "", + value = vo.deviceName ?: "", + type = vo.deviceNo ?: "" + ) + } ?: emptyList() + field.options.clear() + field.options.addAll(options) + val index = fields.indexOf(field) + if (index >= 0) formAdapter.notifyItemChanged(index) + } + + is UiState.Error -> { + toast("加载设备列表失败:${state.msg}") + } + + else -> {} + } + } + ) + } /** * RecyclerView初始化 */ @SuppressLint("ClickableViewAccessibility") private fun initRecyclerView() { + initFormList() + binding.rvFormList.let { + it.layoutManager = GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false) + it.adapter = formAdapter + } + binding.rvMaterialList.let { it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) // 仅 isNewDishType=true 的 item 显示侧滑删除菜单(必须在 setAdapter 之前调用) @@ -570,7 +735,11 @@ class PackActivity : BaseActivity() { } startActivity(launchIntent) { resultIntent -> if (resultIntent == null) return@startActivity - val food = IntentCompat.getSerializableExtra(resultIntent, FoodSearchActivity.FOOD_ITEM, FoodRecord::class.java) + val food = IntentCompat.getSerializableExtra( + resultIntent, + FoodSearchActivity.FOOD_ITEM, + FoodRecord::class.java + ) Log.d(TAG, "jumpSearch: record=$food") if (food == null) return@startActivity binding.etInputDish.run { @@ -583,13 +752,15 @@ class PackActivity : BaseActivity() { } private fun handleRvTouchHideKeyboard() { - val gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() { - override fun onSingleTapUp(e: MotionEvent): Boolean { - hideKeyboard() - return false - } - }) - binding.rvMaterialList.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() { + val gestureDetector = + GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapUp(e: MotionEvent): Boolean { + hideKeyboard() + return false + } + }) + binding.rvMaterialList.addOnItemTouchListener(object : + RecyclerView.SimpleOnItemTouchListener() { private var startX = 0f private var startY = 0f @@ -599,6 +770,7 @@ class PackActivity : BaseActivity() { startX = e.x startY = e.y } + MotionEvent.ACTION_MOVE -> { val dx = abs(e.x - startX) val dy = abs(e.y - startY) @@ -613,4 +785,9 @@ class PackActivity : BaseActivity() { }) } + override fun onDestroy() { + super.onDestroy() + WeightUtil.removeWeightListener(TAG) + } + } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/TaskDetailActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/TaskDetailActivity.kt index baf9cca..8a27f7b 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/TaskDetailActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/TaskDetailActivity.kt @@ -1,18 +1,28 @@ package com.shuwei.dish.match.ui import android.os.Bundle +import android.util.Log import androidx.core.content.IntentCompat import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import com.shuwei.dish.match.R import com.shuwei.dish.match.adapter.TaskDetailAdapter import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.databinding.ActivityTaskDetailBinding +import com.shuwei.dish.match.dialog.CommonDialog +import com.shuwei.dish.match.dialog.InboundIngredientSearchDialog import com.shuwei.dish.match.model.NutComboTaskDetailVO +import com.shuwei.dish.match.model.NutComboTaskItemDetail import com.shuwei.dish.match.model.NutComboTaskVO import com.shuwei.dish.match.net.NetViewModelV2 import com.shuwei.dish.match.net.UiState +import com.shuwei.dish.match.utils.AddressUtil +import com.shuwei.dish.match.utils.SpTool +import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.ext.clickWithDebounce import com.shuwei.dish.match.utils.ext.gone +import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace +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 kotlinx.coroutines.launch @@ -27,23 +37,60 @@ class TaskDetailActivity : BaseActivity() { const val TASK_ITEM = "taskItem" } + private var taskDetailVO: NutComboTaskDetailVO? = null private val binding by lazy { ActivityTaskDetailBinding.inflate(layoutInflater) } private val viewModelV2 by lazy { NetViewModelV2() } private var taskItem: NutComboTaskVO? = null - private val itemList = mutableListOf() + private var clickIndex = -1 + private val comboTaskList = mutableListOf() private val itemAdapter by lazy { - TaskDetailAdapter(itemList).apply { + TaskDetailAdapter(comboTaskList).apply { setOnItemClickListener { adapter, view, position -> - getItem(position) ?: return@setOnItemClickListener - itemList.forEachIndexed { index, detail -> - detail.isClicked = index == position + clickItem(this, position) + } + addOnItemChildClickListener(R.id.ivClearIcon) { _, _, position -> + val item = getItem(position) ?: return@addOnItemChildClickListener + Log.d(PrepareFoodActivity.Companion.TAG, "onFoodItemClick: ${item.toJsonString()}") + // 无重量信息时转为 item 整体点击(触发选中效果) + if (item.actualQty.isNullOrBlank()) { + clickItem(this, position) + return@addOnItemChildClickListener + } + deleteRemindDialog("清除确认", "确定清除食材「${item.ingredientName}」添加的溯源码和重量吗?") { + item.run { + actualQty = null + traceCode = null + isSetFinished = false + } + notifyItemChanged(position) } - notifyDataSetChanged() } } } + /** + * 删除提醒 + */ + private fun deleteRemindDialog(title: String, content: String, action: () -> Unit) { + CommonDialog(this) + .setTitle(title) + .setContent(content) + .setNegativeButton("取消") + .setPositiveButton("确认") { + action() + }.show() + } + + private fun clickItem(adapter: TaskDetailAdapter, position: Int) { + adapter.getItem(position) ?: return + comboTaskList.forEachIndexed { index, detail -> + detail.isClicked = index == position + } + adapter.notifyDataSetChanged() + this@TaskDetailActivity.clickIndex = position + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) @@ -64,8 +111,31 @@ class TaskDetailActivity : BaseActivity() { }) initRecyclerView() + addWeightListener() initView() loadDetail() + + lifecycleScope.launch { + viewModelV2.startComboTaskState.collect { state -> + when (state) { + is UiState.Success -> { + dismissLoading() + toast("已开始组配") + // 刷新详情 + loadDetail() + // 更新本地状态 + taskItem?.comboStatus = 1 + } + + is UiState.Error -> { + dismissLoading() + toast(state.msg) + } + + else -> {} + } + } + } } /** @@ -92,8 +162,59 @@ class TaskDetailActivity : BaseActivity() { } } binding.btnGetTraceCode.clickWithDebounce { - + val item = itemAdapter.getItem(clickIndex) ?: return@clickWithDebounce + InboundIngredientSearchDialog( + activity = this, + viewModelV2 = viewModelV2, + defIngredientName = item.ingredientName + ) { vo -> + itemAdapter.getItem(clickIndex)?.let { + it.traceCode = vo.traceCode + itemAdapter.notifyItemChanged(clickIndex) + } + }.show() } + binding.ivAddWeight.clickWithDebounce { + if (currentWeight <= 0.0) { + toast("当前秤重量无效") + return@clickWithDebounce + } + itemAdapter.getItem(clickIndex)?.let { + it.actualQty = currentWeight.toString() + it.isSetFinished = true + itemAdapter.notifyItemChanged(clickIndex) + } + } + binding.ivClearWeight.clickWithDebounce { + initialWeight = 0.0 + WeightUtil.tareTwo(1) + } + } + + private var currentWeight: Double = 0.0 + + /** 初始秤读数,净重 = 秤读数 - initialWeight,从 SpTool.configWeight 读取 */ + private var initialWeight = SpTool.configWeight.toDouble() + + private fun addWeightListener() { + WeightUtil.addWeightListener( + weightKey = TAG, + getWeight = { address, state, weight -> + if (address == AddressUtil.ONE && state == WeightUtil.STATE_STABLE) { + runOnUiThread { + currentWeight = weight - initialWeight + binding.tvShowWeight.run { + if (tag != currentWeight) { +// text = "称重:${netWeight}g" + val weightText = "${currentWeight.roundedOneDecimalPlace()}g" + text = weightText + tag = currentWeight + } + } + //recognizeFood(netWeight) + } + } + }) } /** @@ -118,22 +239,22 @@ class TaskDetailActivity : BaseActivity() { dismissLoading() when (state) { is UiState.Success -> { - val detail = state.data - if (detail == null) { + taskDetailVO = state.data + if (taskDetailVO == null) { toast("详情为空") return@onResult } // 更新状态(可能已被其他设备修改) - binding.tvStatus.text = getStatusText(detail.comboStatus) - if (detail.comboStatus != 0) { + binding.tvStatus.text = getStatusText(taskDetailVO!!.comboStatus) + if (taskDetailVO!!.comboStatus != 0) { binding.btnStart.gone() } // 加载食材列表 - val items = detail.items + val items = taskDetailVO!!.items if (!items.isNullOrEmpty()) { - itemList.clear() - itemList.addAll(items) + comboTaskList.clear() + comboTaskList.addAll(items) itemAdapter.notifyDataSetChanged() } else { toast("该任务无食材信息") @@ -151,29 +272,23 @@ class TaskDetailActivity : BaseActivity() { * 开始组配 */ private fun startComboTask(taskId: Long) { + val task = taskDetailVO ?: return + val count = comboTaskList.count { it.traceCode.isNullOrBlank() } + if (count > 0) { + toast("存在未设置的溯源码") + return + } showLoading("正在开始组配……") lifecycleScope.launch { - viewModelV2.startComboTask(taskId) - } - lifecycleScope.launch { - viewModelV2.startComboTaskState.collect { state -> - when (state) { - is UiState.Success -> { - dismissLoading() - toast("已开始组配") - // 刷新详情 - loadDetail() - // 更新本地状态 - taskItem?.comboStatus = 1 - } - - is UiState.Error -> { - dismissLoading() - toast(state.msg) - } - - else -> {} + //viewModelV2.startComboTask(taskId) + task.items = comboTaskList + viewModelV2.addComboTask(param = task) { state, msg -> + dismissLoading() + if (state.not()) { + toast(msg) + return@addComboTask } + toast("上报成功") } } } @@ -189,4 +304,10 @@ class TaskDetailActivity : BaseActivity() { else -> "未知" } } + + override fun onDestroy() { + super.onDestroy() + WeightUtil.removeWeightListener(TAG) + } + } diff --git a/app/src/main/java/com/shuwei/dish/match/utils/ext/Common.kt b/app/src/main/java/com/shuwei/dish/match/utils/ext/Common.kt index 21f0699..d0671d0 100644 --- a/app/src/main/java/com/shuwei/dish/match/utils/ext/Common.kt +++ b/app/src/main/java/com/shuwei/dish/match/utils/ext/Common.kt @@ -167,4 +167,16 @@ fun View.hideKeyboard() { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(this.windowToken, 0) this.clearFocus() // 清除焦点避免键盘再次弹出 +} + +/** + * 格式化用量字符串,去掉小数点后末尾的0 + */ +fun String?.formatDecimal(): String { + if (this.isNullOrBlank()) return "0" + return try { + BigDecimal(this).stripTrailingZeros().toPlainString() + } catch (e: Exception) { + this + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pack.xml b/app/src/main/res/layout/activity_pack.xml index aca6137..8947300 100644 --- a/app/src/main/res/layout/activity_pack.xml +++ b/app/src/main/res/layout/activity_pack.xml @@ -88,6 +88,19 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -111,9 +222,9 @@ - diff --git a/app/src/main/res/layout/dialog_inbound_ingredient_search.xml b/app/src/main/res/layout/dialog_inbound_ingredient_search.xml new file mode 100644 index 0000000..d6267e0 --- /dev/null +++ b/app/src/main/res/layout/dialog_inbound_ingredient_search.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_item_food_material.xml b/app/src/main/res/layout/list_item_food_material.xml index 185dc8d..cb74095 100644 --- a/app/src/main/res/layout/list_item_food_material.xml +++ b/app/src/main/res/layout/list_item_food_material.xml @@ -73,7 +73,7 @@ android:layout_width="80dp" android:layout_height="60dp" android:layout_marginEnd="10dp" - android:src="@drawable/ic_delete" + android:src="@drawable/ic_dish_clear" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/list_item_inbound_ingredient.xml b/app/src/main/res/layout/list_item_inbound_ingredient.xml new file mode 100644 index 0000000..fff99c3 --- /dev/null +++ b/app/src/main/res/layout/list_item_inbound_ingredient.xml @@ -0,0 +1,49 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/list_item_task_detail.xml b/app/src/main/res/layout/list_item_task_detail.xml index 9432390..a4fd2c4 100644 --- a/app/src/main/res/layout/list_item_task_detail.xml +++ b/app/src/main/res/layout/list_item_task_detail.xml @@ -1,17 +1,62 @@ - + android:paddingVertical="15dp"> + + + + + + + android:id="@+id/llFoodInfo" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="20dp" + android:layout_marginStart="20dp" + app:layout_constraintEnd_toStartOf="@id/tvActualQty" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed"> - - - - - - - - - + android:layout_marginVertical="8dp" + android:textColor="@color/black666" + android:textSize="20sp" + app:layout_constraintBottom_toTopOf="@+id/tvTraceCode" + app:layout_constraintStart_toStartOf="@id/llFoodInfo" + app:layout_constraintTop_toBottomOf="@id/llFoodInfo" + tools:text="每份用量:200g" /> - +