Author SHA1 Message Date
lvmeng 05c5fe7932 fix(utils): 解决数字转换异常问题
- 修改DecimalAdapter中字符串转double的逻辑,使用安全转换避免崩溃
- 更新PrepareCookActivity中重量验证逻辑,使用安全转换处理无效输入
- 防止因无效数字格式导致的应用崩溃问题
2026-05-25 16:08:12 +08:00
mazengfei b08a77fc9d 提交shape文件 2026-05-25 10:25:45 +08:00
mazengfei 82a9bd15d9 解决秤地址10开始无法清零的问题 2026-05-22 17:59:00 +08:00
mazengfei 8aa5158b41 增加unInit回调 2026-05-22 17:31:38 +08:00
mazengfei 82960b26a0 优化秤重量显示,量程溢出时增加红色背景提示 2026-05-22 17:29:36 +08:00
mazengfei 863cd70118 优化配置页面调料格式显示 2026-05-22 16:34:06 +08:00
mazengfei e8037b12b7 优化调料格子 2026-05-22 10:32:07 +08:00
mazengfei 13390f59dc 优化 2026-05-22 09:50:22 +08:00
mazengfei 335b56eaea 布局优化 2026-05-22 09:47:40 +08:00
lvmeng 2d4f7619aa feat(build): 添加 Flexbox 库依赖
- 集成 Google Android Flexbox 库版本 3.0.0
- 为项目提供灵活的布局管理功能
2026-05-22 09:02:23 +08:00
lvmeng e82fffb40a refactor(app): 优化设备ID管理和秤SDK兼容性
- 移除BaseApp中的deviceId全局变量
- 添加对x86_64架构支持以用于Windows模拟器调试
- 增加BuildConfig字段IS_TEST_DEVICE控制测试设备逻辑
- 启用buildConfig功能支持编译时配置
- 实现测试设备使用固定DEVICE_ID_2替代真实UDID
- 将基础URL从测试环境切换到生产环境
- 重构WeightUtil支持多架构并添加错误处理
- 添加秤SDK可用性检查避免非ARM设备崩溃
2026-05-22 08:59:19 +08:00
lvmeng 30f6c74a99 旧版配比秤迁移新版接口与数据库逻辑
- 新增 net 包:ApiService、RemoteRepository、NetViewModel、UiState、ErrorType 等,替换旧 http 包
- 新增 model 包:CookFoodDTO、GoodsItem、FoodRecord 等 UI 层数据模型
- 重构 db 层:新增 DbRepository、DbViewModel,更新 Room Entity 和 DAO,升级数据库版本至13
- 删除旧 entity 包、http 包、viewmodel/factory 包
- 适配器(DishPartAdapter、GoodsInfoSearchAdapter、SeasoningSearchAdapter)改用 model 类,不再依赖数据库实体
- BaseActivity 统一通过 by viewModels() 提供 appViewModel/netViewModel/dbViewModel,移除各 Activity 工厂模式初始化
- 新增 GlobalData、ResetReasoningRecord;ErrorType 补充 getApiException 函数;build.gradle 添加 Retrofit2 依赖
2026-05-21 18:21:05 +08:00
77 changed files with 3249 additions and 1786 deletions
+12 -4
View File
@@ -18,9 +18,8 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
//设置支持的SO库架构
abiFilters.addAll(listOf("armeabi-v7a"))
//, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
//设置支持的SO库架构x86_64 仅用于在 Windows 模拟器上调试(秤功能不可用)
abiFilters.addAll(listOf("armeabi-v7a", "x86_64"))
}
// 设置输出APK文件名格式
@@ -42,10 +41,13 @@ android {
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("debug_507")
buildConfigField("Boolean", "IS_TEST_DEVICE", "false")
}
debug {
//507扫码秤主板在android.buildTypes{}内增加
signingConfig = signingConfigs.getByName("debug_507")
// 调试构建视为测试设备,使用固定的 DEVICE_ID_1 代替真实 UDID
buildConfigField("Boolean", "IS_TEST_DEVICE", "false")
}
}
compileOptions {
@@ -57,10 +59,11 @@ android {
}
buildFeatures {
viewBinding = true
buildConfig = true
}
packagingOptions {
pickFirst("lib/armeabi-v7a/libserial_port.so")
pickFirst("lib/arme64-v8a/libserial_port.so")
pickFirst("lib/arm64-v8a/libserial_port.so")
}
sourceSets {
named("main") {
@@ -88,6 +91,9 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
implementation("com.squareup.okhttp3:okhttp:4.9.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.google.code.gson:gson:2.10.1")
// implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30")
@@ -112,4 +118,6 @@ dependencies {
//经典加载
//implementation("com.geyifeng.immersionbar:immersionbar:3.2.2")
implementation("com.google.android.flexbox:flexbox:3.0.0")
}
@@ -8,12 +8,11 @@ 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.ListItemDishCookBinding
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
import java.text.DecimalFormat
class DishPartAdapter(list: MutableList<CookFoodGoodsEntity>) :
BaseQuickAdapter<CookFoodGoodsEntity, DishPartAdapter.VH>(list) {
class DishPartAdapter(list: MutableList<GoodsItem>) :
BaseQuickAdapter<GoodsItem, DishPartAdapter.VH>(list) {
inner class VH(var binding: ListItemDishCookBinding) : QuickViewHolder(binding.root)
@@ -23,11 +22,10 @@ class DishPartAdapter(list: MutableList<CookFoodGoodsEntity>) :
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: CookFoodGoodsEntity?) {
override fun onBindViewHolder(holder: VH, position: Int, item: GoodsItem?) {
holder.binding.run {
tvDishName.text = item!!.goodsName
tvDishType.text = if (item.materialType == 1) "主辅材:主材" else if (item.materialType == 2) "主辅材:辅材" else ""
//"${DecimalFormat("#").format(item.useWeight)}克"
tvDishWeight.text =
if (item.useWeight == null || item.useWeight == 0.toDouble()) "" else "${item.useWeight!!.roundedOneDecimalPlace()}"
tvDishWeight.setTextColor(
@@ -38,20 +36,16 @@ class DishPartAdapter(list: MutableList<CookFoodGoodsEntity>) :
)
ivOperateIcon.setImageResource(
if (item.isSamplingPage)
//采集页面
R.drawable.ic_delete
else
//制作页面
if (item.isSetFinished) R.drawable.ic_dish_selected
else R.drawable.ic_dish_unselected
)
root.run {
setBackgroundResource(
if (item.isItemClicked) R.drawable.shape_item_cook_dish
root.setBackgroundResource(
if (item.isClicked) R.drawable.shape_item_cook_dish
else R.drawable.shape_white_fb_15_corners
)
}
}
}
}
@@ -7,7 +7,7 @@ import androidx.core.content.ContextCompat
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.R
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.databinding.ListItemDishBinding
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.visible
@@ -6,7 +6,7 @@ import android.view.ViewGroup
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.databinding.ListItemDishRecordBinding
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.model.FoodRecord
class FoodRecordAdapter(list: MutableList<FoodRecord>) :
BaseQuickAdapter<FoodRecord, FoodRecordAdapter.VH>(list) {
@@ -8,15 +8,15 @@ 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.ListItemSearchGoodsInfoBinding
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.model.GoodsItem
class GoodsInfoSearchAdapter(private var list: MutableList<CookFoodGoodsEntity>) :
BaseQuickAdapter<CookFoodGoodsEntity, GoodsInfoSearchAdapter.VH>(list) {
class GoodsInfoSearchAdapter(private var list: MutableList<GoodsItem>) :
BaseQuickAdapter<GoodsItem, GoodsInfoSearchAdapter.VH>(list) {
override fun onBindViewHolder(
holder: VH,
position: Int,
item: CookFoodGoodsEntity?
item: GoodsItem?
) {
holder.binding.tvGoodsInfo.run {
text = item?.goodsName
@@ -8,7 +8,7 @@ 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.ListItemHomeModeBinding
import com.shuwei.dish.match.entity.HomeModeBean
import com.shuwei.dish.match.model.HomeModeBean
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.visible
@@ -7,7 +7,7 @@ import androidx.core.content.ContextCompat
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.R
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.databinding.ListItemDishBinding
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.visible
@@ -0,0 +1,38 @@
//package com.shuwei.dish.match.adapter
//
//import android.content.Context
//import android.view.LayoutInflater
//import android.view.ViewGroup
//import androidx.core.view.updateLayoutParams
//import com.chad.library.adapter4.BaseQuickAdapter
//import com.chad.library.adapter4.viewholder.QuickViewHolder
//import com.shuwei.dish.match.databinding.ListItemCellBinding
//import com.shuwei.dish.match.model.GoodsItem
//import com.shuwei.dish.match.utils.ext.dp
//
///**
// * 调料格子列表适配器,使用 list_item_cell 布局展示调料名称与用量
// */
//class SeasoningAdapter(list: MutableList<GoodsItem>) :
// BaseQuickAdapter<GoodsItem, SeasoningAdapter.VH>(list) {
//
// override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
// val binding = ListItemCellBinding.inflate(LayoutInflater.from(context), parent, false)
// return VH(binding)
// }
//
// override fun onBindViewHolder(holder: VH, position: Int, item: GoodsItem?) {
// item ?: return
// holder.binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
// width = 226.dp
// height = 120.dp
// leftMargin = 7.dp
// rightMargin = 8.dp
// topMargin = 7.dp
// bottomMargin = 8.dp
// }
// holder.binding.tvCell.text = TextCellAdapter.getTextSpan(item.toSeasoningEntity())
// }
//
// inner class VH(val binding: ListItemCellBinding) : QuickViewHolder(binding.root)
//}
@@ -6,7 +6,7 @@
//import androidx.recyclerview.widget.RecyclerView
//import com.shuwei.dish.match.base.BaseActivity
//import com.shuwei.dish.match.dialog.BottomDialog2
//import com.shuwei.dish.match.entity.SeasoningEntity
//import com.shuwei.dish.match.db.entity.SeasoningEntity
//import com.shuwei.dish.match.ui.DeviceSettingActivity
//import com.shuwei.dish.match.ui.SubmitDishActivity
//import com.shuwei.dish.match.utils.DataUtil
@@ -8,15 +8,15 @@ 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.ListItemSearchGoodsInfoBinding
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.model.GoodsItem
class SeasoningSearchAdapter(private var list: MutableList<SeasoningEntity>) :
BaseQuickAdapter<SeasoningEntity, SeasoningSearchAdapter.VH>(list) {
class SeasoningSearchAdapter(private var list: MutableList<GoodsItem>) :
BaseQuickAdapter<GoodsItem, SeasoningSearchAdapter.VH>(list) {
override fun onBindViewHolder(
holder: VH,
position: Int,
item: SeasoningEntity?
item: GoodsItem?
) {
holder.binding.tvGoodsInfo.run {
text = item?.goodsName
@@ -1,136 +1,137 @@
package com.shuwei.dish.match.adapter
import android.graphics.Typeface
import android.icu.text.DecimalFormat
import android.text.SpannableStringBuilder
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.text.style.LineHeightSpan
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import androidx.recyclerview.widget.RecyclerView
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.R
import com.shuwei.dish.match.databinding.ListItemCellBinding
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.invisible
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
import com.shuwei.dish.match.utils.ext.visible
class TextCellAdapter(var list: MutableList<SeasoningEntity>) :
RecyclerView.Adapter<TextCellAdapter.VH>() {
inner class VH(var binding: ListItemCellBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(parent.context)
val binding = ListItemCellBinding.inflate(inflater, parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int) {
val item = list[position]
holder.binding.run {
if (position == 10 || position == 11 || position == 13 || position == 14) {
root.invisible()
} else {
root.visible()
}
loadCell(tvCell, item)
//root.setBackgroundResource(R.color.dish_green)
(root.layoutParams as RecyclerView.LayoutParams).let {
// it.bottomMargin = 15.dp
it.width = 226.dp
it.height = 120.dp
// it.rightMargin = 0
// it.leftMargin = if (position % 3 == 0) 0 else 15.dp
it.leftMargin = 7.dp
it.rightMargin = 8.dp
it.topMargin = 7.dp
it.bottomMargin = 8.dp
}
if (item.pageType == 1) {
root.clickWithDebounce { onCellClick?.invoke(position) }
}
}
}
override fun getItemCount() = list.size
var onCellClick: ((positon: Int) -> Unit)? = null
companion object {
fun loadLayout(frameLayout: FrameLayout, item: SeasoningEntity) {
val child: TextView = frameLayout.getChildAt(0) as TextView
child.text = getTextSpan(item)
}
fun loadCell(tvCell: TextView, item: SeasoningEntity) {
tvCell.text = getTextSpan(item)
}
// fun loadCell2(tvCell: TextView, item: SeasoningEntity) {
// if (item.goodsName.isNullOrBlank()) {
// tvCell.run {
// text = buildSpannableString {
//package com.shuwei.dish.match.adapter
//
//import android.graphics.Typeface
//import android.icu.text.DecimalFormat
//import android.text.SpannableStringBuilder
//import android.text.style.AbsoluteSizeSpan
//import android.text.style.ForegroundColorSpan
//import android.text.style.LineHeightSpan
//import android.text.style.StyleSpan
//import android.view.LayoutInflater
//import android.view.ViewGroup
//import android.widget.FrameLayout
//import android.widget.TextView
//import androidx.core.content.ContextCompat
//import androidx.core.graphics.toColorInt
//import androidx.recyclerview.widget.RecyclerView
//import com.chad.library.adapter4.viewholder.QuickViewHolder
//import com.shuwei.dish.match.R
//import com.shuwei.dish.match.databinding.ListItemCellBinding
//import com.shuwei.dish.match.db.entity.SeasoningEntity
//import com.shuwei.dish.match.model.GoodsItem
//import com.shuwei.dish.match.utils.ext.appendText
//import com.shuwei.dish.match.utils.ext.buildSpannableString
//import com.shuwei.dish.match.utils.ext.clickWithDebounce
//import com.shuwei.dish.match.utils.ext.dp
//import com.shuwei.dish.match.utils.ext.invisible
//import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
//import com.shuwei.dish.match.utils.ext.visible
//
//class TextCellAdapter(var list: MutableList<SeasoningEntity>) :
// RecyclerView.Adapter<TextCellAdapter.VH>() {
//
// inner class VH(var binding: ListItemCellBinding) : QuickViewHolder(binding.root)
//
// override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
// val inflater = LayoutInflater.from(parent.context)
// val binding = ListItemCellBinding.inflate(inflater, parent, false)
// return VH(binding)
// }
//
// override fun onBindViewHolder(holder: VH, position: Int) {
// val item = list[position]
// holder.binding.run {
// if (position == 10 || position == 11 || position == 13 || position == 14) {
// root.invisible()
// } else {
// root.visible()
// }
// loadCell(tvCell, item)
// //root.setBackgroundResource(R.color.dish_green)
// (root.layoutParams as RecyclerView.LayoutParams).let {
//// it.bottomMargin = 15.dp
// it.width = 226.dp
// it.height = 120.dp
//// it.rightMargin = 0
//// it.leftMargin = if (position % 3 == 0) 0 else 15.dp
//
// it.leftMargin = 7.dp
// it.rightMargin = 8.dp
// it.topMargin = 7.dp
// it.bottomMargin = 8.dp
// }
// if (item.pageType == 1) {
// root.clickWithDebounce { onCellClick?.invoke(position) }
// }
// }
// }
//
// override fun getItemCount() = list.size
//
// var onCellClick: ((positon: Int) -> Unit)? = null
//
// companion object {
// fun loadLayout(frameLayout: FrameLayout, item: SeasoningEntity) {
// val child: TextView = frameLayout.getChildAt(0) as TextView
// child.text = getTextSpan(item)
// }
// fun loadCell(tvCell: TextView, item: SeasoningEntity) {
// tvCell.text = getTextSpan(item)
// }
//// fun loadCell2(tvCell: TextView, item: SeasoningEntity) {
//// if (item.goodsName.isNullOrBlank()) {
//// tvCell.run {
//// text = buildSpannableString {
//// appendText(
//// "未配置",
//// ForegroundColorSpan("#999999".toColorInt()),
//// StyleSpan(Typeface.BOLD),
//// AbsoluteSizeSpan(28, true),
//// LineHeightSpan { text, start, end, spanstartv, v, fm ->
//// fm.descent += 10.dp // 增加行间距
//// }
//// )}
//// //setTextColor(ContextCompat.getColor(context, R.color.black999))
//// }
//// } else {
////// if (item.pageType == 1) {
////// tvCell.run {
////// text = item.goodsName
////// setTextColor(ContextCompat.getColor(context, R.color.black))
////// }
////// } else {
//// tvCell.text = getTextSpan(item)
////// }
//// }
//// }
//
// fun getTextSpan(item: SeasoningEntity): SpannableStringBuilder {
// val endIndex = if (item.sort == 9) 10 else 6
// var name = if (item.goodsName.isNullOrBlank()) "未配置" else item.goodsName!!
// if (name.length > endIndex) {
// name = "${name.substring(0, endIndex)}…"
// }
// return buildSpannableString {
// appendText(
// "未配置",
// ForegroundColorSpan("#999999".toColorInt()),
// name,
// ForegroundColorSpan(if (item.goodsName.isNullOrBlank()) "#999999".toColorInt() else "#000000".toColorInt()),
// StyleSpan(Typeface.BOLD),
// AbsoluteSizeSpan(28, true),
// LineHeightSpan { text, start, end, spanstartv, v, fm ->
// fm.descent += 10.dp // 增加行间距
// }
// )}
// //setTextColor(ContextCompat.getColor(context, R.color.black999))
// }
// } else {
//// if (item.pageType == 1) {
//// tvCell.run {
//// text = item.goodsName
//// setTextColor(ContextCompat.getColor(context, R.color.black))
//// }
//// } else {
// tvCell.text = getTextSpan(item)
//// }
// )
// append("\n")
// val weight = item.useWeight ?: 0.toDouble()
// val weightColor = if (weight == 0.toDouble()) "#999999" else "#00BC71"
// appendText(
//// "${DecimalFormat("#").format(weight)}g",
// "${weight.roundedOneDecimalPlace()}g",
// ForegroundColorSpan(weightColor.toColorInt()),
// AbsoluteSizeSpan(30, true)
// )
// }
// }
fun getTextSpan(item: SeasoningEntity): SpannableStringBuilder {
val endIndex = if (item.sort == 9) 10 else 6
var name = if (item.goodsName.isNullOrBlank()) "未配置" else item.goodsName!!
if (name.length > endIndex) {
name = "${name.substring(0, endIndex)}"
}
return buildSpannableString {
appendText(
name,
ForegroundColorSpan(if (item.goodsName.isNullOrBlank()) "#999999".toColorInt() else "#000000".toColorInt()),
StyleSpan(Typeface.BOLD),
AbsoluteSizeSpan(28, true),
LineHeightSpan { text, start, end, spanstartv, v, fm ->
fm.descent += 10.dp // 增加行间距
}
)
append("\n")
val weight = item.useWeight ?: 0.toDouble()
val weightColor = if (weight == 0.toDouble()) "#999999" else "#00BC71"
appendText(
// "${DecimalFormat("#").format(weight)}g",
"${weight.roundedOneDecimalPlace()}g",
ForegroundColorSpan(weightColor.toColorInt()),
AbsoluteSizeSpan(30, true)
)
}
}
}
}
// }
//
//}
@@ -12,13 +12,15 @@ import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.shuwei.dish.match.R
import com.shuwei.dish.match.databinding.ActivityBaseBinding
import com.shuwei.dish.match.db.DbViewModel
import com.shuwei.dish.match.dialog.Loading
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.net.NetViewModel
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.ui.HomeActivity.Companion.TAG
import com.shuwei.dish.match.ui.InitActivity
import com.shuwei.dish.match.utils.ActivityManager
@@ -35,6 +37,10 @@ import java.util.Locale
@Suppress("DEPRECATION")
open class BaseActivity : AppCompatActivity() {
val netViewModel: NetViewModel by viewModels()
val dbViewModel: DbViewModel by viewModels()
val appViewModel: AppViewModel by viewModels()
private lateinit var binding: ActivityBaseBinding
private var launcher: ActivityResultLauncher<Intent>? = null
private var launchCallback: ((Intent?) -> Unit)? = null
@@ -29,7 +29,6 @@ class BaseApp : Application() {
var configUrl = ""
var token: String? = null
var deviceId: String? = null
var appVersion: String = "1"
@Volatile
private var sharedPref: SharedPreferences? = null
@@ -0,0 +1,43 @@
package com.shuwei.dish.match.base
/**
* 设备角色枚举
* MASTER:主设备,负责汇聚所有设备的秤数据并展示大屏界面
* SLAVE:子设备,负责推送本机秤数据给主设备
*/
enum class DeviceRole { MASTER, SLAVE }
object GlobalData {
var appBaseUrl: String = TEST_BASE_URL
/**
* 具体业务 BaseUrl
*/
const val TEST_BASE_URL = "http://192.168.1.201:14801"
const val UAT_BASE_URL = "https://dev.yixiong-tech.com:8083"
const val PROD_BASE_URL = "https://api.dm.yixiong-tech.com:8443"
//临时用于测试
const val DEVICE_ID_2 = "8fc2ab34-2137-3112-acca-f884ea8736d4"
/**
* 设备id
*/
var deviceId: String = ""
/**
* 当前设备角色,启动时从 SpTool 读取,默认为 SLAVE
*/
var deviceRole: DeviceRole = DeviceRole.SLAVE
}
/**
*
*/
object GlobalKey {
const val KEY_EQUIPMENT_INFO = "equipmentInfo"
const val KEY_TOKEN = "tokenKey"
const val KEY_USER_INFO = "userInfoKey"
const val KEY_USER_NAME = "userNameKey"
const val KEY_BASE_URL = "baseUrl"
}
@@ -6,10 +6,14 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.shuwei.dish.match.db.dao.AppDao
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.db.dao.CookFoodDao
import com.shuwei.dish.match.db.dao.CookFoodGoodsDao
import com.shuwei.dish.match.db.dao.SeasoningDao
import com.shuwei.dish.match.db.dao.SeasoningSlotDao
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.db.entity.SeasoningEntity
import com.shuwei.dish.match.db.entity.SeasoningSlotEntity
// 步骤1:更新版本号
@@ -19,16 +23,20 @@ import com.shuwei.dish.match.entity.SeasoningEntity
SeasoningEntity::class,
CookFoodEntity::class,
CookFoodGoodsEntity::class,
SeasoningSlotEntity::class,
],
version = 7,
version = 13,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
// abstract fun seasoningDao(): SeasoningDao
// abstract fun cookFoodDao(): CookFoodDao
// abstract fun cookFoodGoodsDao(): CookFoodGoodsDao
abstract fun appDao(): AppDao
abstract fun cookFoodDao(): CookFoodDao
abstract fun cookFoodGoodsDao(): CookFoodGoodsDao
abstract fun seasoningDao(): SeasoningDao
abstract fun seasoningSlotDao(): SeasoningSlotDao
}
@@ -140,6 +148,20 @@ val MIGRATION_5_6 = MigrationImpl(5, 6) { db ->
db.endTransaction()
}
val MIGRATION_7_8 = MigrationImpl(7, 8) { db ->
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS `dm_seasoning_slot` (
`deviceId` TEXT NOT NULL,
`address` INTEGER NOT NULL,
`goodsId` TEXT NOT NULL,
`goodsName` TEXT NOT NULL,
PRIMARY KEY(`deviceId`, `address`)
)
""".trimIndent()
)
}
val MIGRATION_6_7 = MigrationImpl(6, 7) {db ->
runCatching {
@@ -192,6 +214,48 @@ val MIGRATION_6_7 = MigrationImpl(6, 7) {db ->
}
}
/**
* 版本8→9:为 dm_cook_food_goods 和 dm_seasoning 表新增 zjmCode、materId 字段
*/
val MIGRATION_8_9 = MigrationImpl(8, 9) { db ->
db.execSQL("ALTER TABLE dm_cook_food_goods ADD COLUMN zjmCode TEXT DEFAULT ''")
db.execSQL("ALTER TABLE dm_cook_food_goods ADD COLUMN materId TEXT DEFAULT ''")
db.execSQL("ALTER TABLE dm_seasoning ADD COLUMN zjmCode TEXT DEFAULT ''")
db.execSQL("ALTER TABLE dm_seasoning ADD COLUMN materId TEXT DEFAULT ''")
}
/**
* 版本9→10:删除 dm_cook_food 表 foodId 唯一索引
* 改为由应用层保证同一 foodId 且 isDel=0 只有一条记录,以支持逻辑删除
*/
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")
}
/**
* 版本12→13:为 dm_cook_food 表新增 submitState(接口提交状态)字段
* 0-未提交(本地草稿/手动删除),1-接口提交成功后删除
*/
val MIGRATION_12_13 = MigrationImpl(12, 13) { db ->
db.execSQL("ALTER TABLE dm_cook_food ADD COLUMN submitState INTEGER NOT NULL DEFAULT 0")
}
class DatabaseProvider(private val context: Context) {
// 单例模式确保全局唯一实例‌
val instance: AppDatabase by lazy {
@@ -210,6 +274,12 @@ class DatabaseProvider(private val context: Context) {
// .addMigrations(MIGRATION_4_5)
// .addMigrations(MIGRATION_5_6)
// .addMigrations(MIGRATION_6_7)
.addMigrations(MIGRATION_7_8)
.addMigrations(MIGRATION_8_9)
.addMigrations(MIGRATION_9_10)
.addMigrations(MIGRATION_10_11)
.addMigrations(MIGRATION_11_12)
.addMigrations(MIGRATION_12_13)
.build()
}
}
@@ -1,142 +0,0 @@
package com.shuwei.dish.match.db
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.db.dao.AppDao
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AppRepository(val appDao: AppDao) {
suspend fun insertCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) {
appDao.insertCookFood(item)
}
suspend fun updateCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) {
appDao.updateCookFood(item)
}
suspend fun getCookFoodById(foodId: String, cookMode: Int) = withContext(Dispatchers.IO) {
appDao.getCookFoodById(BaseApp.canteenId, foodId, cookMode)
}
suspend fun getCookFoodList(cookMode: Int, dinnerType: String) = withContext(Dispatchers.IO) {
appDao.getCookFoodList(BaseApp.canteenId, cookMode, dinnerType)
}
suspend fun countCookFood(cookMode: Int) = withContext(Dispatchers.IO) {
appDao.countCookFood(BaseApp.canteenId, cookMode)
}
suspend fun deleteCookFood(foodId: String, cookMode: Int) = withContext(Dispatchers.IO) {
appDao.deleteCookFood(BaseApp.canteenId, foodId, cookMode)
}
suspend fun deleteCookFoodGoodsList(pid:Long, foodId: String) = withContext(Dispatchers.IO) {
appDao.deleteCookFoodGoodsList(pid, foodId)
}
//--------------------------------------------------------------------------------------------
// suspend fun insertGoods(item: CookFoodGoodsEntity) = withContext(Dispatchers.IO) {
// appDao.insertGoods(item)
// }
suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>) =
withContext(Dispatchers.IO) {
appDao.insertGoodsList(items)
}
// suspend fun updateGoods(item: CookFoodGoodsEntity) = withContext(Dispatchers.IO) {
// appDao.updateGoods(item)
// }
suspend fun getCookFoodGoodsList(pid:Long, foodId: String) = withContext(Dispatchers.IO) {
appDao.getCookFoodGoodsList(pid, foodId)
}
suspend fun getCookFoodGoodsListByType(
foodId: String,
materialType: Int
) = withContext(Dispatchers.IO) {
appDao.getCookFoodGoodsListByType(foodId, materialType)
}
//--------------------------------------------------------------------------------------------
suspend fun getSeasoningById(id: Long) = withContext(Dispatchers.IO) {
appDao.getSeasoningById(id)
}
suspend fun getSeasoningBySort(sort:Int) = withContext(Dispatchers.IO) {
appDao.getSeasoningBySort(sort)
}
suspend fun getSeasoningByGoodsId(goodsId: Int) = withContext(Dispatchers.IO) {
appDao.getSeasoningByGoodsId(goodsId)
}
suspend fun getAllStream() = withContext(Dispatchers.IO) {
appDao.getAllStream()
}
suspend fun search(query: String) = withContext(Dispatchers.IO) {
appDao.search(query)
}
suspend fun clearAllSeasoning() = withContext(Dispatchers.IO) {
appDao.clearAllSeasoning()
}
suspend fun deleteSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) {
appDao.deleteSeasoningBySort(sort)
}
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>) =
withContext(Dispatchers.IO) {
appDao.insertSeasoningList(items)
}
suspend fun updateSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
appDao.updateSeasoning(item)
}
suspend fun insertSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
appDao.insertSeasoning(item)
}
}
//
//class SeasoningRepository(val seasoningDao: SeasoningDao) {
// val seasoningList: Flow<List<SeasoningEntity>> = seasoningDao.getAllStream()
//
// fun addSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
// seasoningDao.insert(item)
// }
//
// fun searchSeasoning(query: String) = withContext(Dispatchers.IO) {
// seasoningDao.search(query)
// }
//}
//
//class CookFoodRepository(val cookFoodDao: CookFoodDao) {
// fun getCookFood(foodId: String, cookMode:Int = 0) = withContext(Dispatchers.IO) {
// cookFoodDao.getCookFoodById(foodId, cookMode)
// }
//
// fun getCookFoodByList(cookMode:Int = 0) = withContext(Dispatchers.IO) {
// cookFoodDao.getCookFoodList(cookMode)
// }
//
// fun countCookFood(cookMode:Int = 0)= withContext(Dispatchers.IO) {
// cookFoodDao.countCookFood(cookMode)
// }
//}
//
//class CookFoodGoodsRepository(val cookFoodGoodsDao: CookFoodGoodsDao) {
// fun getFoodGoodsList(foodId: String) = withContext(Dispatchers.IO) {
// cookFoodGoodsDao.getCookFoodGoodsList(foodId)
// }
//
//}
@@ -0,0 +1,315 @@
package com.shuwei.dish.match.db
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.db.entity.SeasoningEntity
import com.shuwei.dish.match.db.entity.SeasoningSlotEntity
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DbRepository {
private val db = BaseApp.instance!!.database
suspend fun insertCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) {
db.cookFoodDao().insertCookFood(item)
}
suspend fun updateCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) {
db.cookFoodDao().updateCookFood(item)
}
suspend fun getCookFoodById(foodId: String, cookMode: Int, dinnerType: String) = withContext(Dispatchers.IO) {
db.cookFoodDao().getCookFoodById(BaseApp.canteenId, foodId, cookMode, dinnerType)
}
suspend fun getCookFoodList(cookMode: Int, dinnerType: String) = withContext(Dispatchers.IO) {
db.cookFoodDao().getCookFoodList(BaseApp.canteenId, cookMode, dinnerType)
}
suspend fun countCookFood(cookMode: Int) = withContext(Dispatchers.IO) {
db.cookFoodDao().countCookFood(BaseApp.canteenId, cookMode)
}
suspend fun deleteCookFood(foodId: String, cookMode: Int, dinnerType: String) = withContext(Dispatchers.IO) {
db.cookFoodDao().deleteCookFood(BaseApp.canteenId, foodId, cookMode, dinnerType)
}
/** 接口提交成功后逻辑删除菜品,同时把 submitState 置 1 */
suspend fun markSubmittedAndDelete(foodId: String, cookMode: Int, dinnerType: String) = withContext(Dispatchers.IO) {
db.cookFoodDao().markSubmittedAndDelete(BaseApp.canteenId, foodId, cookMode, dinnerType)
}
suspend fun deleteCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().deleteCookFoodGoodsList(pid, foodId)
}
/** 仅逻辑删除调料行(materialType = 3),主辅材不动 */
suspend fun deleteSeasoningOnly(pid: Long, foodId: String) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().deleteSeasoningOnly(pid, foodId)
}
//--------------------------------------------------------------------------------------------
suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>) =
withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().insertGoodsList(items)
}
suspend fun getCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().getCookFoodGoodsList(pid, foodId)
}
suspend fun getCookFoodGoodsListByType(
foodId: String,
materialType: Int
) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().getCookFoodGoodsListByType(foodId, materialType)
}
//--------------------------------------------------------------------------------------------
suspend fun getSeasoningById(id: Long) = withContext(Dispatchers.IO) {
db.seasoningDao().getSeasoningById(id)
}
suspend fun getSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) {
db.seasoningDao().getSeasoningBySort(sort)
}
suspend fun getSeasoningByGoodsId(goodsId: String) = withContext(Dispatchers.IO) {
db.seasoningDao().getSeasoningByGoodsId(goodsId)
}
suspend fun getAllStream() = withContext(Dispatchers.IO) {
db.seasoningDao().getAllStream()
}
suspend fun search(query: String) = withContext(Dispatchers.IO) {
db.seasoningDao().search(query)
}
suspend fun clearAllSeasoning() = withContext(Dispatchers.IO) {
db.seasoningDao().clearAllSeasoning()
}
suspend fun deleteSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) {
db.seasoningDao().deleteSeasoningBySort(sort)
}
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>) =
withContext(Dispatchers.IO) {
db.seasoningDao().insertSeasoningList(items)
}
suspend fun updateSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
db.seasoningDao().updateSeasoning(item)
}
suspend fun insertSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
db.seasoningDao().insertSeasoning(item)
}
/**
* 按 goodsId 查重后 upsert:存在则更新,不存在则插入
* 确保同一个 goodsId 在 dm_seasoning 中只有一条记录
*/
suspend fun upsertSeasoningByGoodsId(item: SeasoningEntity) = withContext(Dispatchers.IO) {
val existing = db.seasoningDao().getSeasoningByGoodsId(item.goodsId)
if (existing != null) {
db.seasoningDao().updateSeasoning(item.also { it.id = existing.id })
} else {
item.id = 0
db.seasoningDao().insertSeasoning(item)
}
}
/**
* 查询指定设备是否已配置调料槽位
* @param deviceId 目标设备 ID
* @return true 表示已有配置,false 表示未配置
*/
suspend fun hasSeasoningSlotConfig(deviceId: String) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().queryByDeviceId(deviceId).isNotEmpty()
}
/**
* 查询所有设备的调料槽位配置
*/
suspend fun getAllSeasoningSlots() = withContext(Dispatchers.IO) {
db.seasoningSlotDao().queryAll()
}
/**
* 查询指定设备的调料槽位配置
* @param deviceId 目标设备 ID
*/
suspend fun getSeasoningSlotsByDeviceId(deviceId: String) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().queryByDeviceId(deviceId)
}
/**
* 插入或更新单个调料槽位配置
*/
suspend fun upsertSeasoningSlot(slot: SeasoningSlotEntity) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().upsert(slot)
}
/**
* 批量插入或更新调料槽位配置
*/
suspend fun upsertAllSeasoningSlots(slots: List<SeasoningSlotEntity>) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().upsertAll(slots)
}
/**
* 删除指定设备的指定槽位配置
* @param deviceId 目标设备 ID
* @param address 秤硬件地址
*/
suspend fun deleteSeasoningSlot(deviceId: String, address: Int) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().deleteByDeviceIdAndAddress(deviceId, address)
}
/**
* 删除指定设备的全部槽位配置
* @param deviceId 目标设备 ID
*/
suspend fun deleteAllSlotsByDeviceId(deviceId: String) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().deleteAllByDeviceId(deviceId)
}
// ---- 调试用:查询各表全量数据(含已删除记录)----
/** 查询 dm_cook_food 全部记录 */
suspend fun getAllCookFood() = withContext(Dispatchers.IO) {
db.cookFoodDao().getAllCookFood()
}
/** 查询 dm_cook_food_goods 全部记录 */
suspend fun getAllCookFoodGoods() = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().getAllCookFoodGoods()
}
/** 查询 dm_seasoning 全部记录 */
suspend fun getAllSeasoning() = withContext(Dispatchers.IO) {
db.seasoningDao().getAllSeasoning()
}
// ---- 调试用:分页查询(含 isDel 过滤)----
suspend fun getCookFoodPaged(showDel: Boolean, limit: Int, offset: Int) =
withContext(Dispatchers.IO) {
db.cookFoodDao().getCookFoodPaged(if (showDel) 1 else 0, limit, offset)
}
suspend fun countCookFoodAll(showDel: Boolean) = withContext(Dispatchers.IO) {
db.cookFoodDao().countCookFoodAll(if (showDel) 1 else 0)
}
suspend fun getCookFoodGoodsPaged(showDel: Boolean, limit: Int, offset: Int) =
withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().getCookFoodGoodsPaged(if (showDel) 1 else 0, limit, offset)
}
suspend fun countCookFoodGoodsAll(showDel: Boolean) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().countCookFoodGoodsAll(if (showDel) 1 else 0)
}
suspend fun getSeasoningPaged(showDel: Boolean, limit: Int, offset: Int) =
withContext(Dispatchers.IO) {
db.seasoningDao().getSeasoningPaged(if (showDel) 1 else 0, limit, offset)
}
suspend fun countSeasoningAll(showDel: Boolean) = withContext(Dispatchers.IO) {
db.seasoningDao().countSeasoningAll(if (showDel) 1 else 0)
}
suspend fun getSlotPaged(limit: Int, offset: Int) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().getSlotPaged(limit, offset)
}
suspend fun countSlotAll() = withContext(Dispatchers.IO) {
db.seasoningSlotDao().countSlotAll()
}
// ---- 调试用:动态条件过滤分页查询 ----
suspend fun getCookFoodPagedFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.cookFoodDao().queryPagedFiltered(query)
}
suspend fun countCookFoodFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.cookFoodDao().countFiltered(query)
}
suspend fun getCookFoodGoodsPagedFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().queryPagedFiltered(query)
}
suspend fun countCookFoodGoodsFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().countFiltered(query)
}
suspend fun getSeasoningPagedFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.seasoningDao().queryPagedFiltered(query)
}
suspend fun countSeasoningFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.seasoningDao().countFiltered(query)
}
suspend fun getSlotPagedFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().queryPagedFiltered(query)
}
suspend fun countSlotFiltered(query: SupportSQLiteQuery) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().countFiltered(query)
}
/**
* 物理清空全部 4 张表的测试数据
* 清除顺序:先删子表(食材),再删主表(菜品),最后删配置表(调料、槽位)
*/
suspend fun clearAllData() = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().deleteAll()
db.cookFoodDao().deleteAll()
db.seasoningDao().deleteAll()
db.seasoningSlotDao().deleteAll()
}
}
//
//class SeasoningRepository(val seasoningDao: SeasoningDao) {
// val seasoningList: Flow<List<SeasoningEntity>> = seasoningDao.getAllStream()
//
// fun addSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
// seasoningDao.insert(item)
// }
//
// fun searchSeasoning(query: String) = withContext(Dispatchers.IO) {
// seasoningDao.search(query)
// }
//}
//
//class CookFoodRepository(val cookFoodDao: CookFoodDao) {
// fun getCookFood(foodId: String, cookMode:Int = 0) = withContext(Dispatchers.IO) {
// cookFoodDao.getCookFoodById(foodId, cookMode)
// }
//
// fun getCookFoodByList(cookMode:Int = 0) = withContext(Dispatchers.IO) {
// cookFoodDao.getCookFoodList(cookMode)
// }
//
// fun countCookFood(cookMode:Int = 0)= withContext(Dispatchers.IO) {
// cookFoodDao.countCookFood(cookMode)
// }
//}
//
//class CookFoodGoodsRepository(val cookFoodGoodsDao: CookFoodGoodsDao) {
// fun getFoodGoodsList(foodId: String) = withContext(Dispatchers.IO) {
// cookFoodGoodsDao.getCookFoodGoodsList(foodId)
// }
//
//}
@@ -0,0 +1,623 @@
package com.shuwei.dish.match.db
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.sqlite.db.SimpleSQLiteQuery
import com.shuwei.dish.match.model.CollectedFoodItem
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.db.entity.SeasoningEntity
import com.shuwei.dish.match.db.entity.SeasoningSlotEntity
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class DbViewModel : ViewModel() {
private val rep = DbRepository()
fun updateCookFood(entity: CookFoodEntity) {
viewModelScope.launch {
rep.updateCookFood(entity)
}
}
private val _cookFoodDetailState = MutableStateFlow<CookFoodEntity?>(null)
val cookFoodDetailState: StateFlow<CookFoodEntity?> = _cookFoodDetailState.asStateFlow()
fun getCookFoodById(foodId: String, cookMode: Int, dinnerType: String) {
viewModelScope.launch {
_cookFoodDetailState.value = rep.getCookFoodById(foodId, cookMode, dinnerType)
}
}
private val _cookFoodListState = MutableStateFlow<MutableList<CookFoodEntity>?>(null)
val cookFoodListState: StateFlow<MutableList<CookFoodEntity>?> = _cookFoodListState.asStateFlow()
fun resetCookFoodList() {
_cookFoodListState.value = null
}
fun getCookFoodList(
cookMode: Int,
dinnerType: String = "0"
) {
viewModelScope.launch {
_cookFoodListState.value = rep.getCookFoodList(cookMode, dinnerType)
}
}
/**
* 查询本地烹饪中数据(回调版本),结果通过 onResult 直接回调,不经过 StateFlow
* 适合不需要跨生命周期持续监听的一次性查询场景
*/
fun getCookFoodListWithCallback(
cookMode: Int,
dinnerType: String = "0",
onResult: (MutableList<CookFoodEntity>?) -> Unit
) {
viewModelScope.launch {
onResult(rep.getCookFoodList(cookMode, dinnerType))
}
}
/**
* 直接返回本地烹饪中数据,供需要串行等待结果的场景使用(如先查库再发网络请求)
*/
suspend fun getCookFoodListDirect(
cookMode: Int,
dinnerType: String = "0"
): MutableList<CookFoodEntity>? {
return rep.getCookFoodList(cookMode, dinnerType)
}
suspend fun countCookFood(cookMode: Int): Int {
return rep.countCookFood(cookMode)
}
// fun saveCookFood(cookMode: Int, entity: CookFoodEntity) {
// viewModelScope.launch {
// val data = rep.getCookFoodById(entity.foodId, cookMode)
// if (data == null) {
// rep.insertCookFood(entity)
// return@launch
// }
// rep.updateCookFood(entity)
// }
// }
suspend fun saveCookFoodAndGoods(
cookMode: Int,
entity: CookFoodEntity,
list: MutableList<CookFoodGoodsEntity>?
) {
val data = rep.getCookFoodById(entity.foodId, cookMode, entity.dinnerType ?: "0")
if (data == null) {
// 首次保存:主表 + 主辅材 + 调料 全量 insert
rep.insertCookFood(entity)
val newData = rep.getCookFoodById(entity.foodId, cookMode, entity.dinnerType ?: "0")
// 回填 id,防止下次调用时 getCookFoodById 查不到而重复插入主辅材
newData?.let { entity.id = it.id }
list?.let {
it.forEach { goods ->
goods.id = 0
goods.pid = newData?.id ?: 0
}
rep.insertGoodsList(it)
}
} else {
// 已存在:主表与主辅材保持不变,仅重建调料部分,避免主表/主辅材累积逻辑删除脏数据
rep.deleteSeasoningOnly(data.id, entity.foodId)
val seasoningList = list?.filter { it.materialType == 3 }?.toMutableList()
if (!seasoningList.isNullOrEmpty()) {
seasoningList.forEach { goods ->
goods.id = 0
goods.pid = data.id
goods.foodId = entity.foodId
}
rep.insertGoodsList(seasoningList)
}
}
}
suspend fun deleteCookFoodAndGoods(cookMode: Int, foodId: String, dinnerType: String) {
val data = rep.getCookFoodById(foodId, cookMode, dinnerType)
if (data != null) {
rep.deleteCookFood(foodId = foodId, cookMode = cookMode, dinnerType = dinnerType)
rep.deleteCookFoodGoodsList(pid = data.id, foodId = foodId)
}
}
/**
* 接口提交成功后删除本地菜品草稿(异步版本,绑定 viewModelScope
* 与 [markSubmittedAndDelete] 的区别:不是 suspend,内部用 viewModelScope 启动协程,
* 不受调用方 Activity 生命周期影响,适合 singleTask 跳转前触发删除的场景
*/
fun markSubmittedAndDeleteAsync(cookMode: Int, foodId: String, dinnerType: String) {
viewModelScope.launch {
markSubmittedAndDelete(cookMode, foodId, dinnerType)
}
}
/**
* 接口提交成功后删除本地菜品草稿
* 与 [deleteCookFoodAndGoods] 的区别:主表 submitState 会被置 1,便于审计区分手动删除与接口删除
*/
suspend fun markSubmittedAndDelete(cookMode: Int, foodId: String, dinnerType: String) {
val data = rep.getCookFoodById(foodId, cookMode, dinnerType)
if (data != null) {
rep.markSubmittedAndDelete(foodId = foodId, cookMode = cookMode, dinnerType = dinnerType)
rep.deleteCookFoodGoodsList(pid = data.id, foodId = foodId)
}
}
//--------------------------------------------------------------------------------------------------------
suspend fun getCookFoodGoodsList(
foodId: String,
cookMode: Int,
dinnerType: String
): MutableList<CookFoodGoodsEntity> {
val cookFood = rep.getCookFoodById(foodId, cookMode, dinnerType)
return if (cookFood != null) rep.getCookFoodGoodsList(pid = cookFood.id, foodId = cookFood.foodId)
else mutableListOf()
}
// fun updateGoods(entity: CookFoodGoodsEntity) {
// viewModelScope.launch {
// rep.updateGoods(entity)
// }
// }
// fun saveCookFoodGoods(list: MutableList<CookFoodGoodsEntity>) {
// viewModelScope.launch {
// val foodId = list[0].foodId
// val data: MutableList<CookFoodGoodsEntity>? =
// rep.getCookFoodGoodsList(foodId!!)
// if (data.isNullOrEmpty()) {
// rep.insertGoodsList(list)
// return@launch
// }
// val localDbSeasoningList = rep.getCookFoodGoodsListByType(foodId, 3)
// localDbSeasoningList.forEach {
// it.isDel = 1
// //更新本地调料数据为已删除的状态
// rep.updateGoods(it)
// }
// //筛选出调料数据保存,主辅材不会变更不需要再次处理
// val saveSeasoningList = list.filter { it.materialType == 3 }
// rep.insertGoodsList(saveSeasoningList.toMutableList())
// }
// }
//--------------------------------------------------------------------------------------------------------
// fun addSeasoning(list: MutableList<SeasoningEntity>, callback: () -> Unit) {
// viewModelScope.launch {
// rep.deleteBatch()
// rep.insertSeasoningList(items = list)
// callback()
// }
// }
private val loadSeasoningMutex = Mutex()
/**
* 本地调料列表状态流
*/
private val _seasoningState = MutableStateFlow<MutableList<SeasoningEntity>>(mutableListOf())
val seasoningState: StateFlow<MutableList<SeasoningEntity>> = _seasoningState.asStateFlow()
fun loadSeasoning() {
if (loadSeasoningMutex.isLocked) return
viewModelScope.launch {
loadSeasoningMutex.withLock {
_seasoningState.value = rep.getAllStream()
}
}
}
/**
* 所有设备调料槽位配置的 UI 状态流
*/
private val _seasoningSlotState = MutableStateFlow<List<SeasoningSlotEntity>>(emptyList())
val seasoningSlotState: StateFlow<List<SeasoningSlotEntity>> = _seasoningSlotState.asStateFlow()
/**
* 查询所有调料槽位配置,更新 seasoningSlotState 并返回结果
* 声明为 suspend,调用方可在同一协程中直接使用返回值,无需等待 StateFlow 更新
*/
suspend fun loadSeasoningSlot(): List<SeasoningSlotEntity> {
val slots = rep.getAllSeasoningSlots()
_seasoningSlotState.value = slots
return slots
}
fun deleteSeasoningBySort(sort: Int) {
viewModelScope.launch {
rep.deleteSeasoningBySort(sort)
}
}
private val _clearAllSeasoningState = MutableStateFlow(false)
val clearAllSeasoningState: StateFlow<Boolean> = _clearAllSeasoningState.asStateFlow()
fun clearAllSeasoning() {
viewModelScope.launch {
rep.clearAllSeasoning()
_clearAllSeasoningState.value = true
}
}
private val _hasSeasoningSlotConfigState = MutableStateFlow(false)
val hasSeasoningSlotConfigState: StateFlow<Boolean> = _hasSeasoningSlotConfigState.asStateFlow()
/**
* 查询指定设备是否已配置调料槽位
* @param deviceId 目标设备 ID
*/
fun hasSeasoningSlotConfig(deviceId: String) {
viewModelScope.launch {
_hasSeasoningSlotConfigState.value = rep.hasSeasoningSlotConfig(deviceId)
}
}
/**
* 查询指定设备的调料槽位配置
*/
suspend fun getSeasoningSlotsByDeviceId(deviceId: String) =
rep.getSeasoningSlotsByDeviceId(deviceId)
/**
* 插入或更新单个调料槽位配置
*/
suspend fun upsertSeasoningSlot(slot: SeasoningSlotEntity) =
rep.upsertSeasoningSlot(slot)
/**
* 批量插入或更新调料槽位配置
*/
suspend fun upsertAllSeasoningSlots(slots: List<SeasoningSlotEntity>) =
rep.upsertAllSeasoningSlots(slots)
/**
* 删除指定设备的指定槽位配置
* @param deviceId 目标设备 ID
* @param address 秤硬件地址
*/
suspend fun deleteSeasoningSlot(deviceId: String, address: Int) =
rep.deleteSeasoningSlot(deviceId, address)
/**
* 删除指定设备的全部槽位配置(子设备接收全量广播时先清空再写入)
* @param deviceId 目标设备 ID
*/
suspend fun deleteAllSlotsByDeviceId(deviceId: String) =
rep.deleteAllSlotsByDeviceId(deviceId)
// ---- 数据库调试查看 ----
/**
* 物理清空全部 4 张表的测试数据,完成后通过回调通知 UI
* @param onDone 清除完成后在主线程执行的回调
*/
fun clearAllData(onDone: () -> Unit) {
viewModelScope.launch {
rep.clearAllData()
onDone()
}
}
companion object {
/** 每页加载条数 */
const val DB_INSPECT_PAGE_SIZE = 50
}
/**
* 单条数据库记录的展示模型
* @param isDel 是否已逻辑删除
* @param fields 字段名-值对列表,按顺序排列
* @param columnsPerRow 每行显示的列数,默认 3;槽位表传 1 保持单列
*/
data class DbRecord(
val isDel: Boolean,
val fields: List<Pair<String, String>>,
val columnsPerRow: Int = 3
)
/**
* 数据库调试页面 UI 状态
* @param items 当前已加载的记录列表
* @param totalCount 当前过滤条件下的总记录数
* @param hasMore 是否还有更多数据可加载
* @param isLoading 是否正在加载(首次加载或加载更多)
* @param filter 当前生效的筛选条件,供"加载更多"复用
*/
data class DbInspectUiState(
val items: List<DbRecord> = emptyList(),
val totalCount: Int = 0,
val hasMore: Boolean = false,
val isLoading: Boolean = false,
val filter: DbInspectFilter = DbInspectFilter()
)
/**
* 数据库调试页面筛选条件
* @param showDel 是否包含已删除记录
* @param exactFields 精确匹配字段:列名 → 值
* @param likeFields 模糊匹配字段:列名 → 值(自动加 %value%)
*/
data class DbInspectFilter(
val showDel: Boolean = true,
val exactFields: Map<String, String> = emptyMap(),
val likeFields: Map<String, String> = emptyMap()
)
private val _dbInspectState = MutableStateFlow(DbInspectUiState())
val dbInspectState: StateFlow<DbInspectUiState> = _dbInspectState.asStateFlow()
/**
* 加载指定表的第一页数据(切换 Tab / 修改筛选条件时调用)
* @param tableIndex 0=菜品 1=食材 2=调料 3=槽位
* @param filter 筛选条件,包含 showDel 和各字段过滤值
*/
fun loadDbInspect(tableIndex: Int, filter: DbInspectFilter) {
viewModelScope.launch {
// 立即清空旧数据并显示加载状态,避免切换 Tab 时旧数据闪现
_dbInspectState.value = DbInspectUiState(isLoading = true, filter = filter)
val (items, total) = queryPage(tableIndex, filter, offset = 0)
_dbInspectState.value = DbInspectUiState(
items = items,
totalCount = total,
hasMore = items.size < total,
isLoading = false,
filter = filter
)
}
}
/**
* 追加加载下一页数据(点击"加载更多"时调用)
* 筛选条件从当前 state 中复用,无需重新传入
* @param tableIndex 0=菜品 1=食材 2=调料 3=槽位
*/
fun loadMoreDbInspect(tableIndex: Int) {
viewModelScope.launch {
val current = _dbInspectState.value
// 已在加载中或无更多数据时直接返回,防止重复请求
if (!current.hasMore || current.isLoading) return@launch
_dbInspectState.value = current.copy(isLoading = true)
val (newItems, total) = queryPage(tableIndex, current.filter, offset = current.items.size)
val merged = current.items + newItems
_dbInspectState.value = DbInspectUiState(
items = merged,
totalCount = total,
hasMore = merged.size < total,
isLoading = false,
filter = current.filter
)
}
}
/**
* 执行分页查询,返回 DbRecord 列表和总记录数
* 通过 buildQueries 将 filter 转换为动态 SQL
*/
private suspend fun queryPage(
tableIndex: Int,
filter: DbInspectFilter,
offset: Int
): Pair<List<DbRecord>, Int> {
val limit = DB_INSPECT_PAGE_SIZE
return when (tableIndex) {
0 -> {
val (pageQ, countQ) = buildQueries("dm_cook_food", "createTime DESC", true, filter, limit, offset)
val total = rep.countCookFoodFiltered(countQ)
val rows = rep.getCookFoodPagedFiltered(pageQ).map { e ->
DbRecord(
isDel = e.isDel == 1,
fields = listOf(
"id" to "${e.id}", "foodId" to "${e.foodId}", "foodName" to "${e.foodName}",
"canteenId" to "${e.canteenId}", "foodWeight" to "${e.foodWeight}", "cookMode" to "${e.cookMode}",
"dinnerType" to "${e.dinnerType}", "isDel" to "${e.isDel}", "submitState" to "${e.submitState}",
"createTime" to "${e.createTime}"
)
)
}
rows to total
}
1 -> {
val (pageQ, countQ) = buildQueries("dm_cook_food_goods", "createTime DESC", true, filter, limit, offset)
val total = rep.countCookFoodGoodsFiltered(countQ)
val rows = rep.getCookFoodGoodsPagedFiltered(pageQ).map { e ->
DbRecord(
isDel = e.isDel == 1,
fields = listOf(
"id" to "${e.id}", "pid" to "${e.pid}", "goodsId" to "${e.goodsId}",
"goodsName" to "${e.goodsName}", "foodId" to "${e.foodId}", "materialType" to "${e.materialType}",
"relateionType" to "${e.relateionType}", "allEdible" to "${e.allEdible}", "useWeight" to "${e.useWeight}",
"sort" to "${e.sort}", "zjmCode" to "${e.zjmCode}", "materId" to "${e.materId}",
"isDel" to "${e.isDel}", "createTime" to "${e.createTime}"
)
)
}
rows to total
}
2 -> {
val (pageQ, countQ) = buildQueries("dm_seasoning", "sort ASC", true, filter, limit, offset)
val total = rep.countSeasoningFiltered(countQ)
val rows = rep.getSeasoningPagedFiltered(pageQ).map { e ->
DbRecord(
isDel = e.isDel == 1,
fields = listOf(
"id" to "${e.id}", "goodsId" to "${e.goodsId}", "goodsName" to "${e.goodsName}",
"sort" to "${e.sort}", "materialType" to "${e.materialType}", "useWeight" to "${e.useWeight}",
"zjmCode" to "${e.zjmCode}", "materId" to "${e.materId}", "isDel" to "${e.isDel}",
"createTime" to "${e.createTime}"
)
)
}
rows to total
}
3 -> {
val (pageQ, countQ) = buildQueries("dm_seasoning_slot", "deviceId ASC, address ASC", false, filter, limit, offset)
val total = rep.countSlotFiltered(countQ)
val rows = rep.getSlotPagedFiltered(pageQ).map { e ->
DbRecord(
isDel = false,
fields = listOf(
"deviceId" to e.deviceId, "address" to "${e.address}",
"goodsId" to e.goodsId, "goodsName" to e.goodsName
),
columnsPerRow = 1
)
}
rows to total
}
else -> emptyList<DbRecord>() to 0
}
}
/**
* 根据筛选条件动态构建分页查询和计数查询的 SQL
* @param tableName 表名
* @param orderBy ORDER BY 子句(不含关键字)
* @param hasIsDel 是否有 isDel 字段,无则忽略 showDel 参数
* @param filter 筛选条件
* @param limit 分页大小
* @param offset 偏移量
* @return Pair<分页查询, 计数查询>
*/
private fun buildQueries(
tableName: String,
orderBy: String,
hasIsDel: Boolean,
filter: DbInspectFilter,
limit: Int,
offset: Int
): Pair<SimpleSQLiteQuery, SimpleSQLiteQuery> {
val conditions = mutableListOf<String>()
val args = mutableListOf<Any>()
if (hasIsDel && !filter.showDel) {
conditions.add("isDel = 0")
}
filter.exactFields.forEach { (col, value) ->
if (value.isNotBlank()) {
conditions.add("$col = ?")
args.add(value)
}
}
filter.likeFields.forEach { (col, value) ->
if (value.isNotBlank()) {
conditions.add("$col LIKE ?")
args.add("%$value%")
}
}
val where = if (conditions.isEmpty()) "" else "WHERE ${conditions.joinToString(" AND ")}"
val pageQuery = SimpleSQLiteQuery(
"SELECT * FROM $tableName $where ORDER BY $orderBy LIMIT ? OFFSET ?",
(args + limit + offset).toTypedArray()
)
val countQuery = SimpleSQLiteQuery(
"SELECT COUNT(*) FROM $tableName $where",
args.toTypedArray()
)
return pageQuery to countQuery
}
//--------------------------------------------------------------------------------------------------------
// viewModelScope.launch {
// list.forEach {
// rep.updateSeasoning(it)
// }
// }
// }
fun saveSeasoning(entity: SeasoningEntity, block: () -> Unit) {
viewModelScope.launch {
val list = rep.getSeasoningBySort(entity.sort)
val size = list?.size?:0
if (size == 1) {
rep.updateSeasoning(entity.also { it.id = list!![0].id })
block()
return@launch
}
if (size > 1) {
list?.forEach {
rep.deleteSeasoningBySort(it.sort)
}
}
entity.id = 0
val id = rep.insertSeasoning(entity)
entity.id = id
block()
}
}
/**
* 按 goodsId 查重后 upsert 调料数据,确保同一 goodsId 只有一条记录
*/
suspend fun upsertSeasoningByGoodsId(entity: SeasoningEntity) {
rep.upsertSeasoningByGoodsId(entity)
}
/**
* 按 goodsId 查询调料详情
*/
suspend fun getSeasoningByGoodsId(goodsId: String): SeasoningEntity? {
return rep.getSeasoningByGoodsId(goodsId)
}
//--------------------------------------------------------------------------------------------------------
/**
* 已采集食材列表 UI 状态流
* 元素为按名称分组后的(名称,数量)数据
*/
private val _collectedFoodListState = MutableStateFlow<List<CollectedFoodItem>>(emptyList())
val collectedFoodListState: StateFlow<List<CollectedFoodItem>> = _collectedFoodListState.asStateFlow()
// /**
// * 加载已采集食材列表,按名称分组统计数量
// * 使用属性查询只读 name 字段,不加载 foodVector,节省内存
// * @param nameFilter 名称过滤关键词,为空时加载全部
// */
// fun loadCollectedFoodList(nameFilter: String? = null) {
// viewModelScope.launch {
// val result = ObjectBox.queryFoodNameCounts(nameFilter)
// _collectedFoodListState.value = result.map { (name, count) ->
// CollectedFoodItem(foodName = name, collectCount = count)
// }
// }
// }
// /**
// * 删除指定名称的所有采集记录,删除完成后刷新列表
// * @param foodName 食材名称
// * @param nameFilter 当前搜索关键词,用于删除后刷新列表
// */
// fun removeCollectedFood(foodName: String, nameFilter: String? = null) {
// viewModelScope.launch {
// ObjectBox.remove(foodName)
// loadCollectedFoodList(nameFilter)
// }
// }
// /**
// * 清除所有向量采集数据,完成后刷新列表
// * @param onDone 清除完成后在主线程执行的回调
// */
// fun clearAllCollectedFood(onDone: (() -> Unit)? = null) {
// viewModelScope.launch {
// ObjectBox.removeAll()
// loadCollectedFoodList()
// onDone?.invoke()
// }
// }
//--------------------------------------------------------------------------------------------------------
}
@@ -1,92 +0,0 @@
package com.shuwei.dish.match.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface AppDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCookFood(item: CookFoodEntity): Long
@Update
suspend fun updateCookFood(item: CookFoodEntity)
@Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode")
suspend fun getCookFoodById(canteenId:String, foodId: String, cookMode: Int): CookFoodEntity?
@Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode AND dinnerType = :dinnerType ORDER BY createTime DESC")
suspend fun getCookFoodList(canteenId:String, cookMode: Int, dinnerType:String): MutableList<CookFoodEntity>?
@Query("SELECT count(1) FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode")
suspend fun countCookFood(canteenId:String, cookMode: Int): Int
@Query("DELETE FROM dm_cook_food WHERE canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode")
suspend fun deleteCookFood(canteenId:String, foodId: String, cookMode: Int)
//--------------------------------------------------------------------------------------------
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGoods(item: CookFoodGoodsEntity): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>): Array<Long>
@Update
suspend fun updateGoods(item: CookFoodGoodsEntity)
@Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND pid = :pid AND foodId = :foodId")
suspend fun getCookFoodGoodsList(pid:Long, foodId: String): MutableList<CookFoodGoodsEntity>
@Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId AND materialType = :materialType")
suspend fun getCookFoodGoodsListByType(
foodId: String,
materialType: Int
): MutableList<CookFoodGoodsEntity>
@Query("DELETE FROM dm_cook_food_goods WHERE pid = :pid AND foodId = :foodId")
suspend fun deleteCookFoodGoodsList(pid:Long, foodId: String)
//--------------------------------------------------------------------------------------------
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND id = :id")
suspend fun getSeasoningById(id: Long): SeasoningEntity?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
suspend fun getSeasoningBySort(sort: Int): MutableList<SeasoningEntity>?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId = :goodsId")
suspend fun getSeasoningByGoodsId(goodsId: Int): SeasoningEntity?
// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId != -1 AND goodsName != '' ORDER BY sort ASC")
// fun getAllStream(): Flow<MutableList<SeasoningEntity>>
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId != -1 AND goodsName != '' ORDER BY sort ASC")
fun getAllStream(): MutableList<SeasoningEntity>
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsName LIKE '%' || :query || '%'")
suspend fun search(query: String): MutableList<SeasoningEntity>
@Query("DELETE FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
suspend fun deleteSeasoningBySort(sort:Int): Int
@Query("DELETE FROM dm_seasoning WHERE isDel = 0")
suspend fun clearAllSeasoning(): Int
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>): Array<Long>
@Update
suspend fun updateSeasoning(item: SeasoningEntity)
@Insert
suspend fun insertSeasoning(item: SeasoningEntity): Long
//--------------------------------------------------------------------------------------------
}
@@ -1,20 +1,61 @@
//package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao
//import androidx.room.Query
//import com.shuwei.dish.match.db.BaseDao
//import com.shuwei.dish.match.entity.CookFoodEntity
//
//@Dao
//interface CookFoodDao : BaseDao<CookFoodEntity> {
//
// @Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND foodId = :foodId AND cookMode = :cookMode")
// suspend fun getCookFoodById(foodId: String, cookMode:Int): CookFoodEntity?
//
// @Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND cookMode = :cookMode")
// suspend fun getCookFoodList(cookMode:Int): MutableList<CookFoodEntity>?
//
// @Query("SELECT count(1) FROM dm_cook_food WHERE isDel = 0 AND cookMode = :cookMode")
// suspend fun countCookFood(cookMode:Int): Int
//
//}
package com.shuwei.dish.match.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery
import com.shuwei.dish.match.db.entity.CookFoodEntity
@Dao
interface CookFoodDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCookFood(item: CookFoodEntity): Long
@Update
suspend fun updateCookFood(item: CookFoodEntity)
@Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode AND dinnerType = :dinnerType")
suspend fun getCookFoodById(canteenId: String, foodId: String, cookMode: Int, dinnerType: String): CookFoodEntity?
@Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode AND dinnerType = :dinnerType ORDER BY createTime DESC")
suspend fun getCookFoodList(canteenId: String, cookMode: Int, dinnerType: String): MutableList<CookFoodEntity>?
@Query("SELECT count(1) FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode")
suspend fun countCookFood(canteenId: String, cookMode: Int): Int
// @Query("DELETE FROM dm_cook_food WHERE canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode")
@Query("UPDATE dm_cook_food SET isDel = 1 WHERE isDel = 0 AND canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode AND dinnerType = :dinnerType")
suspend fun deleteCookFood(canteenId: String, foodId: String, cookMode: Int, dinnerType: String)
/** 接口提交成功后逻辑删除菜品,同时把 submitState 置 1 以便在数据库审计中区分手动删除 */
@Query("UPDATE dm_cook_food SET isDel = 1, submitState = 1 WHERE isDel = 0 AND canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode AND dinnerType = :dinnerType")
suspend fun markSubmittedAndDelete(canteenId: String, foodId: String, cookMode: Int, dinnerType: String)
/** 查询全部菜品记录(含已删除),用于数据库调试查看 */
@Query("SELECT * FROM dm_cook_food ORDER BY createTime DESC")
suspend fun getAllCookFood(): MutableList<CookFoodEntity>
/** 分页查询菜品记录,showDel=true 时包含已删除记录 */
@Query("SELECT * FROM dm_cook_food WHERE (:showDel = 1 OR isDel = 0) ORDER BY createTime DESC LIMIT :limit OFFSET :offset")
suspend fun getCookFoodPaged(showDel: Int, limit: Int, offset: Int): List<CookFoodEntity>
/** 统计菜品记录总数,showDel=true 时包含已删除记录 */
@Query("SELECT COUNT(*) FROM dm_cook_food WHERE (:showDel = 1 OR isDel = 0)")
suspend fun countCookFoodAll(showDel: Int): Int
/** 物理清空全表,用于清除测试数据 */
@Query("DELETE FROM dm_cook_food")
suspend fun deleteAll()
/** 动态条件分页查询菜品记录,由 ViewModel 构建 SQL */
@RawQuery
suspend fun queryPagedFiltered(query: SupportSQLiteQuery): List<CookFoodEntity>
/** 动态条件统计菜品记录总数,由 ViewModel 构建 SQL */
@RawQuery
suspend fun countFiltered(query: SupportSQLiteQuery): Int
}
@@ -1,29 +1,61 @@
//package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao
//import androidx.room.Query
//import com.shuwei.dish.match.db.BaseDao
//import com.shuwei.dish.match.entity.CookFoodGoodsEntity
//
//@Dao
//interface CookFoodGoodsDao : BaseDao<CookFoodGoodsEntity> {
//
// @Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId")
// suspend fun getCookFoodGoodsList(foodId: String): MutableList<CookFoodGoodsEntity>
//
// @Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId AND materialType = :materialType")
// suspend fun getCookFoodGoodsListByType(foodId: String, materialType:Int): MutableList<CookFoodGoodsEntity>
//
//// @Query(
//// """
//// UPDATE dm_cook_food_goods
//// SET goodsName=:entity.goodsName, foodId=:entity.foodId, relateionType=:entity.relateionType, sort=:entity.sort
//// allEdible=:entity.allEdible, useWeight=:entity.useWeight, isDel=:entity.isDel, createTime=:entity.createTime
//// WHERE isDel = 0
//// AND foodId=:entity.foodId
//// AND goodsId=:goodsId
//// """
//// )
//// fun updateByGoodsId(entity: CookFoodGoodsEntity)
//
//}
package com.shuwei.dish.match.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
@Dao
interface CookFoodGoodsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGoods(item: CookFoodGoodsEntity): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>): Array<Long>
@Update
suspend fun updateGoods(item: CookFoodGoodsEntity)
@Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND pid = :pid AND foodId = :foodId")
suspend fun getCookFoodGoodsList(pid: Long, foodId: String): MutableList<CookFoodGoodsEntity>
@Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId AND materialType = :materialType")
suspend fun getCookFoodGoodsListByType(foodId: String, materialType: Int): MutableList<CookFoodGoodsEntity>
// @Query("DELETE FROM dm_cook_food_goods WHERE pid = :pid AND foodId = :foodId")
@Query("UPDATE dm_cook_food_goods SET isDel = 1 WHERE isDel = 0 AND pid = :pid AND foodId = :foodId")
suspend fun deleteCookFoodGoodsList(pid: Long, foodId: String)
/** 仅逻辑删除调料行(materialType = 3),主辅材不动,用于 cook 时只重建调料部分 */
@Query("UPDATE dm_cook_food_goods SET isDel = 1 WHERE isDel = 0 AND pid = :pid AND foodId = :foodId AND materialType = 3")
suspend fun deleteSeasoningOnly(pid: Long, foodId: String)
/** 查询全部食材记录(含已删除),用于数据库调试查看 */
@Query("SELECT * FROM dm_cook_food_goods ORDER BY createTime DESC")
suspend fun getAllCookFoodGoods(): MutableList<CookFoodGoodsEntity>
/** 分页查询食材记录,showDel=true 时包含已删除记录 */
@Query("SELECT * FROM dm_cook_food_goods WHERE (:showDel = 1 OR isDel = 0) ORDER BY createTime DESC LIMIT :limit OFFSET :offset")
suspend fun getCookFoodGoodsPaged(showDel: Int, limit: Int, offset: Int): List<CookFoodGoodsEntity>
/** 统计食材记录总数,showDel=true 时包含已删除记录 */
@Query("SELECT COUNT(*) FROM dm_cook_food_goods WHERE (:showDel = 1 OR isDel = 0)")
suspend fun countCookFoodGoodsAll(showDel: Int): Int
/** 物理清空全表,用于清除测试数据 */
@Query("DELETE FROM dm_cook_food_goods")
suspend fun deleteAll()
/** 动态条件分页查询食材记录,由 ViewModel 构建 SQL */
@RawQuery
suspend fun queryPagedFiltered(query: SupportSQLiteQuery): List<CookFoodGoodsEntity>
/** 动态条件统计食材记录总数,由 ViewModel 构建 SQL */
@RawQuery
suspend fun countFiltered(query: SupportSQLiteQuery): Int
}
@@ -1,25 +1,70 @@
//package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao
//import androidx.room.Query
//import com.shuwei.dish.match.db.BaseDao
//import com.shuwei.dish.match.entity.SeasoningEntity
//import kotlinx.coroutines.flow.Flow
//
//@Dao
//interface SeasoningDao : BaseDao<SeasoningEntity> {
//
//// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND id = :id")
//// suspend fun getSeasoningById(id: Long): SeasoningEntity?
////
//// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 ORDER BY sort ASC")
//// fun getAllStream(): Flow<MutableList<SeasoningEntity>>
////
//// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND name LIKE '%' || :query || '%'")
//// suspend fun search(query: String): MutableList<SeasoningEntity>
////
//// @Query("DELETE FROM dm_seasoning WHERE rowid IN " +
//// "(SELECT rowid FROM dm_seasoning LIMIT 1000)")
//// suspend fun deleteBatch(): Int
//
//}
package com.shuwei.dish.match.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery
import com.shuwei.dish.match.db.entity.SeasoningEntity
@Dao
interface SeasoningDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSeasoning(item: SeasoningEntity): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>): Array<Long>
@Update
suspend fun updateSeasoning(item: SeasoningEntity)
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND id = :id")
suspend fun getSeasoningById(id: Long): SeasoningEntity?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
suspend fun getSeasoningBySort(sort: Int): MutableList<SeasoningEntity>?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId = :goodsId")
suspend fun getSeasoningByGoodsId(goodsId: String): SeasoningEntity?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId != -1 AND goodsName != '' ORDER BY sort ASC")
fun getAllStream(): MutableList<SeasoningEntity>
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsName LIKE '%' || :query || '%'")
suspend fun search(query: String): MutableList<SeasoningEntity>
// @Query("DELETE FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
@Query("UPDATE dm_seasoning SET isDel = 1 WHERE isDel = 0 AND sort = :sort")
suspend fun deleteSeasoningBySort(sort: Int): Int
// @Query("DELETE FROM dm_seasoning WHERE isDel = 0")
@Query("UPDATE dm_seasoning SET isDel = 1 WHERE isDel = 0")
suspend fun clearAllSeasoning(): Int
/** 查询全部调料记录(含已删除),用于数据库调试查看 */
@Query("SELECT * FROM dm_seasoning ORDER BY sort ASC")
suspend fun getAllSeasoning(): MutableList<SeasoningEntity>
/** 分页查询调料记录,showDel=true 时包含已删除记录 */
@Query("SELECT * FROM dm_seasoning WHERE (:showDel = 1 OR isDel = 0) ORDER BY sort ASC LIMIT :limit OFFSET :offset")
suspend fun getSeasoningPaged(showDel: Int, limit: Int, offset: Int): List<SeasoningEntity>
/** 统计调料记录总数,showDel=true 时包含已删除记录 */
@Query("SELECT COUNT(*) FROM dm_seasoning WHERE (:showDel = 1 OR isDel = 0)")
suspend fun countSeasoningAll(showDel: Int): Int
/** 物理清空全表,用于清除测试数据(比 clearAllSeasoning 的逻辑删除更彻底) */
@Query("DELETE FROM dm_seasoning")
suspend fun deleteAll()
/** 动态条件分页查询调料记录,由 ViewModel 构建 SQL */
@RawQuery
suspend fun queryPagedFiltered(query: SupportSQLiteQuery): List<SeasoningEntity>
/** 动态条件统计调料记录总数,由 ViewModel 构建 SQL */
@RawQuery
suspend fun countFiltered(query: SupportSQLiteQuery): Int
}
@@ -0,0 +1,77 @@
package com.shuwei.dish.match.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.sqlite.db.SupportSQLiteQuery
import com.shuwei.dish.match.db.entity.SeasoningSlotEntity
/**
* 调料槽位配置 DAO
* 以 (deviceId, address) 为主键做 upsert,保证同一槽位只保留最新配置
*/
@Dao
interface SeasoningSlotDao {
/**
* 插入或更新单个槽位配置(主键冲突时覆盖)
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(slot: SeasoningSlotEntity)
/**
* 批量插入或更新槽位配置(主设备广播全量时使用)
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertAll(slots: List<SeasoningSlotEntity>)
/**
* 查询指定设备的全部槽位配置
* @param deviceId 目标设备 ID
*/
@Query("SELECT * FROM dm_seasoning_slot WHERE deviceId = :deviceId")
suspend fun queryByDeviceId(deviceId: String): List<SeasoningSlotEntity>
/**
* 查询所有设备的全部槽位配置(主设备广播时使用)
*/
@Query("SELECT * FROM dm_seasoning_slot")
suspend fun queryAll(): List<SeasoningSlotEntity>
/** 分页查询槽位记录(槽位表无 isDel,showDel 参数忽略) */
@Query("SELECT * FROM dm_seasoning_slot ORDER BY deviceId ASC, address ASC LIMIT :limit OFFSET :offset")
suspend fun getSlotPaged(limit: Int, offset: Int): List<SeasoningSlotEntity>
/** 统计槽位记录总数 */
@Query("SELECT COUNT(*) FROM dm_seasoning_slot")
suspend fun countSlotAll(): Int
/**
* 删除指定设备的指定槽位配置
* @param deviceId 目标设备 ID
* @param address 秤硬件地址
*/
@Query("DELETE FROM dm_seasoning_slot WHERE deviceId = :deviceId AND address = :address")
suspend fun deleteByDeviceIdAndAddress(deviceId: String, address: Int)
/**
* 删除指定设备的全部槽位配置(子设备接收全量广播时先清空再写入)
* @param deviceId 目标设备 ID
*/
@Query("DELETE FROM dm_seasoning_slot WHERE deviceId = :deviceId")
suspend fun deleteAllByDeviceId(deviceId: String)
/** 物理清空全表,用于清除测试数据 */
@Query("DELETE FROM dm_seasoning_slot")
suspend fun deleteAll()
/** 动态条件分页查询槽位记录,由 ViewModel 构建 SQL */
@RawQuery
suspend fun queryPagedFiltered(query: SupportSQLiteQuery): List<SeasoningSlotEntity>
/** 动态条件统计槽位记录总数,由 ViewModel 构建 SQL */
@RawQuery
suspend fun countFiltered(query: SupportSQLiteQuery): Int
}
@@ -1,4 +1,4 @@
package com.shuwei.dish.match.entity
package com.shuwei.dish.match.db.entity
import androidx.room.Entity
import androidx.room.Ignore
@@ -18,11 +18,17 @@ data class CookFoodEntity(
//0-制作模式,1-采样模式,2-品控模式
var cookMode:Int = 0,
var isDel: Int = 0,
/** 接口提交状态:0-未提交(本地草稿或手动删除),1-接口提交成功后删除 */
var submitState: Int = 0,
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now()),
var dinnerType: String? = "0",
) : BaseEntity {
// @Ignore
// var stFoodInfoConstituteList: MutableList<CookFoodGoodsEntity>? = null
@Ignore
var stFoodInfoConstituteList: MutableList<CookFoodGoodsEntity>? = null
var matchingConstituteInfoList: MutableList<CookFoodGoodsEntity>? = null
@Ignore
var foodConstituteList: MutableList<CookFoodGoodsEntity>? = null
constructor() : this(foodId = "") // 必需的空构造
}
@@ -1,9 +1,9 @@
package com.shuwei.dish.match.entity
package com.shuwei.dish.match.db.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.shuwei.dish.match.db.BaseEntity
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.utils.DateTimeUtil
import java.io.Serializable
import java.time.LocalDateTime
@@ -43,10 +43,14 @@ class CookFoodGoodsEntity(
*/
var materialType: Int = 0,
// /**
// * 物品 是否全部可食:1-全部可食,0-部分可食,默认全部
// */
// var allEdible: Int = 1,
/**
* 物品 是否全部可食1-全部可食0-部分可食默认全部
* 物品 是否全部可食true-全部可食false-部分可食默认全部
*/
var allEdible: Int = 1,
var allEdible: Boolean = true,
/**
* 物品 用料重量
@@ -59,23 +63,36 @@ class CookFoodGoodsEntity(
var relateionType_dictText: String? = "",
var zjmCode: String? = "",
var materId: String? = "",
/** 物料编码 */
var goodsCode: String? = "",
/** 净材种类 */
var rawMaterialsType: String? = null,
var isDel: Int = 0,
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
) : BaseEntity, Serializable {
constructor() : this(goodsId = "") // 必需的空构造,
constructor() : this(goodsId = "") // 必需的空构造
@Ignore
var isSetFinished: Boolean = false
@Ignore
var isItemClicked: Boolean = false
@Ignore
var isSamplingPage: Boolean = false
@Ignore
var isNewDishType: Boolean = false
@Ignore
var isClicked: Boolean = false
/**
* 转换为 GoodsItem用于 UI DishPartAdapterPrepareFoodActivity
*/
fun toGoodsItem(): GoodsItem = GoodsItem(
goodsId = goodsId,
goodsName = goodsName,
popularName = popularName,
zjmCode = zjmCode,
materId = materId,
materCode = goodsCode,
materialType = materialType,
useWeight = useWeight,
relateionType = relateionType,
allEdible = allEdible,
goodsOrRelationCode = goodsOrRelationCode,
rawMaterialsType = rawMaterialsType
)
}
@@ -0,0 +1,6 @@
package com.shuwei.dish.match.db.entity
/** 默认调料配置 JSON 反序列化的包装类 */
data class ResetReasoningRecord(
val list: MutableList<SeasoningEntity> = mutableListOf()
)
@@ -0,0 +1,97 @@
package com.shuwei.dish.match.db.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.shuwei.dish.match.db.BaseEntity
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.utils.DateTimeUtil
import java.time.LocalDateTime
@Entity(tableName = "dm_seasoning")
data class SeasoningEntity(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
/**
* 物品id
*/
var goodsId: String,
/**
* 物品名称
*/
var goodsName: String? = null,
/**
* 菜品id
*/
var foodId: String? = "",
var goodsOrRelationCode: String? = "",
/**
* 食材原材料:1,预制品:2
*/
var relateionType: Int = 0,
/**
* 物品类型:1主料2辅料3调料
*/
var materialType: Int = 3,
// /**
// * 物品 是否全部可食:1-全部可食,0-部分可食,默认全部
// */
// var allEdible: Int = 1,
/**
* 物品 是否全部可食:true-全部可食,false-部分可食,默认全部
*/
var allEdible: Boolean = true,
/**
* 物品 用料重量
*/
var useWeight: Double? = 0.0,
var popularName: String? = "",
var canteenId: String? = "",
var relateionType_dictText: String? = "",
var zjmCode: String? = "",
var materId: String? = "",
/** 物料编码 */
var goodsCode: String? = "",
/** 净材种类 */
var rawMaterialsType: String? = null,
var sort: Int = 9999,
/**
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
*/
var isDel: Int = 0,
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
) : BaseEntity {
/** UI 状态:格子类型,非数据库字段 */
@Ignore var pageType: Int = 0
/** UI 状态:是否选中,非数据库字段 */
@Ignore var isClicked: Boolean = false
constructor() : this(goodsId = "") // 必需的空构造
/** 转换为 GoodsItem,用于 UI 层展示 */
fun toGoodsItem(): GoodsItem = GoodsItem(
goodsId = goodsId,
goodsName = goodsName,
popularName = popularName,
zjmCode = zjmCode,
materId = materId,
materialType = materialType,
useWeight = useWeight,
relateionType = relateionType,
allEdible = allEdible,
goodsOrRelationCode = goodsOrRelationCode,
rawMaterialsType = rawMaterialsType
)
}
@@ -0,0 +1,23 @@
package com.shuwei.dish.match.db.entity
import androidx.room.Entity
/**
* 调料槽位配置表,记录每个格子对应哪种调料
* 主键为 (deviceId, address) 组合,唯一标识一个物理秤槽位
*
* @param deviceId 所属设备 ID,用于区分 22格/18格 设备
* @param address 秤硬件地址,对应 ScaleDeviceConfig.SCALE_ORDER_22/18 中的值
* @param goodsId 调料 ID
* @param goodsName 调料名称,用于 adapter 展示
*/
@Entity(
tableName = "dm_seasoning_slot",
primaryKeys = ["deviceId", "address"]
)
data class SeasoningSlotEntity(
val deviceId: String,
val address: Int,
val goodsId: String,
val goodsName: String
)
@@ -16,14 +16,13 @@ import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogBottomSheetBinding
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.GoodsRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.net.RemoteRepository
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.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import com.shuwei.dish.match.utils.ext.toast
/**
@@ -37,7 +36,7 @@ class BottomDialog(
const val TAG = "BottomDialog"
}
private var list = mutableListOf<CookFoodGoodsEntity>()
private var list = mutableListOf<GoodsItem>()
private lateinit var binding: DialogBottomSheetBinding
private lateinit var goodsInfoSearchAdapter: GoodsInfoSearchAdapter
@@ -45,7 +44,7 @@ class BottomDialog(
private var activity: BaseActivity? = null
fun show(activity: BaseActivity, callback: (item: CookFoodGoodsEntity) -> Unit) {
fun show(activity: BaseActivity, callback: (item: GoodsItem) -> Unit) {
this.activity = activity
//直接使用BottomSheetDialog
var dialog: BottomSheetDialog? = null
@@ -142,40 +141,29 @@ class BottomDialog(
*/
@SuppressLint("NotifyDataSetChanged")
private fun getGoodsList() {
// val pathSegmentList = UrlConfig.QUERY_GOODS_LIST.split(delimiters = arrayOf("/"), limit = 1)
val queryParams = mutableMapOf(
val param = mutableMapOf<String, Any>(
"goodsType" to "$goodsType",
"canteenId" to BaseApp.canteenId,
"pageNo" to "$pageNo",
"pageSize" to "$pageSize"
"placeId" to BaseApp.canteenId,
"pageNum" to pageNo,
"pageSize" to pageSize
)
if (goodsName.isNullOrBlank().not()) {
queryParams.put("name", goodsName!!)
param["goodsName"] = goodsName!!
}
val sb = StringBuilder().apply {
append(UrlConfig.QUERY_GOODS_LIST)
append("?")
queryParams.forEach { (key, value) ->
append("$key=$value&")
}
}
sb.deleteCharAt(sb.length - 1)
Log.d(TAG, "getGoodsList: url = $sb")
HttpUtil.get(
url = sb.toString(),
doSuccess = {
val json = it.toJsonString()
Log.d(TAG, "getGoodsList: json=$json")
val recordBean: GoodsRecordBean? = json.toObject<GoodsRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
Log.d(TAG, "getGoodsList: param=$param")
activity?.lifecycleScope?.launch {
val state = RemoteRepository().queryGoodsList(param)
when (state) {
is UiState.Success -> {
val records = state.data
if (records.isNullOrEmpty()) {
activity?.toast(if (goodsType == 1) "暂未搜索到调料信息" else "暂未搜索到食材信息")
return@get
return@launch
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(records)
goodsInfoSearchAdapter.notifyDataSetChanged()
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
@@ -187,14 +175,18 @@ class BottomDialog(
if (isLoadMoreEnable) {
pageNo++
}
}, doFailure = { code, msg ->
activity?.toast(msg)
}
is UiState.Error -> {
activity?.toast(state.msg)
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
} else {
binding.refreshLayout.finishLoadMore(1200)
}
})
}
else -> {}
}
}
}
}
@@ -23,20 +23,19 @@ import com.shuwei.dish.match.adapter.SeasoningSearchAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogBottomSheet2Binding
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.SeasoningRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.net.RemoteRepository
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
/**
@@ -52,7 +51,7 @@ class BottomDialog2(
}
private val goodsType: Int = 1
private var list = mutableListOf<SeasoningEntity>()
private var list = mutableListOf<GoodsItem>()
private lateinit var binding: DialogBottomSheet2Binding
private lateinit var searchAdapter: SeasoningSearchAdapter
@@ -60,7 +59,7 @@ class BottomDialog2(
private var activity: BaseActivity? = null
fun show(activity: BaseActivity, callback: (item: SeasoningEntity) -> Unit) {
fun show(activity: BaseActivity, callback: (item: GoodsItem) -> Unit) {
this.activity = activity
//直接使用BottomSheetDialog
var dialog: BottomSheetDialog? = null
@@ -173,39 +172,29 @@ class BottomDialog2(
*/
@SuppressLint("NotifyDataSetChanged")
private fun getGoodsList() {
val queryParams = mutableMapOf(
val param = mutableMapOf<String, Any>(
"goodsType" to "$goodsType",
"canteenId" to BaseApp.canteenId,
"pageNo" to "$pageNo",
"pageSize" to "$pageSize"
"placeId" to BaseApp.canteenId,
"pageNum" to pageNo,
"pageSize" to pageSize
)
if (goodsName.isNullOrBlank().not()) {
queryParams.put("name", goodsName!!)
param["goodsName"] = goodsName!!
}
val sb = StringBuilder().apply {
append(UrlConfig.QUERY_GOODS_LIST)
append("?")
queryParams.forEach { (key, value) ->
append("$key=$value&")
}
}
sb.deleteCharAt(sb.length - 1)
Log.d(TAG, "getGoodsList: url = $sb")
HttpUtil.get(
url = sb.toString(),
doSuccess = {
val json = it.toJsonString()
Log.d(TAG, "getGoodsList: json=$json")
val recordBean: SeasoningRecordBean? = json.toObject<SeasoningRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
Log.d(TAG, "getGoodsList: param=$param")
activity?.lifecycleScope?.launch {
val state = RemoteRepository().queryGoodsList(param)
when (state) {
is UiState.Success -> {
val records = state.data
if (records.isNullOrEmpty()) {
activity?.toast(if (goodsType == 1) "暂未搜索到调料信息" else "暂未搜索到食材信息")
return@get
return@launch
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(records)
searchAdapter.notifyDataSetChanged()
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
@@ -217,14 +206,18 @@ class BottomDialog2(
if (isLoadMoreEnable) {
pageNo++
}
}, doFailure = { code, msg ->
activity?.toast(msg)
}
is UiState.Error -> {
activity?.toast(state.msg)
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
} else {
binding.refreshLayout.finishLoadMore(1200)
}
})
}
else -> {}
}
}
}
fun getTextSpan(weight: Double): SpannableStringBuilder {
@@ -1,62 +0,0 @@
package com.shuwei.dish.match.entity
data class HomeModeBean(
var modeName: String = "",
var modeDesc: String = "",
var modeIconId: Int = 0,
var isLocked: Boolean = false,
var onClick: (() -> Unit)? = null
)
data class FoodRecordBean(
var records: MutableList<FoodRecord>? = null,
var total: Int,
var size: Int,
var current: Int,
var page: Int
)
data class GoodsRecordBean(
var records: MutableList<CookFoodGoodsEntity>? = null,
var total: Int,
var size: Int,
var current: Int,
var page: Int
)
data class SeasoningRecordBean(
var records: MutableList<SeasoningEntity>? = null,
var total: Int,
var size: Int,
var current: Int,
var page: Int
)
data class ResetReasoningRecord(
val list: MutableList<SeasoningEntity>
)
//data class DishDetailEntity(
// var foodId: String? = null,
// var foodWeight: Double? = null,
// var foodRealWeight: Double? = null,
// var voList: List<DishPartEntity>? = null
//)
//data class DishPartEntity(
// var foodId: String? = null,
// var foodName: String? = null,
// var foodWeight: Int? = null,
// var useWeight: Int? = null,
// var useRealWeight: Int? = null,
// var goodsId: Int? = null,
// var goodsName: String? = null,
// var materialType: Int? = null,
// var isSetFinished: Boolean = false,
// var isItemClicked: Boolean = false,
// var isSamplingPage: Boolean = false
//)
@@ -1,25 +0,0 @@
package com.shuwei.dish.match.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.shuwei.dish.match.db.BaseEntity
//@Entity(tableName = "dm_dish")
//data class DishEntity (
// /**
// * 菜品id
// */
// @PrimaryKey var id: Long,
// /**
// * 菜品名称
// */
// var name: String? = "",
// /**
// * 菜品重量
// */
// var weight: Int? = 0,
//
// var sort: Int = 0,
// @ColumnInfo(name = "create_time") val createTime: Long = System.currentTimeMillis()
//): BaseEntity
@@ -1,38 +0,0 @@
//package com.shuwei.dish.match.entity
//
//import androidx.room.Ignore
//import androidx.room.PrimaryKey
//import com.shuwei.dish.match.db.BaseEntity
//
//data class GoodsInfoEntity(
// @PrimaryKey(autoGenerate = true) var id: Long = 0,
// var goodsId: Int,
// var goodsName: String? = "",
// var popularName: String? = "",
// var canteenId: String? = "",
// var goodsOrRelationCode: String? = "",
// var relateionType: Int? = -1,
// var relateionType_dictText: String? = "",
//
// var sort: Int = 0,
//
// var swMaterBaseMaterial: String? = "",
// var preMaterClass: String? = "",
// var preUseWeight: Int? = 0,
// var preWeight: Int? = 0,
// var oil: String? = "",
// var sugar: String? = "",
// /**
// * isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
// */
// var isDel: Int = 0,
// val createTime: Long = System.currentTimeMillis()
//) : BaseEntity {
// @Ignore
// var pageType: Int = 0
//
// @Ignore
// var isClicked: Boolean = false
//
// constructor() : this(id = -1, goodsId = -1) // 必需的空构造
//}
@@ -1,69 +0,0 @@
package com.shuwei.dish.match.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.shuwei.dish.match.db.BaseEntity
import com.shuwei.dish.match.utils.DateTimeUtil
import java.time.LocalDateTime
@Entity(tableName = "dm_seasoning")
data class SeasoningEntity(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
/**
* 物品id
*/
var goodsId: String,
/**
* 物品名称
*/
var goodsName: String? = null,
/**
* 菜品id
*/
var foodId: String? = "",
var goodsOrRelationCode: String? = "",
/**
* 食材原材料:1,预制品:2
*/
var relateionType: Int = 0,
/**
* 物品类型:1主料2辅料3调料
*/
var materialType: Int = 3,
/**
* 物品 是否全部可食:1-全部可食,0-部分可食,默认全部
*/
var allEdible: Int = 1,
/**
* 物品 用料重量
*/
var useWeight: Double? = 0.0,
var popularName: String? = "",
var canteenId: String? = "",
var relateionType_dictText: String? = "",
var sort: Int = 9999,
/**
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
*/
var isDel: Int = 0,
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
) : BaseEntity {
@Ignore
var pageType: Int = 0
@Ignore
var isClicked: Boolean = false
constructor() : this(goodsId = "") // 必需的空构造
}
@@ -1,199 +0,0 @@
package com.shuwei.dish.match.http
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.gson.reflect.TypeToken
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.BaseReq
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.ext.toType
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.IOException
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
object HttpUtil {
var loopGetToken = true
fun runMainThread(action: () -> Unit) {
Handler(Looper.getMainLooper()).post {
action()
}
}
// GET请求(HTTPS
fun get(
url: String,
header: Map<String, String>?=null,
doSuccess: (data: Any) -> Unit,
doFailure: (code: Int?, msg: String?) -> Unit
) {
val request = Request.Builder()
.url(if (url.isHttpApi()) url else UrlConfig.BASE_URL + url)
.apply {
if (header != null) {
header.forEach {
addHeader(it.key, it.value)
}
} else {
val token = getToken()
if (token.isNotBlank()) {
addHeader("X-Access-Token", token)
}
}
}
.get()
.build()
HttpClient.instance.newCall(request).enqueue(CallbackImpl(doSuccess, doFailure))
}
//
// fun get(
// isHttps: Boolean = false,
// host: String = "vip.shuziweidao.com",
// pathSegmentList: List<String>,
// queryParams: Map<String, String>,
// doSuccess: (data: Any) -> Unit,
// doFailure: (code: Int?, msg: String?) -> Unit
// ) {
// val token = getToken()
// val url = HttpUrl.Builder()
// .scheme(if (isHttps) "https" else "http")
// .host(host)
// .apply {
// pathSegmentList.forEach { addPathSegment(it) }
// queryParams.forEach { (key, value) -> addQueryParameter(key, value) }
// }
// .build()
// val request = Request.Builder()
// .url(url)
// .apply {
// if (token.isNotBlank()) {
// addHeader("Authorization", token)
// }
// }
// .get()
// .build()
// HttpClient.instance.newCall(request).enqueue(CallbackImpl(doSuccess, doFailure))
// }
// POST表单(HTTPS
// fun postForm(url: String, params: Map<String, String>, callback: Callback) {
// val formBody = FormBody.Builder().apply {
// params.forEach { (k, v) -> add(k, v) }
// }.build()
//
// Request.Builder()
// .url(url)
// .post(formBody)
// .build().let { HttpClient.instance.newCall(it).enqueue(callback) }
// }
// POST JSONHTTPS
fun postJson(
url: String,
json: String,
doSuccess: (data: Any) -> Unit,
doFailure: (code: Int?, msg: String?) -> Unit
) {
val body = json
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
val token = getToken()
Request.Builder()
.url(if (url.isHttpApi()) url else UrlConfig.BASE_URL + url)
.apply {
if (token.isNotBlank()) {
addHeader("X-Access-Token", token)
}
}
.post(body)
.build()
.let { HttpClient.instance.newCall(it).enqueue(CallbackImpl(doSuccess, doFailure)) }
}
val executor = Executors.newSingleThreadScheduledExecutor()
var getTokenIsRunning = false
private fun exeTokenThread() {
if (getTokenIsRunning.not()) {
executor.scheduleWithFixedDelay(
task, 0, 1, TimeUnit.MINUTES
)
getTokenIsRunning = true
}
}
val task = Runnable {
if (BaseApp.token.isNullOrBlank()) {
getAppToken()
} else {
executor.shutdown()
}
}
fun getToken() = SpTool.getString(SpTool.TOKEN)
//BaseApp.instance?.token ?:""
//BaseApp.getSharedPref()?.getString("token", "") ?: ""
fun getAppToken() {
val url = "${UrlConfig.GET_TOKEN}?qrcodeId=${SpTool.getString(SpTool.DEVICE_ID)}"
// Log.d(TAG, "getToken: url = $url")
get(url = url, doSuccess = {
SpTool.put(SpTool.TOKEN, it)
BaseApp.token = it.toString()
}) { code, msg ->
//toast(msg)
}
}
class CallbackImpl(
private val doSuccess: (data: Any) -> Unit,
private val doFailure: (code: Int?, msg: String?) -> Unit
) :
Callback {
override fun onFailure(call: Call, e: IOException) {
runMainThread {
e.printStackTrace()
doFailure(-1, "服务异常,${e.message}")
}
}
override fun onResponse(call: Call, response: Response) {
val respData = response.body?.string()
runMainThread {
Log.d("HttpUtil", respData ?: "")
runCatching {
val typeToken = object : TypeToken<BaseReq<Any>>() {}
val baseReq = respData?.toType<BaseReq<Any>>(typeToken = typeToken)
if (baseReq == null) {
doFailure(-1, "查询数据失败")
return@runCatching
}
if (baseReq.code != 200) {
doFailure(baseReq.code, baseReq.message)
//baseReq.code == 500 ||
if (baseReq.code == 401 && loopGetToken) {
exeTokenThread()
}
return@runCatching
}
doSuccess(baseReq.result ?: "")
}.onFailure {
it.printStackTrace()
doFailure(-1, "解析异常")
}
}
}
}
}
fun String.isHttpApi() = this.startsWith("http://") || this.startsWith("https://")
@@ -1,36 +0,0 @@
package com.shuwei.dish.match.http
import com.shuwei.dish.match.base.BaseApp
object UrlConfig {
private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889"
/**
* device获取token
*/
const val DEVICE_TOKEN = "${DEVICE_BASE_URL}/sys/getEquipmentToken"
/**
*获取配置信息
*/
const val DEVICE_CONFIG = "${DEVICE_BASE_URL}/equipment/stEquipment/queryByEquipmentCode"
// const val BASE_URL = "http://vip.shuziweidao.com/shuwei-zhct"
// const val DISH_DETAIL = "$BASE_URL/scales/goodsUseList?foodId={foodId}&foodWeight={foodWeight}"
// const val DISH_LIST = "$BASE_URL/scales/getRestInfoFoods?eaId=99&type=0&foodName"
// const val BASE_URL = "http://192.168.1.207:9102"
const val BASE_URL = "https://yyjk.shuziweidao.com/gateway"
// const val DISH_LIST = "$BASE_URL/scales/getRestInfoFoods?eaId=99&type=0&foodName"
var DISH_DETAIL = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryById"
// "$BASE_URL/scales/generateToken?deviceId=1111111111111111111111111111"
var GET_TOKEN = "${BaseApp.configUrl}/restaurant/equipment/stEquipment/getEquipmentToken"
var SUBMIT_DISH = "${BaseApp.configUrl}/food/stFoodInfoMatching/saveoredit"
var QUERY_GOODS_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryGoodsInfoList"
var QUERY_FOOD_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/list"
var SAMPLING_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryHistorical/goodsInfoList"
}
@@ -0,0 +1,11 @@
package com.shuwei.dish.match.model
/**
* 已采集食材列表项数据模型
* @param foodName 食材名称
* @param collectCount 已采集数量
*/
data class CollectedFoodItem(
val foodName: String = "",
val collectCount: Int = 0
)
@@ -0,0 +1,43 @@
package com.shuwei.dish.match.model
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import java.io.Serializable
/**
* 菜品详情接口传输对象(DTO),不依赖 Room
* 对应接口:getConstituteByFoodId / saveConstitute
*/
data class CookFoodDTO(
var foodId: String = "",
var foodName: String? = null,
var canteenId: String? = null,
var foodWeight: Double = 0.0,
// 0-制作模式,1-采样模式,2-品控模式
var cookMode: Int = 0,
var dinnerType: String? = "0",
// 接口返回的食材构成列表
var matchingConstituteInfoList: MutableList<CookFoodGoodsDTO>? = null,
var foodConstituteList: MutableList<CookFoodGoodsDTO>? = null
) : Serializable {
/**
* 转换为 Room 实体,用于写入本地数据库
* id/isDel/createTime 由 Room 自行管理,不从 DTO 携带
*/
fun toEntity(): CookFoodEntity = CookFoodEntity(
foodId = foodId,
foodName = foodName,
canteenId = canteenId,
foodWeight = foodWeight,
cookMode = cookMode,
dinnerType = dinnerType
).also { entity ->
entity.matchingConstituteInfoList = matchingConstituteInfoList
?.map { it.toEntity() }
?.toMutableList()
entity.foodConstituteList = foodConstituteList
?.map { it.toEntity() }
?.toMutableList()
}
}
@@ -0,0 +1,119 @@
package com.shuwei.dish.match.model
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import java.io.Serializable
/**
* 菜品构成(主辅料/调料)接口传输对象(DTO),不依赖 Room
* 对应 CookFoodDTO 中 foodConstituteList、matchingConstituteInfoList 的元素
*/
data class CookFoodGoodsDTO(
var goodsId: String = "",
var goodsName: String? = null,
var foodId: String? = "",
var goodsOrRelationCode: String? = "",
// 调料数据顺序
var sort: Int = 0,
// 食材原材料:1,预制品:2
var relateionType: Int = 0,
// 物品类型:1主料 2辅料 3调料
var materialType: Int = 0,
// 物品 是否全部可食:true-全部可食,false-部分可食
var allEdible: Boolean = true,
// 物品 用料重量
var useWeight: Double? = 0.0,
var popularName: String? = "",
var canteenId: String? = "",
var relateionType_dictText: String? = "",
var zjmCode: String? = "",
var materId: String? = "",
// 物料编码
var goodsCode: String? = "",
// 净材种类
var rawMaterialsType: String? = null
) : Serializable {
/**
* 转换为 GoodsItem,用于 UI 层
*/
fun toGoodsItem(): GoodsItem = GoodsItem(
goodsId = goodsId,
goodsName = goodsName,
popularName = popularName,
zjmCode = zjmCode,
materId = materId,
materCode = goodsCode,
materialType = materialType,
useWeight = useWeight,
relateionType = relateionType,
allEdible = allEdible,
goodsOrRelationCode = goodsOrRelationCode,
rawMaterialsType = rawMaterialsType
)
/**
* 转换为 Room 实体,用于写入本地数据库
* id/pid/isDel/createTime 由 Room 自行管理,不从 DTO 携带
*/
fun toEntity(): CookFoodGoodsEntity = CookFoodGoodsEntity(
goodsId = goodsId,
goodsName = goodsName,
foodId = foodId,
goodsOrRelationCode = goodsOrRelationCode,
sort = sort,
relateionType = relateionType,
materialType = materialType,
allEdible = allEdible,
useWeight = useWeight,
popularName = popularName,
canteenId = canteenId,
relateionType_dictText = relateionType_dictText,
zjmCode = zjmCode,
materId = materId,
goodsCode = goodsCode,
rawMaterialsType = rawMaterialsType
)
}
/**
* Room 实体 → DTO 反向映射,用于将本地数据转为接口提交格式
* id/pid/isDel/createTime 为本地数据库字段,不参与提交
*/
fun CookFoodGoodsEntity.toDTO(): CookFoodGoodsDTO = CookFoodGoodsDTO(
goodsId = goodsId,
goodsName = goodsName,
foodId = foodId,
goodsOrRelationCode = goodsOrRelationCode,
sort = sort,
relateionType = relateionType,
materialType = materialType,
allEdible = allEdible,
useWeight = useWeight,
popularName = popularName,
canteenId = canteenId,
relateionType_dictText = relateionType_dictText,
zjmCode = zjmCode,
materId = materId,
goodsCode = goodsCode,
rawMaterialsType = rawMaterialsType
)
/**
* Room 实体 → DTO 反向映射,用于将本地数据转为接口提交格式
* id/isDel/createTime 为本地数据库字段,不参与提交
*/
fun CookFoodEntity.toDTO(): CookFoodDTO = CookFoodDTO(
foodId = foodId,
foodName = foodName,
canteenId = canteenId,
foodWeight = foodWeight,
cookMode = cookMode,
dinnerType = dinnerType,
matchingConstituteInfoList = matchingConstituteInfoList
?.map { it.toDTO() }
?.toMutableList(),
foodConstituteList = foodConstituteList
?.map { it.toDTO() }
?.toMutableList()
)
@@ -0,0 +1,52 @@
package com.shuwei.dish.match.model
import android.graphics.Bitmap
import android.net.Uri
import java.io.File
data class HomeModeBean(
var modeName: String = "",
var modeDesc: String = "",
var modeIconId: Int = 0,
var isLocked: Boolean = false,
var onClick: (() -> Unit)? = null
)
data class FoodCollectionBean(
var imageUri: Uri? = null,
var bitmap: Bitmap? = null,
var imageFile: File? = null,
var imageVector: FloatArray? = null,
var isShowCamera: Boolean = false,
var isFinish:Boolean = false,
var uploadSuccess:Boolean = false
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FoodCollectionBean
if (isShowCamera != other.isShowCamera) return false
if (isFinish != other.isFinish) return false
if (uploadSuccess != other.uploadSuccess) return false
if (imageUri != other.imageUri) return false
if (bitmap != other.bitmap) return false
if (imageFile != other.imageFile) return false
if (!imageVector.contentEquals(other.imageVector)) return false
return true
}
override fun hashCode(): Int {
var result = isShowCamera.hashCode()
result = 31 * result + isFinish.hashCode()
result = 31 * result + uploadSuccess.hashCode()
result = 31 * result + (imageUri?.hashCode() ?: 0)
result = 31 * result + (bitmap?.hashCode() ?: 0)
result = 31 * result + (imageFile?.hashCode() ?: 0)
result = 31 * result + (imageVector?.contentHashCode() ?: 0)
return result
}
}
@@ -1,4 +1,4 @@
package com.shuwei.dish.match.entity
package com.shuwei.dish.match.model
import java.io.Serializable
@@ -13,7 +13,7 @@ data class FoodRecord(
var cookMode: Int = 0,
//1-早餐,2-午餐,3-晚餐
var dinnerType: String = "0",
//true-原始数据,false-非原始数据
var isOriginalData: Boolean = true,
var sort: Int = 0
) : Serializable
@@ -0,0 +1,83 @@
package com.shuwei.dish.match.model
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.db.entity.SeasoningEntity
import java.io.Serializable
/**
* 物品信息接口返回的数据类(DTO),不依赖 Room,同时承载 UI 状态字段
* 对应接口:queryGoodsInfoList,食材(goodsType=0)和调料(goodsType=1)共用此结构
*/
data class GoodsItem(
// 接口返回字段
var goodsId: String = "",
val goodsName: String? = null,
val popularName: String? = null,
val zjmCode: String? = null,
var materId: String? = null,
val materCode: String? = null,
// 从 CookFoodGoodsEntity 迁移的数据字段(DishPartAdapter 显示及转换用)
var materialType: Int = 0,
var useWeight: Double? = 0.0,
var relateionType: Int = 0,
var allEdible: Boolean = true,
var goodsOrRelationCode: String? = "",
// 从 CookFoodGoodsEntity @Ignore 迁移的 UI 状态字段
var isSetFinished: Boolean = false,
// var isItemClicked: Boolean = false,
var isSamplingPage: Boolean = false,
var isNewDishType: Boolean = false,
var isClicked: Boolean = false,
// true-原始数据,false-非原始数据(识图新增)
var isOriginalData: Boolean = true,
// 识图匹配分数,-1 表示不显示
var foodScore: Int = -1,
// 净材种类(当前选中值)
var rawMaterialsType: String? = null,
// 净材种类候选列表(临时字段,逗号分隔,不持久化)
var rawMaterialsTypes: String? = null,
var oldGoodsId: String? = null
) : Serializable {
/**
* 物料编码,与查询接口返回的 materCode 同值,统一对外暴露为 goodsCode 供下游使用
*/
val goodsCode: String? get() = materCode
/**
* 转换为 CookFoodGoodsEntity,用于写入数据库或传给 SubmitFoodActivity
*/
fun toCookFoodGoodsEntity(): CookFoodGoodsEntity = CookFoodGoodsEntity(
goodsId = goodsId,
goodsName = goodsName,
popularName = popularName,
zjmCode = zjmCode,
materId = materId,
goodsCode = goodsCode,
materialType = materialType,
useWeight = useWeight,
relateionType = relateionType,
allEdible = allEdible,
goodsOrRelationCode = goodsOrRelationCode,
rawMaterialsType = rawMaterialsType
)
/**
* 转换为 SeasoningEntity,用于保存调料完整信息到 dm_seasoning
*/
fun toSeasoningEntity(): SeasoningEntity = SeasoningEntity(
goodsId = goodsId,
goodsName = goodsName,
popularName = popularName,
zjmCode = zjmCode,
materId = materId,
goodsCode = goodsCode,
materialType = materialType,
relateionType = relateionType,
allEdible = allEdible,
goodsOrRelationCode = goodsOrRelationCode,
rawMaterialsType = rawMaterialsType
)
}
@@ -0,0 +1,12 @@
package com.shuwei.dish.match.model
/**
* 物品名称批量查询入参 DTO
* 用于 queryGoodsInfoList 接口 goodsNames 字段的元素类型
* @param goodsName 物品名称
* @param rawMaterialsType 净材种类
*/
data class GoodsNameQueryDTO(
val goodsName: String,
val rawMaterialsType: String
)
@@ -0,0 +1,40 @@
package com.shuwei.dish.match.model
/**
* 设置页面的列表项实体类
*
* @param type 菜单项类型,用于唯一标识每一项
* @param title 主标题文字
* @param subtitle 副标题文字,仅部分项目(如菜品模式)有值
* @param isHidden 是否为隐藏项,隐藏项需触发开发者模式后才显示
* @param onClick 点击回调,初始化时直接注入对应的业务逻辑
*/
data class SettingItem(
val type: Type,
val title: String,
val subtitle: String? = null,
val isHidden: Boolean = false,
val onClick: () -> Unit
) {
/** 设置菜单项类型枚举 */
enum class Type {
/** 菜品模式切换 */
COOK_MODE,
/** 调料区设置 */
SEASONING_CONFIG,
/** 食材采集 */
FOOD_COLLECT,
/** 减重配置 */
WEIGHT_CONFIG,
/** 数据库查看(隐藏项) */
DB_INSPECT,
/** 秤数据监控(隐藏项) */
SCALE_OBSERVE,
/** 切换环境(隐藏项) */
ENV_SWITCH,
/** 数据清除(隐藏项) */
CLEAR_DATA,
/** 测试调料拿取(隐藏项) */
TEST_SEASONING,
}
}
@@ -0,0 +1,76 @@
package com.shuwei.dish.match.net
import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.model.CookFoodDTO
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.model.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
interface ApiService {
/**
* 查询菜品详情
*/
@GET
suspend fun getFoodDetail(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/getConstituteByFoodId",
@Query("foodId") foodId: String
): ApiResponse<CookFoodDTO?>
/**
* 提交制作菜品
*/
@POST
suspend fun submitCookFood(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/saveConstitute",
@Body param: CookFoodDTO
): ApiResponse<Any?>
/**
* 搜索菜品
*/
@POST
suspend fun searchFoodList(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryPageList",
@Body param: MutableMap<String, Any>
): ApiResponse<MutableList<FoodRecord>?>
/**
* 采样数据
*/
@POST
suspend fun getSamplingList(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryHistoryGoodsInfoList",
@Body param: MutableMap<String, Any>
): ApiResponse<MutableList<FoodRecord>?>
/**
* 物品信息(食材 goodsType=0,调料 goodsType=1 共用此接口)
*/
@POST
suspend fun queryGoodsList(
@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: MutableMap<String, RequestBody>,
@Part foodPics: List<MultipartBody.Part>
): ApiResponse<List<String>?>
}
@@ -0,0 +1,34 @@
package com.shuwei.dish.match.net
import java.io.IOException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
// 1. 网络异常类型枚举
enum class ErrorType {
NETWORK_ERROR, // 网络错误(无网)
TIMEOUT_ERROR, // 超时
SERVER_ERROR, // 服务器错误(5xx
CLIENT_ERROR, // 客户端错误(4xx
PARSE_ERROR, // 数据解析错误
UNKNOWN_ERROR // 未知错误
}
// 2. 自定义网络异常类
class ApiException(
val errorType: ErrorType,
val errorCode: Int = -1,
val errorMsg: String,
val throwable: Throwable? = null
) : Exception(errorMsg, throwable)
/** 将任意异常转换为 ApiException,供 safeApiCall 统一使用 */
fun getApiException(e: Exception): ApiException = when (e) {
is ApiException -> e
is UnknownHostException -> ApiException(ErrorType.NETWORK_ERROR, errorMsg = "网络未连接,请检查网络")
is SocketTimeoutException -> ApiException(ErrorType.TIMEOUT_ERROR, errorMsg = "请求超时,请稍后重试")
is ConnectException -> ApiException(ErrorType.TIMEOUT_ERROR, errorMsg = "连接超时,请检查网络")
is IOException -> ApiException(ErrorType.NETWORK_ERROR, errorMsg = "网络异常:${e.message ?: "未知IO错误"}")
else -> ApiException(ErrorType.UNKNOWN_ERROR, errorMsg = "未知错误:${e.message ?: "未知"}", throwable = e)
}
@@ -0,0 +1,40 @@
//package com.shuwei.dish.match.net
//
//import android.annotation.SuppressLint
//import okhttp3.Interceptor
//import okhttp3.Response
//import org.apache.http.conn.ConnectTimeoutException
//import java.io.IOException
//import java.net.SocketTimeoutException
//import java.net.UnknownHostException
//
//class ExceptionInterceptor : Interceptor {
// @SuppressLint("SuspiciousIndentation")
// override fun intercept(chain: Interceptor.Chain): Response {
// val request = chain.request()
// try {
// val response = chain.proceed(request)
// // 拦截 HTTP 状态码异常(4xx/5xx
// if (!response.isSuccessful) {
// val e = ApiException(
// errorType = if (response.code >= 500) ErrorType.SERVER_ERROR else ErrorType.CLIENT_ERROR,
// errorCode = response.code,
// errorMsg = "HTTP错误:${response.code}"
// )
// throw e
// }
// return response
// } catch (e: Exception) {
// // 捕获各类网络异常并转换为自定义异常
// val apiException = when (e) {
// is UnknownHostException -> ApiException(ErrorType.NETWORK_ERROR, errorMsg = "网络未连接,请检查网络")
// is SocketTimeoutException -> ApiException(ErrorType.TIMEOUT_ERROR, errorMsg = "请求超时,请稍后重试")
// is ConnectTimeoutException -> ApiException(ErrorType.TIMEOUT_ERROR, errorMsg = "连接超时,请检查网络")
// is IOException -> ApiException(ErrorType.NETWORK_ERROR, errorMsg = "网络异常:${e.message ?: "未知IO错误"}")
// is ApiException -> e // 已转换的异常直接抛出
// else -> ApiException(ErrorType.UNKNOWN_ERROR, errorMsg = "未知错误:${e.message ?: "未知"}", throwable = e)
// }
// throw apiException
// }
// }
//}
@@ -1,20 +1,38 @@
package com.shuwei.dish.match.http
package com.shuwei.dish.match.net
import android.annotation.SuppressLint
import android.util.Log
import okhttp3.Call
import okhttp3.Interceptor
import com.shuwei.dish.match.base.GlobalData
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.net.ssl.*
import java.security.SecureRandom
import java.security.cert.X509Certificate
import kotlin.apply
import kotlin.jvm.java
class HttpClient private constructor() {
private val client: OkHttpClient by lazy {
val apiService: ApiService = Retrofit.Builder()
.baseUrl(GlobalData.appBaseUrl)
// .baseUrl(UrlConfig.DEVICE_BASE_URL)
.client(HttpManager.instance.client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
//val apiService2: ApiService2 = Retrofit.Builder()
// .baseUrl(UrlConfig.BASE_URL)
//// .baseUrl(UrlConfig.DEVICE_BASE_URL)
// .client(HttpManager.instance.client)
// .addConverterFactory(GsonConverterFactory.create())
// .build()
// .create(ApiService2::class.java)
class HttpManager private constructor() {
val client: OkHttpClient by lazy {
OkHttpClient.Builder()
.apply {
connectTimeout(15, TimeUnit.SECONDS)
@@ -22,40 +40,32 @@ class HttpClient private constructor() {
writeTimeout(15, TimeUnit.SECONDS)
sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
hostnameVerifier { _, _ -> true }
//if (BuildConfig.Debug) {
addInterceptor(LoggingInterceptor())
// }
addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Log.d("HttpManager", "okhttp logger ==>${it}")
}).also {
it.level = HttpLoggingInterceptor.Level.BODY
})
addInterceptor(RequestInterceptor())
// addInterceptor(ExceptionInterceptor())
}
.build()
}
companion object {
val instance by lazy { HttpClient() }
}
fun newCall(request: Request): Call = client.newCall(request)
inner class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 打印请求日志
Log.d("OkHttp", "--> ${request.method} ${request.url}")
val response = chain.proceed(request)
// 打印响应日志
Log.d("OkHttp", "<-- ${response.code} ${response.request.url}")
return response
}
val instance by lazy { HttpManager() }
}
// 信任所有证书的TrustManager实现
@SuppressLint("CustomX509TrustManager")
class TrustAllCerts : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
@@ -0,0 +1,258 @@
package com.shuwei.dish.match.net
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.model.CookFoodDTO
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.model.GoodsNameQueryDTO
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.asSharedFlow
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
* @param repository 网络数据仓库,默认使用单例实例
*/
class NetViewModel(
private val repository: RemoteRepository = RemoteRepository()
) : ViewModel() {
/**
* 提交制作菜品的 UI 状态流,UI 层通过 collect 监听
*/
private val _submitCookFoodState = MutableStateFlow<UiState<Any?>>(UiState.Idle)
val submitCookFoodState: StateFlow<UiState<Any?>> = _submitCookFoodState.asStateFlow()
/**
* 提交制作菜品
*/
fun submitCookFood(entity: CookFoodDTO) {
viewModelScope.launch {
_submitCookFoodState.value = UiState.Loading
_submitCookFoodState.value = repository.submitCookFood(entity)
}
}
/**
* 查询菜品详情的 UI 状态流,UI 层通过 collect 监听
*/
private val _foodDetailState = MutableStateFlow<UiState<CookFoodDTO?>>(UiState.Idle)
val foodDetailState: StateFlow<UiState<CookFoodDTO?>> = _foodDetailState.asStateFlow()
/**
* 查询菜品详情
*/
fun getFoodDetail(foodId: String) {
viewModelScope.launch {
_foodDetailState.value = UiState.Loading
_foodDetailState.value = repository.getFoodDetail(foodId)
}
}
fun resetFoodDetailState() {
_foodDetailState.value = UiState.Idle
}
/**
* 搜索菜品列表的 UI 状态流。
* 使用 replay=1 保证 Fragment 重建后能收到最近一次结果;
* emit 时包一层 Event wrapper 打破 equals 去重,确保相同数据也能触发 collect。
*/
private val _searchFoodState = MutableSharedFlow<UiState<MutableList<FoodRecord>?>>(
replay = 1,
extraBufferCapacity = 1
)
val searchFoodState: SharedFlow<UiState<MutableList<FoodRecord>?>> = _searchFoodState.asSharedFlow()
/**
* 搜索菜品列表
*/
fun searchFoodList(param: MutableMap<String, Any>) {
viewModelScope.launch {
// pageNum=1 时先 emit Idle 重置 replay 缓存,防止相同数据导致 SharedFlow 去重不触发
if (param["pageNum"] == 1) {
_searchFoodState.emit(UiState.Idle)
}
_searchFoodState.emit(UiState.Loading)
_searchFoodState.emit(repository.searchFoodList(param))
}
}
/**
* 搜索菜品列表(回调版本),结果通过 onResult 直接回调,不经过 StateFlow/SharedFlow
* 适合需要每次都能触发的场景(下拉刷新、上拉加载更多)
*/
fun searchFoodListWithCallback(
param: MutableMap<String, Any>,
onLoading: () -> Unit = {},
onResult: (UiState<MutableList<FoodRecord>?>) -> Unit
) {
viewModelScope.launch {
onLoading()
onResult(repository.searchFoodList(param))
}
}
/**
*/
private val _samplingListState = MutableStateFlow<UiState<MutableList<FoodRecord>?>>(UiState.Idle)
val samplingListState: StateFlow<UiState<MutableList<FoodRecord>?>> = _samplingListState.asStateFlow()
fun resetSamplingListState() {
_samplingListState.value = UiState.Idle
}
/**
* 查询采样数据列表
*/
fun getSamplingList(param: MutableMap<String, Any>) {
viewModelScope.launch {
_samplingListState.value = UiState.Loading
_samplingListState.value = repository.getSamplingList(param)
}
}
/**
* 查询采样数据列表(回调版本),结果通过 onResult 直接回调,不经过 StateFlow
* 适合不需要跨生命周期持续监听的一次性查询场景
*/
fun getSamplingListWithCallback(
param: MutableMap<String, Any>,
onLoading: () -> Unit = {},
onResult: (UiState<MutableList<FoodRecord>?>) -> Unit
) {
viewModelScope.launch {
onLoading()
onResult(repository.getSamplingList(param))
}
}
/**
* 查询物品信息列表的 UI 状态流(食材/调料共用),UI 层通过 collect 监听
*/
private val _goodsListState = MutableStateFlow<UiState<MutableList<GoodsItem>?>>(UiState.Idle)
val goodsListState: StateFlow<UiState<MutableList<GoodsItem>?>> = _goodsListState.asStateFlow()
/**
* 食材搜索弹窗专用的 UI 状态流,与 goodsListState 隔离,避免弹窗请求影响其他页面
*/
private val _foodSearchGoodsListState = MutableStateFlow<UiState<MutableList<GoodsItem>?>>(UiState.Idle)
val foodSearchGoodsListState: StateFlow<UiState<MutableList<GoodsItem>?>> = _foodSearchGoodsListState.asStateFlow()
fun resetGoodsListState() {
_goodsListState.value = UiState.Idle
}
/**
* 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用)
* @param goodsType 物品类型:0=食材,1=调料
* @param placeId 场所 ID,默认取当前食堂 ID
* @param pageNum 页码,默认第 1 页
* @param pageSize 每页条数,默认 50
* @param goodsName 按名称模糊搜索,为 null 时不传该字段
* @param goodsNames 按名称+净材种类列表批量查询,为 null 时不传该字段
*/
fun queryGoodsList(
goodsType: String,
placeId: String = BaseApp.canteenId,
pageNum: Int = 1,
pageSize: Int = 50,
goodsName: String? = null,
goodsNames: List<GoodsNameQueryDTO>? = null
) {
val param = mutableMapOf<String, Any>(
"goodsType" to goodsType,
"placeId" to placeId,
"pageNum" to pageNum,
"pageSize" to pageSize
)
goodsName?.let { param["goodsName"] = it }
goodsNames?.let { param["goodsNames"] = it }
viewModelScope.launch {
_goodsListState.value = UiState.Loading
_goodsListState.value = repository.queryGoodsList(param)
}
}
/**
* 食材搜索弹窗专用的查询方法,结果写入 foodSearchGoodsListState,不影响 goodsListState
* @param goodsType 物品类型:0=食材,1=调料
* @param placeId 场所 ID,默认取当前食堂 ID
* @param pageNum 页码,默认第 1 页
* @param pageSize 每页条数,默认 50
* @param goodsName 按名称模糊搜索,为 null 时不传该字段
* @param goodsNames 按名称+净材种类列表批量查询,为 null 时不传该字段
*/
fun queryFoodSearchGoodsList(
goodsType: String,
placeId: String = BaseApp.canteenId,
pageNum: Int = 1,
pageSize: Int = 50,
goodsName: String? = null,
goodsNames: List<GoodsNameQueryDTO>? = null
) {
val param = mutableMapOf<String, Any>(
"goodsType" to goodsType,
"placeId" to placeId,
"pageNum" to pageNum,
"pageSize" to pageSize
)
goodsName?.let { param["goodsName"] = it }
goodsNames?.let { param["goodsNames"] = it }
viewModelScope.launch {
_foodSearchGoodsListState.value = UiState.Loading
_foodSearchGoodsListState.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 ->
//multipart/form-data image/jpeg
file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
.let { MultipartBody.Part.createFormData("foodPics", file.name, it) }
}
return repository.uploadFoodVectorData(params, fileParts)
}
}
@@ -0,0 +1,53 @@
package com.shuwei.dish.match.net
import com.shuwei.dish.match.model.CookFoodDTO
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.model.GoodsItem
import okhttp3.MultipartBody
import okhttp3.RequestBody
/**
* 网络数据仓库,封装所有 ApiService 调用
* ViewModel 不直接接触 apiService,统一通过此类发起网络请求
*/
class RemoteRepository {
/** 统一封装 API 调用:处理响应映射和异常转换,返回 UiState */
private suspend fun <T> safeApiCall(call: suspend () -> ApiResponse<T>): UiState<T?> {
return try {
val resp = call()
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
UiState.Error("-1", getApiException(e).errorMsg)
}
}
/** 提交制作菜品 */
suspend fun submitCookFood(entity: CookFoodDTO): UiState<Any?> =
safeApiCall { apiService.submitCookFood(param = entity) }
/** 查询菜品详情 */
suspend fun getFoodDetail(foodId: String): UiState<CookFoodDTO?> =
safeApiCall { apiService.getFoodDetail(foodId = foodId) }
/** 搜索菜品列表 */
suspend fun searchFoodList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> =
safeApiCall { apiService.searchFoodList(param = param) }
/** 查询采样数据列表 */
suspend fun getSamplingList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> =
safeApiCall { apiService.getSamplingList(param = param) }
/** 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用) */
suspend fun queryGoodsList(param: MutableMap<String, Any>): UiState<MutableList<GoodsItem>?> =
safeApiCall { apiService.queryGoodsList(param = param) }
/** 上传采集数据 */
suspend fun uploadFoodVectorData(
params: MutableMap<String, RequestBody>,
foodPics: List<MultipartBody.Part>
): UiState<List<String>?> =
safeApiCall { apiService.uploadFoodVectorData(params = params, foodPics = foodPics) }
}
@@ -0,0 +1,22 @@
package com.shuwei.dish.match.net
import com.shuwei.dish.match.base.GlobalData
import okhttp3.Interceptor
import okhttp3.Response
/**
* 请求拦截器
*/
class RequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("X-Access-Token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiJiZTE1NDgzMS0zNDY2LTNiYTItYTJlYS01NzY1MmM5MTlmZWQiLCJ0eXBlIjoiNCIsInVzZXJJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDEifQ.sN40cOC-O5WQFrF4IDUs8fFlkNdUKLbJt_rHyTsgYYM")
.header("X-DEVICE-CODE", GlobalData.deviceId)
.header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d")
val newRequest = requestBuilder.build()
return chain.proceed(newRequest)
}
}
@@ -0,0 +1,9 @@
package com.shuwei.dish.match.net
data class ApiResponse<T>(
val code: String,
val msg: String? = "",
val data: T? = null
) {
fun isSuccess() = code == "00000"
}
@@ -0,0 +1,15 @@
package com.shuwei.dish.match.net
/**
* 统一 UI 状态模型,用于 ViewModel 向 UI 层传递请求状态
* Idle — 初始状态,未发起请求
* Loading — 请求进行中
* Success — 请求成功,携带数据
* Error — 请求失败,携带错误码和错误信息
*/
sealed class UiState<out T> {
object Idle : UiState<Nothing>()
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val code: String, val msg: String) : UiState<Nothing>()
}
@@ -9,22 +9,14 @@ import android.text.style.LineHeightSpan
import android.text.style.StyleSpan
import android.util.Log
import android.util.SparseArray
import android.util.SparseIntArray
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.graphics.toColorInt
import androidx.core.view.forEach
import androidx.lifecycle.ViewModelProvider
import androidx.core.content.ContextCompat
import com.shuwei.dish.match.R
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.ResetReasoningRecord
import com.shuwei.dish.match.db.entity.SeasoningEntity
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.JsonAssetsLoader
import com.shuwei.dish.match.utils.MultiClickDetector
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
@@ -32,13 +24,11 @@ import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.TextCellAdapter
import com.shuwei.dish.match.databinding.ActivityDeviceConfigBinding
import com.shuwei.dish.match.dialog.BottomDialog2
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.TextCellTool
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
@@ -64,9 +54,8 @@ class DeviceConfigActivity : BaseActivity() {
private lateinit var binding: ActivityDeviceConfigBinding
private lateinit var appViewModel: AppViewModel
private var cookMode: Int = 0
// private val weightArray = SparseIntArray()
private val weightArray = SparseArray<Double>()
private val addressArray = AddressUtil.getWeighAddressArray()
@@ -76,7 +65,6 @@ class DeviceConfigActivity : BaseActivity() {
binding = ActivityDeviceConfigBinding.inflate(layoutInflater)
setContentView(binding.root)
cookMode = intent.getIntExtra(COOK_MODE, 0)
initViewModel()
setHeaderBackground()
setTitleBar(titleBarAction = {
it.visible()
@@ -84,13 +72,6 @@ class DeviceConfigActivity : BaseActivity() {
it.text = "设备配置"
}, rightIconActon = {
it.gone()
// it.alpha = 0.0F
// it.setImageResource(R.drawable.ic_setting)
// it.setOnClickListener {_->
// detector.setOnDelayedMultiClickListener(it) {
// defaultDataSettingDialog()
// }
// }
})
loadQualitySpan(isEnable = false)
@@ -148,127 +129,80 @@ class DeviceConfigActivity : BaseActivity() {
weightArray.put(address, weight)
val item = seasoningItems.firstOrNull { address == addressArray[it.sort] }
item?.let {
it.useWeight = weight.toDouble()
it.useWeight = weight
updateGridData(it)
// 根据是否量程溢出切换格子背景色
val frameLayout = binding.include.root.findViewWithTag<FrameLayout>(it.sort.toString())
frameLayout?.background = if (state == WeightUtil.STATE_OVER_WEIGHT) {
ContextCompat.getDrawable(this, R.drawable.shape_red_10_corners)
} else {
ContextCompat.getDrawable(this, R.drawable.selector_text_cell)
}
}
Log.d(TAG, "addWeighListener: update-----------------------")
})
}
private var seasoningItems = mutableListOf<SeasoningEntity>()
private fun addGridItemListener() {
binding.include.root.forEach { child ->
child.clickWithDebounce {
val sort = child.tag.toString().toInt()
val entity = seasoningItems.firstOrNull { it.sort == sort } ?: SeasoningEntity()
clickGridItem(sort, entity)
}
}
}
val detector = MultiClickDetector(targetCount = 10, intervalMs = 800)
private fun defaultDataSettingDialog() {
val remindBinding = LayoutFoodRemindBinding.inflate(layoutInflater)
remindBinding.tvDialogTitle.text = "温馨提示"
remindBinding.tvDialogContent.text =
"您好,使用默认配置作为您的调料数据,则已有配置将被清除,确认吗?"
CommonDialog(this).apply {
messageView = remindBinding.root
leftText = "取消"
rightText = "确认"
onLeftClick = { dismiss() }
onRightClick = {
dismiss()
resetList()
}
onDismiss = { hideStatusBar() }
}.show()
}
private fun resetList() {
runCatching {
val record = JsonAssetsLoader.parseJsonFromAssets<ResetReasoningRecord>(
this,
"default_seasoning_list.json"
)
// val weightArray = WeightUtil.weightArray
appViewModel.clearAllSeasoning {
record?.list?.forEach { entity ->
entity.useWeight = weightArray[addressArray[entity.sort]].toDouble()
appViewModel.saveSeasoning(entity) {
updateGridData(entity)
}
}
// loadSeasoning()
}
}.onFailure { it.printStackTrace() }
}
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao()))
appViewModel =
ViewModelProvider(this, factory)[AppViewModel::class.java]
}
private fun loadSeasoning() {
appViewModel.loadSeasoning {
seasoningItems.clear()
seasoningItems.addAll(it)
initConfigData()
setGridData(seasoningItems)
}
}
private fun initConfigData() {
repeat(12) { num ->
val firstOne = seasoningItems.firstOrNull { it.sort == num }
if (firstOne == null) {
seasoningItems.add(SeasoningEntity().also { it.sort = num })
}
}
}
private fun setGridData(list: MutableList<SeasoningEntity>) {
val gridLayout = binding.include.root
list.forEach { entity ->
val tag = entity.sort.toString()
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout, entity)
}
}
private fun updateGridData(entity: SeasoningEntity) {
val gridLayout = binding.include.root
val tag = entity.sort.toString()
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout, entity)
TextCellTool.loadLayout(frameLayout, entity)
}
private fun addGridItemListener() {
binding.include.root.forEach { child ->
child.clickWithDebounce {
val sort = child.tag.toString().toInt()
val entity = seasoningItems.firstOrNull { it.sort == sort }
?: SeasoningEntity().also { it.sort = sort }
clickGridItem(sort, entity)
}
}
}
private fun loadSeasoning() {
Log.d(TAG, "loadSeasoning,start-----------------------")
appViewModel.loadSeasoning { list ->
Log.d(TAG, "loadSeasoning,list.size=${list.size}-----------------------")
seasoningItems.clear()
// 补全缺失的格子:对 0~11 中没有对应 sort 的位置,追加空实体
val existingSorts = list.map { it.sort }.toSet()
for (index in 0 until 12) {
if (index !in existingSorts) {
list.add(SeasoningEntity().also { it.sort = index })
}
}
seasoningItems.addAll(list)
// 按 sort 字段找到对应 FrameLayout,填充数据
list.forEach { entity ->
val tag = entity.sort.toString()
val frameLayout = binding.include.root.findViewWithTag<FrameLayout>(tag)
frameLayout?.let { TextCellTool.loadLayout(it, entity) }
}
}
}
private fun clickGridItem(sort: Int, entity: SeasoningEntity) {
val currentAddress = AddressUtil.getWeighAddressArray().get(sort)
// val seasoningEntity = list.first{it.sort == sort}
BottomDialog2(
weighIndex = sort,
weighAddress = currentAddress,
clickName = entity.goodsName
).show(this) { item ->
syncItem(entity, item)
//3-调料
entity.materialType = 3
entity.sort = sort
saveSeasoning(entity) {
val tag = sort.toString()
val gridLayout = binding.include.root
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout, entity)
val frameLayout = binding.include.root.findViewWithTag<FrameLayout>(sort.toString())
frameLayout?.let { TextCellTool.loadLayout(it, entity) }
}
}
}
private fun syncItem(oldItem: SeasoningEntity, newItem: SeasoningEntity) {
private fun syncItem(oldItem: SeasoningEntity, newItem: GoodsItem) {
oldItem.run {
goodsId = newItem.goodsId
goodsName = newItem.goodsName
@@ -278,11 +212,6 @@ class DeviceConfigActivity : BaseActivity() {
allEdible = newItem.allEdible
useWeight = newItem.useWeight
popularName = newItem.popularName
canteenId = newItem.canteenId
relateionType_dictText = newItem.relateionType_dictText
foodId = newItem.foodId
sort = newItem.sort
}
}
@@ -5,20 +5,18 @@ import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.DishPartAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityDishSamplingBinding
import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.BottomDialog
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.WeightUtil
@@ -29,8 +27,6 @@ import com.shuwei.dish.match.utils.ext.startActivity
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.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import java.io.Serializable
class DishSamplingActivity : BaseActivity() {
@@ -58,7 +54,6 @@ class DishSamplingActivity : BaseActivity() {
setTitleBar()
setHeaderBackground()
addViewClickListener()
initViewModel()
foodName = intent.getStringExtra(FOOD_NAME)
binding.etInputDish.setText(foodName)
@@ -92,16 +87,6 @@ class DishSamplingActivity : BaseActivity() {
})
}
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao()))
appViewModel =
ViewModelProvider(this, factory)[AppViewModel::class.java]
}
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
appViewModel.getCookFoodList(cookMode = 1) { action(it) }
}
@@ -142,9 +127,9 @@ class DishSamplingActivity : BaseActivity() {
}
val list: MutableList<CookFoodGoodsEntity> = mutableListOf()
val list: MutableList<GoodsItem> = mutableListOf()
private var tempDishEntity: CookFoodGoodsEntity? = null
private var tempDishEntity: GoodsItem? = null
private var dishMaterialType = 1
@@ -236,7 +221,7 @@ class DishSamplingActivity : BaseActivity() {
Log.d(TAG, "goToSubmit: goodsList:${list.toJsonString()}")
// startActivity<SubmitDishActivity> {
startActivity<SubmitFoodActivity> {
putExtra(SubmitFoodActivity.GOODS_LIST, list as java.io.Serializable)
putExtra(SubmitFoodActivity.GOODS_LIST, list.map { it.toCookFoodGoodsEntity() }.toMutableList() as java.io.Serializable)
putExtra(SubmitFoodActivity.FOOD_ITEM, food as Serializable)
}
}
@@ -276,16 +261,7 @@ class DishSamplingActivity : BaseActivity() {
setText(item.goodsName)
setSelection(length())
}
tempDishEntity = CookFoodGoodsEntity().apply {
goodsId = item.goodsId
goodsName = item.goodsName
relateionType = item.relateionType ?: 0
goodsOrRelationCode = item.goodsOrRelationCode
// TODO: 待定
//allEdible = item.allEdible
}
tempDishEntity = item
}
}
@@ -12,16 +12,12 @@ import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityFoodSearchBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.FoodRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.model.FoodRecord
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.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import java.io.Serializable
@@ -146,60 +142,53 @@ class FoodSearchActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged")
private fun queryListInfo(input: String) {
showLoading()
val map = mutableMapOf(
val param = mutableMapOf<String, Any>(
"foodName" to input.trim(),
"pageNo" to "$pageNo",
"pageSize" to "$pageSize",
"pageNo" to pageNo,
"pageSize" to pageSize,
"canteenId" to BaseApp.canteenId
)
if (dinnerType.toInt() > 0) {
map["dinnerType"] = getDinnerTypeText()
param["dinnerType"] = getDinnerTypeText()
}
val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
append("?")
map.forEach { (key, value) -> append("$key=$value&") }
}
sb.deleteCharAt(sb.length - 1)
HttpUtil.get(
url = sb.toString(),
doSuccess = {
netViewModel.searchFoodListWithCallback(
param = param,
onResult = { state ->
when (state) {
is UiState.Success -> {
delayDismissLoading()
finishRefresh()
binding.refreshLayout.run {
setEnableRefresh(true)
}
val json = it.toJsonString()
val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
// toast("暂未搜索到相关菜品信息")
binding.refreshLayout.setEnableRefresh(true)
val records = state.data
if (records.isNullOrEmpty()) {
if (pageNo == 1) {
loadEmptyView()
}
return@get
return@searchFoodListWithCallback
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(records)
recordAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
},
doFailure = { code, msg ->
binding.refreshLayout.run {
setEnableRefresh(true)
}
toast(msg)
is UiState.Error -> {
binding.refreshLayout.setEnableRefresh(true)
toast(state.msg)
delayDismissLoading()
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
}
else -> {}
}
}
)
}
private fun finishRefresh() {
@@ -2,22 +2,18 @@ package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.HomeModeAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityHomeBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.entity.HomeModeBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.model.HomeModeBean
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
class HomeActivity : BaseActivity() {
@@ -33,7 +29,6 @@ class HomeActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
statusBarDarkFont(enable = true)
initViewModel()
// // 获取 ANDROID_ID
// val deviceId =
// Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
@@ -43,7 +38,7 @@ class HomeActivity : BaseActivity() {
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
setHeaderBackground(isHomePage = true)
binding.radioGroup.setOnCheckedChangeListener { group, checkedId ->
binding.radioGroup.setOnCheckedChangeListener { _, checkedId ->
if (checkedId == R.id.radioSw) {
BaseApp.canteenId = "1678234139391512577"
} else {
@@ -134,14 +129,6 @@ class HomeActivity : BaseActivity() {
)
}
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
private fun goSampling() {
appViewModel.loadSeasoning {
if (it.isEmpty()) {
@@ -4,27 +4,19 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.shuwei.dish.match.BuildConfig
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityInitBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.utils.AppUtil
import com.shuwei.dish.match.utils.QRCodeUtil
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.invisible
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
@@ -36,8 +28,6 @@ class InitActivity : BaseActivity() {
}
private lateinit var binding: ActivityInitBinding
private lateinit var appViewModel: AppViewModel
@SuppressLint("HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -51,12 +41,14 @@ class InitActivity : BaseActivity() {
BaseApp.appVersion = AppUtil.getAppVersionCode(this).toString()
// 获取 ANDROID_ID
var androidId =
Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
androidId = "39a7abdd06b3c7ab"
BaseApp.deviceId = androidId
SpTool.put(SpTool.DEVICE_ID, androidId)
BaseApp.configUrl = UrlConfig.BASE_URL
// var androidId =
// Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
// androidId = "39a7abdd06b3c7ab"
val deviceId = if (BuildConfig.IS_TEST_DEVICE) GlobalData.DEVICE_ID_2 else AppUtil.getUDID(this)
GlobalData.deviceId = deviceId
SpTool.put(SpTool.DEVICE_ID, deviceId)
// GlobalData.appBaseUrl = GlobalData.TEST_BASE_URL
GlobalData.appBaseUrl = GlobalData.PROD_BASE_URL
BaseApp.canteenId = "0"
// // TODO: 以上保存deviceId用于临时使用,后续改为下面注释方式
@@ -82,22 +74,12 @@ class InitActivity : BaseActivity() {
binding.ivQrCode.invisible()
binding.btnInit.invisible()
initViewModel()
WeightUtil.init()
WeightUtil.getWeight()
WeightUtil.startContinuousRead()
HttpUtil.getAppToken()
HttpUtil.loopGetToken = true
countDown()
}
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
private fun startNextPage() {
val launchPageType = SpTool.getInt(SpTool.LAUNCH_PAGE_TYPE, -1)
when (launchPageType) {
@@ -157,68 +139,6 @@ class InitActivity : BaseActivity() {
}
}
private fun initConfig() {
binding.ivQrCode.setImageBitmap(
QRCodeUtil.generateQRCode(
content = BaseApp.deviceId?:"",
size = 200.dp
)
)
binding.btnInit.setOnClickListener {
HttpUtil.loopGetToken = false
getDeviceConfig()
}
}
private fun getDeviceConfig() {
val tokenUrl = "${UrlConfig.DEVICE_TOKEN}?qrcodeId=${BaseApp.deviceId}&appVersion=${BaseApp.appVersion}"
HttpUtil.get(url = tokenUrl, doSuccess = { token ->
Log.d(TAG, "initConfig: $token")
getConfig(token.toString())
}, doFailure = { code, msg ->
Log.d(TAG, "initConfig: $code,$msg")
})
}
private fun getConfig(token: String) {
val deviceConfigUrl = "${UrlConfig.DEVICE_CONFIG}?equipmentCode=${BaseApp.deviceId}&&appVersion=${BaseApp.appVersion}"
HttpUtil.get(url = deviceConfigUrl, header = mutableMapOf(
"X-Access-Token" to token
), doSuccess = {
Log.d(TAG, "getDeviceConfig: $it")
val data = it.toJsonString()
val checkResult = checkConfigData(data)
if (checkResult.not()) {
toast("初始化设备失败,请稍后重试")
return@get
}
HttpUtil.loopGetToken = true
SpTool.put(SpTool.DEVICE_CONFIG_CACHE, data)
startActivity<HomeActivity>()
// finish()
}, doFailure = { code, msg ->
Log.d(TAG, "getDeviceConfig: $code,$msg")
toast("初始化设备失败,请稍后重试,code=${code},msg=${msg}")
})
}
data class DeviceConfigBean(
var appPackageUrl:String? = null,
var canteenId:String? = null
)
private fun checkConfigData(data: String): Boolean {
if (data.isBlank()) {
return false
}
val config = data.toObject<DeviceConfigBean?>()
if (config == null) {
return false
}
BaseApp.configUrl = config.appPackageUrl?:""
BaseApp.canteenId = config.canteenId?:""
return true
}
override fun onDestroy() {
WeightUtil.stopContinuousRead()
super.onDestroy()
@@ -14,11 +14,15 @@ import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding
import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding
import com.shuwei.dish.match.dialog.BottomDialog
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.model.GoodsItem
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.model.CookFoodDTO
import com.shuwei.dish.match.model.FoodRecord
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.shuwei.dish.match.net.UiState
import kotlinx.coroutines.launch
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.WeightUtil
@@ -26,7 +30,6 @@ import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import java.io.Serializable
@@ -44,8 +47,6 @@ class PrepareCookActivity : BaseActivity() {
private var food: FoodRecord? = null
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPrepareCookBinding.inflate(layoutInflater)
@@ -69,6 +70,7 @@ class PrepareCookActivity : BaseActivity() {
food = intent.extras?.getSerializable(FOOD_ITEM) as FoodRecord?
binding.tvDishName.text = food?.foodName ?: ""
addViewClickListener()
initObserver()
getDishDetail()
}
@@ -96,7 +98,7 @@ class PrepareCookActivity : BaseActivity() {
return@setOnClickListener
}
}
val weight = binding.tvDishPartWeight.text.toString().toDouble()
val weight = binding.tvDishPartWeight.text.toString().toDoubleOrNull() ?:0.toDouble()
if (weight <= 0.toDouble()) {
toast("食材用量需要大于0")
return@setOnClickListener
@@ -115,12 +117,7 @@ class PrepareCookActivity : BaseActivity() {
toast("不允许重复添加同一食材")
return@show
}
list.add(CookFoodGoodsEntity().apply {
goodsId = item.goodsId
goodsName = item.goodsName
// materialType =
isNewDishType = true
})
list.add(GoodsItem(goodsId = item.goodsId, goodsName = item.goodsName, isNewDishType = true))
onItemClick(list.size - 1)
}
}
@@ -163,15 +160,8 @@ class PrepareCookActivity : BaseActivity() {
}
list.forEach {
if (it.isSetFinished) {
goodsList?.add(CookFoodGoodsEntity().apply {
foodId = food!!.foodId
goodsId = it.goodsId
relateionType = it.relateionType
allEdible = it.allEdible
goodsName = it.goodsName
materialType = it.materialType
useWeight = it.useWeight ?: 0.toDouble()
goodsOrRelationCode = it.goodsOrRelationCode
goodsList?.add(it.toCookFoodGoodsEntity().also { entity ->
entity.foodId = food!!.foodId
})
}
}
@@ -193,35 +183,45 @@ class PrepareCookActivity : BaseActivity() {
}
private fun getDishDetail() {
//649
val url = "${UrlConfig.DISH_DETAIL}?foodId=${food?.foodId}"
HttpUtil.get(
url = url,
doSuccess = {
val json = it.toJsonString()
Log.d(TAG, "getDishDetail: json:$json")
val detail: CookFoodEntity? = json.toObject<CookFoodEntity>()
private fun initObserver() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
netViewModel.foodDetailState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> {
dismissLoading()
netViewModel.resetFoodDetailState()
val detail = state.data
if (detail == null) {
toast("查询菜品信息为空")
return@get
return@collect
}
loadDishDetail(detail)
}) { code, msg ->
toast(msg)
}
is UiState.Error -> {
dismissLoading()
toast(state.msg)
}
else -> {}
}
}
}
}
}
private fun loadDishDetail(detail: CookFoodEntity) {
val voList = detail.stFoodInfoConstituteList
private fun getDishDetail() {
netViewModel.getFoodDetail(food?.foodId ?: "")
}
private fun loadDishDetail(detail: CookFoodDTO) {
val voList = detail.foodConstituteList
if (voList.isNullOrEmpty()) {
return
}
//筛选出主材和辅材
val tempDate = voList.filter { it.materialType == 1 || it.materialType == 2 }
.apply {
forEach { it.useWeight = 0.toDouble() }
}
.map { it.toGoodsItem().also { item -> item.useWeight = 0.0 } }
list.clear()
list.addAll(tempDate)
firstReqSize = list.size
@@ -229,7 +229,7 @@ class PrepareCookActivity : BaseActivity() {
}
private var firstReqSize = 0
private val list = mutableListOf<CookFoodGoodsEntity>()
private val list = mutableListOf<GoodsItem>()
private val dishPartAdapter by lazy {
DishPartAdapter(list).apply {
setOnItemClickListener { _, _, positon ->
@@ -247,8 +247,8 @@ class PrepareCookActivity : BaseActivity() {
}
binding.tvDishType.text =
if (it.materialType == 1) "主材" else if (it.materialType == 2) "辅材" else ""
list.forEach { bean -> bean.isItemClicked = false }
it.isItemClicked = true
list.forEach { bean -> bean.isClicked = false }
it.isClicked = true
dishPartAdapter.notifyDataSetChanged()
if (it.isNewDishType) {
@@ -6,7 +6,6 @@ import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
@@ -15,23 +14,17 @@ import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivitySamplingBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.DialogTool
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.FoodRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.DateTimeUtil
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import java.io.Serializable
class SamplingListActivity : BaseActivity() {
@@ -49,7 +42,6 @@ class SamplingListActivity : BaseActivity() {
setContentView(binding.root)
setTitleBar()
setHeaderBackground()
initViewModel()
initRecyclerView()
addViewListener()
requestData(true)
@@ -76,16 +68,6 @@ class SamplingListActivity : BaseActivity() {
}
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao()))
appViewModel =
ViewModelProvider(this, factory)[AppViewModel::class.java]
}
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
appViewModel.getCookFoodList(cookMode = 1) { action(it) }
}
@@ -204,33 +186,29 @@ class SamplingListActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged")
fun getSamplingList() {
showLoading()
val map = mapOf(
// "foodName" to input,
"pageNo" to "$pageNo",
"pageSize" to "$pageSize",
val param = mutableMapOf<String, Any>(
"pageNo" to pageNo,
"pageSize" to pageSize,
"canteenId" to BaseApp.canteenId
)
val sb = StringBuilder(UrlConfig.SAMPLING_LIST).apply {
append("?")
map.forEach { (key, value) -> append("$key=$value&") }
}
sb.deleteCharAt(sb.length - 1)
HttpUtil.get(
url = sb.toString(),
doSuccess = {
loadDishList(it)
}, doFailure = { code, msg ->
binding.refreshLayout.run {
setEnableRefresh(true)
}
toast(msg)
finishRefresh()
netViewModel.getSamplingListWithCallback(
param = param,
onResult = { state ->
when (state) {
is UiState.Success -> loadDishList(state.data)
is UiState.Error -> {
binding.refreshLayout.setEnableRefresh(true)
toast(state.msg)
finishRefresh()
delayDismissLoading()
if (pageNo == 1) {
loadEmptyView()
}
})
}
else -> {}
}
}
)
}
private fun finishRefresh() {
@@ -332,26 +310,20 @@ class SamplingListActivity : BaseActivity() {
}
@SuppressLint("NotifyDataSetChanged")
private fun loadDishList(data: Any) {
binding.refreshLayout.run {
setEnableRefresh(true)
}
private fun loadDishList(records: MutableList<FoodRecord>?) {
binding.refreshLayout.setEnableRefresh(true)
delayDismissLoading()
finishRefresh()
val json = data.toJsonString()
val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
//toast("暂未搜索到相关菜品信息")
if (records.isNullOrEmpty()) {
if (pageNo == 1) {
loadEmptyView()
}
return
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(records)
dishAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
@@ -5,14 +5,12 @@ import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.shuwei.dish.match.R
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivitySelectDishBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.DialogTool
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.ui.fragment.DishListFragment
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
@@ -20,8 +18,6 @@ import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
class SelectDishActivity : BaseActivity() {
@@ -32,8 +28,6 @@ class SelectDishActivity : BaseActivity() {
private lateinit var binding: ActivitySelectDishBinding
private lateinit var appViewModel: AppViewModel
private var dinnerType: String = "1"
private val fragmentList = mutableListOf<DishListFragment>().apply {
@@ -62,19 +56,10 @@ class SelectDishActivity : BaseActivity() {
}, backAction = {
it.gone()
})
initViewModel()
addViewListener()
loadFragment()
}
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao()))
appViewModel =
ViewModelProvider(this, factory)[AppViewModel::class.java]
}
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType) { action(it) }
}
@@ -6,24 +6,26 @@ import android.util.Log
import android.util.SparseArray
import android.util.SparseIntArray
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.util.forEach
import androidx.lifecycle.ViewModelProvider
import com.google.gson.reflect.TypeToken
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.TextCellAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivitySubmitFoodBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.db.entity.SeasoningEntity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.shuwei.dish.match.model.toDTO
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.TextCellTool
import kotlinx.coroutines.launch
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
@@ -33,8 +35,6 @@ import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toType
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
@SuppressLint("UseSparseArrays")
class SubmitFoodActivity : BaseActivity() {
@@ -69,7 +69,6 @@ class SubmitFoodActivity : BaseActivity() {
super.onCreate(savedInstanceState)
binding = ActivitySubmitFoodBinding.inflate(layoutInflater)
setContentView(binding.root)
initViewModel()
setHeaderBackground()
intent.extras?.apply {
food = getSerializable(FOOD_ITEM) as FoodRecord?
@@ -95,10 +94,11 @@ class SubmitFoodActivity : BaseActivity() {
}
})
addViewListener()
initObserver()
}
private fun loadSeasoningFromLocal() {
appViewModel.getCookFoodGoodsList(foodId = food!!.foodId!!, cookMode = food!!.cookMode) { it ->
appViewModel.getCookFoodGoodsList(foodId = food!!.foodId!!, cookMode = food!!.cookMode, dinnerType = food!!.dinnerType) { it ->
goodsList = mutableListOf()
val goodsIdList = mutableListOf<String>()
//设置主辅材数据
@@ -221,7 +221,7 @@ class SubmitFoodActivity : BaseActivity() {
val gridLayout = binding.include.root
val tag = entity.sort.toString()
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout , entity)
TextCellTool.loadLayout(frameLayout , entity)
}
private fun getCookingSeasoning(address: Int): Double {
@@ -265,7 +265,7 @@ class SubmitFoodActivity : BaseActivity() {
toast("未获取到调料信息")
return
}
if (realSeasoningData.isNotEmpty()&&realSeasoningData[0].goodsId.isNullOrBlank()) {
if (realSeasoningData.isNotEmpty()&&realSeasoningData[0].goodsId.isBlank()) {
toast("还未设置调料信息,请去设置页面操作")
return
}
@@ -316,7 +316,7 @@ class SubmitFoodActivity : BaseActivity() {
toast("未获取到调料信息")
return
}
if (realSeasoningData.isNotEmpty()&&realSeasoningData[0].goodsId.isNullOrBlank()) {
if (realSeasoningData.isNotEmpty()&&realSeasoningData[0].goodsId.isBlank()) {
toast("还未设置调料信息,请去设置页面操作")
return
}
@@ -335,7 +335,7 @@ class SubmitFoodActivity : BaseActivity() {
}
cookFoodEntity.let {
it.stFoodInfoConstituteList = this@SubmitFoodActivity.goodsList
it.matchingConstituteInfoList = this@SubmitFoodActivity.goodsList
it.dinnerType = when(it.dinnerType) {
"1" -> "早餐"
"2" -> "午餐"
@@ -350,37 +350,21 @@ class SubmitFoodActivity : BaseActivity() {
cookFoodEntity.foodId = ""
goodsList?.forEach { it.foodId = "" }
}
val json = cookFoodEntity.toJsonString()
Log.d(TAG, "submit: json=$json")
HttpUtil.postJson(
url = UrlConfig.SUBMIT_DISH,
json = json,
doSuccess = {
Log.d(TAG, "submit: postJson=$it")
submitSuccess(isSamplingData)
}, doFailure = { code, msg ->
Log.d(TAG, "submit: postJson:code=$code,msg=$msg")
toast(msg)
dismissLoading()
})
netViewModel.submitCookFood(entity = cookFoodEntity.toDTO())
}
private fun submitSuccess(isSamplingData: Boolean) {
if (food?.foodId.isNullOrBlank()) {
//本地无删除数据直接跳转页面
dismissLoading()
jumpPage(isSamplingData, false)
return
}
//删除本地数据
appViewModel.deleteCookFoodAndGoods(
if (food?.foodId.isNullOrBlank().not()) {
//接口提交成功后异步删除本地草稿
dbViewModel.markSubmittedAndDeleteAsync(
cookMode = food!!.cookMode,
foodId = food!!.foodId!!
) {
foodId = food!!.foodId!!,
dinnerType = food!!.dinnerType ?: "0"
)
}
dismissLoading()
jumpPage(isSamplingData, false)
}
}
private fun jumpPage(isSampling: Boolean, isCooking: Boolean) {
if (isSampling) {
@@ -395,14 +379,26 @@ class SubmitFoodActivity : BaseActivity() {
}
}
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao()))
appViewModel =
ViewModelProvider(this, factory)[AppViewModel::class.java]
private fun initObserver() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
netViewModel.submitCookFoodState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> {
Log.d(TAG, "submit: success")
submitSuccess(food?.cookMode == 1)
}
is UiState.Error -> {
Log.d(TAG, "submit: error code=${state.code}, msg=${state.msg}")
toast(state.msg)
dismissLoading()
}
else -> {}
}
}
}
}
}
override fun onResume() {
@@ -447,7 +443,7 @@ class SubmitFoodActivity : BaseActivity() {
list.forEach { entity ->
val tag = entity.sort.toString()
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout, entity)
TextCellTool.loadLayout(frameLayout, entity)
}
}
}
@@ -14,17 +14,13 @@ import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentDishListBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.FoodRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.model.FoodRecord
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.ui.PrepareCookActivity
import com.shuwei.dish.match.ui.SelectDishActivity
import com.shuwei.dish.match.ui.SubmitFoodActivity
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import java.io.Serializable
@@ -153,40 +149,35 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
@Suppress("unchecked_cast")
fun getDishList() {
activity.showLoading()
val map = mapOf(
// "foodName" to input,
val param = mutableMapOf<String, Any>(
"dinnerType" to getDinnerTypeText(),
"pageNo" to "$pageNo",
"pageSize" to "$pageSize",
"pageNo" to pageNo,
"pageSize" to pageSize,
"canteenId" to BaseApp.canteenId
)
val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
append("?")
map.forEach { (key, value) -> append("$key=$value&") }
}
sb.deleteCharAt(sb.length - 1)
HttpUtil.get(
url = sb.toString(),
doSuccess = {
loadDishList(it)
}, doFailure = { code, msg ->
activity.netViewModel.searchFoodListWithCallback(
param = param,
onResult = { state ->
try {
if (isAdded.not()) {
return@get
}
binding.refreshLayout.run {
setEnableRefresh(true)
}
toast(msg)
if (isAdded.not()) return@searchFoodListWithCallback
when (state) {
is UiState.Success -> loadDishList(state.data)
is UiState.Error -> {
binding.refreshLayout.setEnableRefresh(true)
toast(state.msg)
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
activity.delayDismissLoading()
}
else -> {}
}
} catch (e: Exception) {
e.printStackTrace()
}
})
}
)
}
private var emptyViewBinding: LayoutEmptyViewBinding? = null
@@ -255,30 +246,22 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
}
@SuppressLint("NotifyDataSetChanged")
private fun loadDishList(data: Any) {
private fun loadDishList(records: MutableList<FoodRecord>?) {
try {
if (isAdded.not()) {
return
}
binding.refreshLayout.run {
setEnableRefresh(true)
}
if (isAdded.not()) return
binding.refreshLayout.setEnableRefresh(true)
activity.delayDismissLoading()
finishRefresh()
val json = data.toJsonString()
val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
//toast("暂未搜索到相关菜品信息")
if (records.isNullOrEmpty()) {
if (pageNo == 1) {
loadEmptyView()
}
return
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(records)
dishAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
@@ -14,7 +14,7 @@ class DecimalAdapter : TypeAdapter<Number>() {
override fun read(reader: JsonReader): Number {
return when (reader.peek()) {
JsonToken.NUMBER -> reader.nextDouble()
JsonToken.STRING -> reader.nextString().toDouble()
JsonToken.STRING -> reader.nextString().toDoubleOrNull() ?: 0.0
else -> 0.0
}
}
@@ -0,0 +1,91 @@
package com.shuwei.dish.match.utils;
import android.util.Log;
import com.wabon.wbintelligenthardwaresdk.api.SensorScale;
import java.lang.reflect.Method;
/**
* 修复 SensorScale 地址编码 bug 的子类。
*
* 问题根因:SensorScale.zeroTwo / tareTwo 内部使用
* String command = address + "630603";
* 将 int 地址直接拼接为十进制字符串,再整体按十六进制解析成字节。
* 对于地址 1-9,十进制 == 十六进制,偶然正确;
* 对于地址 10+,十进制 "10" 被解析为 0x10=16),命中错误的物理秤。
*
* 修复方式:重写这两个方法,将地址先格式化为两位十六进制字符串(如 10 → "0A"),
* 再拼接命令码,通过反射调用父类私有方法 suspendSendThread 和 sumHex 发送。
*/
public class FixedSensorScale extends SensorScale {
private static final String TAG = "FixedSensorScale";
public FixedSensorScale(OnScaleResult result) {
super(result);
}
/**
* 重写清零命令,修复地址编码:十进制地址 → 两位十六进制字符串。
* 例:address=10 → "0A630603",发送字节 0x0A(正确),而非原来的 0x10(错误)。
*
* @param listener 操作成功回调
* @param address 秤地址(来自 onGetWeight 回调的十进制值)
*/
@Override
public void zeroTwo(OperateSuccessListener listener, int address) {
sendFixedCommand(listener, address, "630603", "zeroTwo");
}
/**
* 重写去皮命令,修复地址编码,逻辑同 zeroTwo。
*
* @param listener 操作成功回调
* @param address 秤地址
*/
@Override
public void tareTwo(OperateSuccessListener listener, int address) {
sendFixedCommand(listener, address, "630601", "tareTwo");
}
/**
* 通用发送逻辑:将地址格式化为两位十六进制后拼接命令码,
* 通过反射调用父类私有方法 sumHex 计算校验和,再调用 suspendSendThread 发送。
*
* @param listener 操作成功回调
* @param address 秤地址(十进制)
* @param commandCode 命令码(十六进制字符串,如 "630603"
* @param methodName 调用方名称,仅用于日志
*/
private void sendFixedCommand(OperateSuccessListener listener, int address,
String commandCode, String methodName) {
try {
// 将十进制地址转为两位十六进制字符串:10 → "0A"1 → "01"
String hexAddress = String.format("%02X", address);
String command = hexAddress + commandCode;
Log.d(TAG, methodName + ", address=" + address
+ ", hexAddress=" + hexAddress + ", command=" + command);
// 反射调用 sumHex(String) 计算校验和
Method sumHexMethod = SensorScale.class.getDeclaredMethod("sumHex", String.class);
sumHexMethod.setAccessible(true);
String checksum = (String) sumHexMethod.invoke(this, command);
// 反射调用 suspendSendThread(String) 发送完整命令帧
Method suspendMethod = SensorScale.class.getDeclaredMethod(
"suspendSendThread", String.class);
suspendMethod.setAccessible(true);
suspendMethod.invoke(this, command + checksum);
// 保存回调(通过反射写入父类私有字段 mOperateSuccessListener
java.lang.reflect.Field listenerField = SensorScale.class
.getDeclaredField("mOperateSuccessListener");
listenerField.setAccessible(true);
listenerField.set(this, listener);
} catch (Exception e) {
Log.e(TAG, methodName + " 反射调用失败: address=" + address, e);
}
}
}
@@ -0,0 +1,56 @@
package com.shuwei.dish.match.utils
import android.graphics.Typeface
import android.text.SpannableStringBuilder
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.text.style.LineHeightSpan
import android.text.style.StyleSpan
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.graphics.toColorInt
import com.shuwei.dish.match.db.entity.SeasoningEntity
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
object TextCellTool {
fun loadLayout(frameLayout: FrameLayout, item: SeasoningEntity) {
val child: TextView = frameLayout.getChildAt(0) as TextView
child.text = getTextSpan(item)
}
fun loadCell(tvCell: TextView, item: SeasoningEntity) {
tvCell.text = getTextSpan(item)
}
fun getTextSpan(item: SeasoningEntity): SpannableStringBuilder {
val endIndex = if (item.sort == 9) 10 else 6
var name = if (item.goodsName.isNullOrBlank()) "未配置" else item.goodsName!!
if (name.length > endIndex) {
name = "${name.take(endIndex)}"
}
return buildSpannableString {
appendText(
name,
ForegroundColorSpan(if (item.goodsName.isNullOrBlank()) "#999999".toColorInt() else "#000000".toColorInt()),
StyleSpan(Typeface.BOLD),
AbsoluteSizeSpan(28, true),
LineHeightSpan { text, start, end, spanstartv, v, fm ->
fm.descent += 10.dp // 增加行间距
}
)
append("\n")
val weight = item.useWeight ?: 0.toDouble()
val weightColor = if (weight == 0.toDouble()) "#999999" else "#00BC71"
appendText(
// "${DecimalFormat("#").format(weight)}g",
"${weight.roundedOneDecimalPlace()}g",
ForegroundColorSpan(weightColor.toColorInt()),
AbsoluteSizeSpan(30, true)
)
}
}
}
@@ -23,14 +23,25 @@ public class Weigher2 {
public static final int ERR_003 = 1003;
public static final int ERR_004 = 1004;
public static final int ERR_PCB_NOT_SUPPORT = 2000;
private static SensorScale mSensorScale;
private static FixedSensorScale mSensorScale;
private static String mDevicePort = "/dev/ttyS4";
// private static String mDevicePort = "/dev/ttyS7";
private static boolean mConnect = false;
private static Listener mListener;
/** 允许外部在 init() 前设置串口路径,用于不同硬件型号的子设备 */
public static void setDevicePort(String port) {
mDevicePort = port;
Log.d(TAG, "串口已设置为: " + port);
}
public static String getDevicePort() {
return mDevicePort;
}
public static void init() {
String var0;
if ((var0 = Build.MODEL) == "pcb_941") {
String var0 = Build.MODEL;
if ("pcb_941".equals(var0)) {
Log.w(TAG, "pcb not support, " + var0);
Listener var1;
if ((var1 = mListener) != null) {
@@ -40,8 +51,9 @@ public class Weigher2 {
} else {
System.init();
initScale();
if (var0 == "pcb_908") {
mDevicePort = "/dev/ttyS4";
if ("pcb_908".equals(var0)) {
// mDevicePort = "/dev/ttyS4";
mDevicePort = "/dev/ttyS7";
}
mSensorScale.openScale(mDevicePort, 115200, (open) -> {
@@ -64,8 +76,8 @@ public class Weigher2 {
}
public static void config() {
String var0;
if ((var0 = Build.MODEL) == "pcb_941") {
String var0 = Build.MODEL;
if ("pcb_941".equals(var0)) {
Log.w(TAG, "pcb not support, " + var0);
Listener var2;
if ((var2 = mListener) != null) {
@@ -390,7 +402,8 @@ public class Weigher2 {
}
};
//.<init>();
mSensorScale = new SensorScale(var0);
// 使用修复版子类,解决地址 ≥ 10 时 zeroTwo/tareTwo 编码错误的问题
mSensorScale = new FixedSensorScale(var0);
}
public static void setNoPull485Pin(boolean noPull) {
@@ -1,33 +1,58 @@
package com.shuwei.dish.match.utils
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
//import com.aithings.Weigher
import com.wabon.wbintelligenthardwaresdk.api.SensorScale
import java.util.concurrent.ConcurrentHashMap
typealias WeightCallback = (address: Int, state: Int, weight: Double) -> Unit
object WeightUtil {
private const val TAG = "WeightUtil"
const val STATE_STABLE = SensorScale.STATE_STABLE
const val STATE_UNSTABLE = SensorScale.STATE_UNSTABLE
const val STATE_OVER_WEIGHT = SensorScale.STATE_OVER_WEIGHT
// 使用字面量常量,避免在非 ARM 设备上触发 SensorScale 类加载
const val STATE_STABLE = 1
const val STATE_UNSTABLE = 0
const val STATE_OVER_WEIGHT = 2
var isConnected = false
var weightFuncMap: MutableMap<String, WeightCallback?>? =
null
/** 秤 SDK 是否可用;非 ARM 设备上为 false,所有秤操作将静默跳过 */
var isAvailable = false
val weightFuncMap: ConcurrentHashMap<String, WeightCallback?> = ConcurrentHashMap()
fun init() {
val abi = Build.SUPPORTED_ABIS.firstOrNull() ?: ""
if (!abi.startsWith("arm")) {
Log.w(TAG, "当前架构 $abi 不支持秤 SDK,已跳过初始化")
return
}
try {
SensorScale.isLog = true
Weigher2.init()
} catch (e: UnsatisfiedLinkError) {
Log.w(TAG, "秤 SDK 加载失败(非 ARM 设备),已跳过: ${e.message}")
return
} catch (e: Exception) {
Log.w(TAG, "秤 SDK 初始化异常,已跳过: ${e.message}")
return
}
isAvailable = true
Weigher2.setListener(object : WeightListenerImpl() {
override fun onInit(connect: Boolean) {
super.onInit(connect)
isConnected = connect
}
override fun onTare() {
super.onTare()
Log.d(TAG, "tareTwo,onTare 回调触发")
}
override fun onZero() {
super.onZero()
Log.d(TAG, "tareTwo,onZero 回调触发")
}
override fun onGetWeight(address: Int, state: Int, weight: Double) {
super.onGetWeight(address, state, weight)
val stateStr = when (state) {
@@ -38,7 +63,7 @@ object WeightUtil {
}
val useWeight = (weight*1000).roundedOneDecimalPlace()
Log.d(TAG, "readWeight, address=$address,state=$stateStr, weight=$useWeight")
weightFuncMap?.forEach { (key, value) ->
weightFuncMap.forEach { (key, value) ->
value?.invoke(address, state, useWeight)
}
@@ -55,10 +80,12 @@ object WeightUtil {
}
fun startContinuousRead() {
if (!isAvailable) return
Weigher2.startContinuousRead()
}
fun stopContinuousRead() {
if (!isAvailable) return
try {
Weigher2.stopContinuousRead()
Weigher2.unInit()
@@ -69,26 +96,25 @@ object WeightUtil {
fun tareTwo(address: Int) {
try {
Weigher2.tareTwo(address)
Log.d(TAG, "tareTwo,执行清零操作: address=$address")
// SDK 内部自动管理总线,直接发送清零命令
Weigher2.zeroTwo(address)
// Weigher2.tareTwo(address)
} catch (e: Exception) {
e.printStackTrace()
Log.e(TAG, "tareTwo,执行清零异常: address=$address, error=${e.message}")
}
}
private fun runOnUiThread(action: () -> Unit) {
Handler(Looper.getMainLooper()).post {
action()
}
fun removeWeightListener(weightKey: String) {
weightFuncMap.remove(weightKey)
}
fun addWeightListener(
weightKey: String,
getWeight: WeightCallback = { _, _, _ -> }
) {
if (weightFuncMap == null) {
weightFuncMap = mutableMapOf()
}
weightFuncMap?.put(weightKey, getWeight)
weightFuncMap[weightKey] = getWeight
}
}
@@ -98,7 +124,7 @@ open class WeightListenerImpl : Weigher2.Listener {
}
override fun onZero() {
Log.d("WeightUtil", "zero ok")
Log.d("WeightUtil", "tareTwo,zero ok")
}
override fun onTare() {
@@ -2,14 +2,15 @@ package com.shuwei.dish.match.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.db.DbRepository
import com.shuwei.dish.match.db.entity.CookFoodEntity
import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.db.entity.SeasoningEntity
import kotlinx.coroutines.launch
import kotlin.collections.forEach
class AppViewModel(private val rep: AppRepository) : ViewModel() {
class AppViewModel : ViewModel() {
private val rep = DbRepository()
fun updateCookFood(entity: CookFoodEntity) {
viewModelScope.launch {
@@ -17,9 +18,9 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
}
}
fun getCookFoodById(foodId: String, cookMode: Int, action: (CookFoodEntity?) -> Unit) {
fun getCookFoodById(foodId: String, cookMode: Int, dinnerType: String = "0", action: (CookFoodEntity?) -> Unit) {
viewModelScope.launch {
val data = rep.getCookFoodById(foodId, cookMode)
val data = rep.getCookFoodById(foodId, cookMode, dinnerType)
action(data)
}
}
@@ -42,17 +43,6 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
}
}
// fun saveCookFood(cookMode: Int, entity: CookFoodEntity) {
// viewModelScope.launch {
// val data = rep.getCookFoodById(entity.foodId, cookMode)
// if (data == null) {
// rep.insertCookFood(entity)
// return@launch
// }
// rep.updateCookFood(entity)
// }
// }
fun saveCookFoodAndGoods(
cookMode: Int,
entity: CookFoodEntity,
@@ -60,15 +50,13 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
onFinish: () -> Unit
) {
viewModelScope.launch {
val data = rep.getCookFoodById(entity.foodId, cookMode)
val data = rep.getCookFoodById(entity.foodId, cookMode, entity.dinnerType?:"")
if (data != null) {
//存在数据删除
rep.deleteCookFood(entity.foodId, cookMode)
rep.deleteCookFood(entity.foodId, cookMode, entity.dinnerType?:"")
rep.deleteCookFoodGoodsList(data.id, entity.foodId)
}
//重新保存数据
rep.insertCookFood(entity)
val newData = rep.getCookFoodById(entity.foodId, cookMode)
val newData = rep.getCookFoodById(entity.foodId, cookMode, entity.dinnerType?:"")
list?.let { it ->
it.forEach { goods ->
goods.id = 0
@@ -83,28 +71,27 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
fun deleteCookFoodAndGoods(
cookMode: Int,
foodId: String,
dinnerType: String = "0",
onFinish: () -> Unit
) {
viewModelScope.launch {
val data = rep.getCookFoodById(foodId, cookMode)
val data = rep.getCookFoodById(foodId, cookMode, dinnerType)
if (data != null) {
rep.deleteCookFood(foodId = foodId, cookMode = cookMode)
rep.deleteCookFood(foodId = foodId, cookMode = cookMode, dinnerType = dinnerType)
rep.deleteCookFoodGoodsList(pid = data.id, foodId = foodId)
}
onFinish()
}
}
//--------------------------------------------------------------------------------------------------------
fun getCookFoodGoodsList(
foodId: String,
cookMode: Int,
dinnerType: String = "0",
action: (list: MutableList<CookFoodGoodsEntity>) -> Unit
) {
viewModelScope.launch {
val cookFood = rep.getCookFoodById(foodId, cookMode)
val cookFood = rep.getCookFoodById(foodId, cookMode, dinnerType)
cookFood?.let {
val list = rep.getCookFoodGoodsList(pid = it.id, foodId = it.foodId)
action(list)
@@ -112,43 +99,6 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
}
}
// fun updateGoods(entity: CookFoodGoodsEntity) {
// viewModelScope.launch {
// rep.updateGoods(entity)
// }
// }
// fun saveCookFoodGoods(list: MutableList<CookFoodGoodsEntity>) {
// viewModelScope.launch {
// val foodId = list[0].foodId
// val data: MutableList<CookFoodGoodsEntity>? =
// rep.getCookFoodGoodsList(foodId!!)
// if (data.isNullOrEmpty()) {
// rep.insertGoodsList(list)
// return@launch
// }
// val localDbSeasoningList = rep.getCookFoodGoodsListByType(foodId, 3)
// localDbSeasoningList.forEach {
// it.isDel = 1
// //更新本地调料数据为已删除的状态
// rep.updateGoods(it)
// }
// //筛选出调料数据保存,主辅材不会变更不需要再次处理
// val saveSeasoningList = list.filter { it.materialType == 3 }
// rep.insertGoodsList(saveSeasoningList.toMutableList())
// }
// }
//--------------------------------------------------------------------------------------------------------
// fun addSeasoning(list: MutableList<SeasoningEntity>, callback: () -> Unit) {
// viewModelScope.launch {
// rep.deleteBatch()
// rep.insertSeasoningList(items = list)
// callback()
// }
// }
private var isProcessing = false
fun loadSeasoning(action: (MutableList<SeasoningEntity>) -> Unit) {
viewModelScope.launch {
@@ -173,14 +123,6 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
}
}
// fun updateAll(list: MutableList<SeasoningEntity>) {
// viewModelScope.launch {
// list.forEach {
// rep.updateSeasoning(it)
// }
// }
// }
fun saveSeasoning(entity: SeasoningEntity, block: () -> Unit) {
viewModelScope.launch {
val list = rep.getSeasoningBySort(entity.sort)
@@ -201,6 +143,4 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
block()
}
}
//--------------------------------------------------------------------------------------------------------
}
@@ -3,7 +3,7 @@
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.CookFoodGoodsRepository
//import com.shuwei.dish.match.entity.CookFoodGoodsEntity
//import com.shuwei.dish.match.db.entity.CookFoodGoodsEntity
//import kotlinx.coroutines.launch
//
//class CookFoodGoodsViewModel(private val rep: CookFoodGoodsRepository) : ViewModel() {
@@ -3,7 +3,7 @@
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.CookFoodRepository
//import com.shuwei.dish.match.entity.CookFoodEntity
//import com.shuwei.dish.match.db.entity.CookFoodEntity
//import kotlinx.coroutines.launch
//
//class CookFoodViewModel(private val rep: CookFoodRepository) : ViewModel() {
@@ -3,7 +3,7 @@
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.SeasoningRepository
//import com.shuwei.dish.match.entity.SeasoningEntity
//import com.shuwei.dish.match.db.entity.SeasoningEntity
//import kotlinx.coroutines.delay
//import kotlinx.coroutines.launch
//
@@ -3,8 +3,8 @@
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.SeasoningRepository
//import com.shuwei.dish.match.entity.CookFoodEntity
//import com.shuwei.dish.match.entity.SeasoningEntity
//import com.shuwei.dish.match.db.entity.CookFoodEntity
//import com.shuwei.dish.match.db.entity.SeasoningEntity
//import kotlinx.coroutines.launch
//
//class SubmitDishViewModel(private val rep: SeasoningRepository) : ViewModel() {
@@ -1,43 +0,0 @@
package com.shuwei.dish.match.viewmodel.factory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.viewmodel.AppViewModel
@Suppress("UNCHECKED_CAST")
class AppFactory(private val repo: AppRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return AppViewModel(repo) as T
}
}
//
//@Suppress("UNCHECKED_CAST")
//class DeviceSettingFactory(private val repo: SeasoningRepository) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return DeviceSettingViewModel(repo) as T
// }
//}
//
////@Suppress("UNCHECKED_CAST")
////class SubmitDishFactory(private val repo: SeasoningRepository) : ViewModelProvider.Factory {
//// override fun <T : ViewModel> create(modelClass: Class<T>): T {
//// return SubmitDishViewModel(repo) as T
//// }
////}
//
//@Suppress("UNCHECKED_CAST")
//class CookFoodFactory(private val repo: CookFoodRepository) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return CookFoodViewModel(repo) as T
// }
//}
//
//@Suppress("UNCHECKED_CAST")
//class CookFoodGoodsFactory(private val repo: CookFoodGoodsRepository) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return CookFoodGoodsViewModel(repo) as T
// }
//}
Binary file not shown.
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFEBEE" />
<corners android:radius="10dp" />
<stroke
android:width="2dp"
android:color="#FF5252" />
</shape>
@@ -126,12 +126,6 @@
android:textColor="@color/gray_b4"
android:textSize="28sp" />
<!-- <View-->
<!-- android:id="@+id/viewPoint"-->
<!-- android:layout_width="5dp"-->
<!-- android:layout_height="5dp"-->
<!-- android:layout_gravity="center_vertical"-->
<!-- android:background="@drawable/bg_circle_green"/>-->
</LinearLayout>
<include