refactor(base): 重构基础组件和数据库访问层

- 修复 BaseActivity 中 rightIconActon 方法名拼写错误
- 重命名 appViewModel 为 dbViewModel 以统一数据库访问实例
- 优化 BaseApp 中的共享偏好设置获取逻辑
- 移除 GlobalData 中硬编码的应用ID和SDK密钥
- 重命名 NetworkUtils 为 NetworkUtil 并增强网络连接检测
- 重构 RemoteRepository 中的API调用方法结构
- 在多个Activity和Fragment中统一使用dbViewModel替代appViewModel
- 优化食品列表合并烹饪中条目的逻辑处理
- 调整文件上传时的媒体类型定义
- 清理注释和代码结构以提高可维护性
This commit is contained in:
2026-05-15 11:24:44 +08:00
parent b562694665
commit deb03a0c37
23 changed files with 172 additions and 236 deletions
@@ -92,13 +92,13 @@ open class BaseActivity : AppCompatActivity() {
titleBarAction: ((FrameLayout) -> Unit)? = null, titleBarAction: ((FrameLayout) -> Unit)? = null,
backAction: ((ImageView) -> Unit)? = null, backAction: ((ImageView) -> Unit)? = null,
titleAction: ((TextView) -> Unit)? = null, titleAction: ((TextView) -> Unit)? = null,
rightIconActon: ((ImageView) -> Unit)? = null rightIconAction: ((ImageView) -> Unit)? = null
) { ) {
binding.llTitleBar.visible() binding.llTitleBar.visible()
titleBarAction?.invoke(binding.llTitleBar) titleBarAction?.invoke(binding.llTitleBar)
backAction?.invoke(binding.ivBack) backAction?.invoke(binding.ivBack)
titleAction?.invoke(binding.tvTitle) titleAction?.invoke(binding.tvTitle)
rightIconActon?.invoke(binding.ivRightIcon) rightIconAction?.invoke(binding.ivRightIcon)
} }
fun setHeaderBgVisible(show: Boolean) { fun setHeaderBgVisible(show: Boolean) {
@@ -182,7 +182,7 @@ open class BaseActivity : AppCompatActivity() {
val netViewModel: NetViewModel by viewModels() val netViewModel: NetViewModel by viewModels()
val appViewModel: DbViewModel by viewModels() val dbViewModel: DbViewModel by viewModels()
private var singlePermissionCallback: ((isGranted: Boolean) -> Unit)? = null private var singlePermissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var multiplePermissionsCallback: ((isGranted: Boolean) -> Unit)? = null private var multiplePermissionsCallback: ((isGranted: Boolean) -> Unit)? = null
@@ -20,29 +20,19 @@ import com.shuwei.dish.match.utils.CrashHandler
import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.Weigher2 import com.shuwei.dish.match.utils.Weigher2
/**
* BaseApp is the main application class that extends Android's Application class.
* It handles initialization of core components, device role determination,
* and service management for the weighing system.
*/
class BaseApp : Application() { class BaseApp : Application() {
// Lazy initialization of the database instance
val database by lazy { DatabaseProvider(this).instance } val database by lazy { DatabaseProvider(this).instance }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
// Set the application instance
instance = this instance = this
// 读取本地保存的环境地址,覆盖默认值 // 读取本地保存的环境地址,覆盖默认值
val savedBaseUrl = SpTool.baseUrl val savedBaseUrl = SpTool.baseUrl
if (savedBaseUrl.isNotEmpty()) { if (savedBaseUrl.isNotEmpty()) {
GlobalData.appBaseUrl = savedBaseUrl GlobalData.appBaseUrl = savedBaseUrl
} }
// Initialize crash handler for error tracking
CrashHandler.init(this) CrashHandler.init(this)
// Get and store the unique device ID
val deviceId = AppUtil.getUDID(this) val deviceId = AppUtil.getUDID(this)
Log.d(TAG, "onCreate: deviceId=$deviceId") Log.d(TAG, "onCreate: deviceId=$deviceId")
GlobalData.deviceId = deviceId GlobalData.deviceId = deviceId
@@ -88,24 +78,21 @@ class BaseApp : Application() {
companion object { companion object {
private val TAG = "BaseApp" private val TAG = "BaseApp"
// const val canteenId = "1678234139391512577"
var canteenId = "0" var canteenId = "0"
// var configUrl = ""
// var token: String? = null
// var deviceId: String? = null
var appVersion: String = "1" var appVersion: String = "1"
@Volatile @Volatile
private var sharedPref: SharedPreferences? = null private var sharedPref: SharedPreferences? = null
public var instance: BaseApp? = null lateinit var instance: BaseApp
fun getSharedPref(): SharedPreferences? {
if (sharedPref != null) { /** 双重检查锁,保证多线程下只初始化一次 */
return sharedPref fun getSharedPref(): SharedPreferences {
return sharedPref ?: synchronized(this) {
sharedPref ?: instance.getSharedPreferences(
instance.getString(R.string.sp_save_name),
Context.MODE_PRIVATE
).also { sharedPref = it }
} }
val saveName = instance?.getString(R.string.sp_save_name)
sharedPref = instance?.getSharedPreferences(saveName, Context.MODE_PRIVATE)
return sharedPref
} }
} }
@@ -25,8 +25,6 @@ object GlobalData {
* 当前设备角色,启动时从 SpTool 读取,默认为 SLAVE * 当前设备角色,启动时从 SpTool 读取,默认为 SLAVE
*/ */
var deviceRole: DeviceRole = DeviceRole.SLAVE var deviceRole: DeviceRole = DeviceRole.SLAVE
var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj"
var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k"
} }
/** /**
@@ -225,7 +225,7 @@ class NetViewModel(
params: MutableMap<String, RequestBody> params: MutableMap<String, RequestBody>
) { ) {
val fileParts = fileList.map { file -> val fileParts = fileList.map { file ->
file.asRequestBody("image/jpeg".toMediaTypeOrNull()) file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
.let { MultipartBody.Part.createFormData("foodPics", file.name, it) } .let { MultipartBody.Part.createFormData("foodPics", file.name, it) }
} }
viewModelScope.launch { viewModelScope.launch {
@@ -5,7 +5,6 @@ import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.GoodsItem import com.shuwei.dish.match.entity.GoodsItem
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody import okhttp3.RequestBody
import java.io.File
/** /**
* 网络数据仓库,封装所有 ApiService 调用 * 网络数据仓库,封装所有 ApiService 调用
@@ -13,104 +12,42 @@ import java.io.File
*/ */
class RemoteRepository { class RemoteRepository {
/** /** 统一封装 API 调用:处理响应映射和异常转换,返回 UiState */
* 提交制作菜品 private suspend fun <T> safeApiCall(call: suspend () -> ApiResponse<T>): UiState<T?> {
* @param entity 菜品实体
* @return UiState 包装的结果,Success 表示提交成功,Error 携带错误信息
*/
suspend fun submitCookFood(entity: CookFoodDTO): UiState<Any?> {
return try { return try {
val resp = apiService.submitCookFood(param = entity) val resp = call()
if (resp.isSuccess()) UiState.Success(resp.data) if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "") else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) { } catch (e: Exception) {
val ex = getApiException(e) UiState.Error("-1", getApiException(e).errorMsg)
UiState.Error("-1", ex.errorMsg)
} }
} }
/** /** 提交制作菜品 */
* 查询菜品详情 suspend fun submitCookFood(entity: CookFoodDTO): UiState<Any?> =
* @param foodId 菜品 ID safeApiCall { apiService.submitCookFood(param = entity) }
* @return UiState 包装的结果,Success 携带详情数据,Error 携带错误信息
*/
suspend fun getFoodDetail(foodId: String): UiState<CookFoodDTO?> {
return try {
val resp = apiService.getFoodDetail(foodId = foodId)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/** /** 查询菜品详情 */
* 搜索菜品列表 suspend fun getFoodDetail(foodId: String): UiState<CookFoodDTO?> =
* @param param 查询参数 safeApiCall { apiService.getFoodDetail(foodId = foodId) }
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
*/
suspend fun searchFoodList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> {
return try {
val resp = apiService.searchFoodList(param = param)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/** /** 搜索菜品列表 */
* 查询采样数据列表 suspend fun searchFoodList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> =
* @param param 查询参数 safeApiCall { apiService.searchFoodList(param = param) }
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
*/
suspend fun getSamplingList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> {
return try {
val resp = apiService.getSamplingList(param = param)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/** /** 查询采样数据列表 */
* 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用) suspend fun getSamplingList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> =
* @param param 查询参数 safeApiCall { apiService.getSamplingList(param = param) }
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
*/
suspend fun queryGoodsList(param: MutableMap<String, Any>): UiState<MutableList<GoodsItem>?> {
return try {
val resp = apiService.queryGoodsList(param = param)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/** /** 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用) */
* 上次采集数据 suspend fun queryGoodsList(param: MutableMap<String, Any>): UiState<MutableList<GoodsItem>?> =
* @param params 参数 safeApiCall { apiService.queryGoodsList(param = param) }
* @param foodPics 图片
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息 /** 上传采集数据 */
*/
suspend fun uploadFoodVectorData( suspend fun uploadFoodVectorData(
params: MutableMap<String, RequestBody>, params: MutableMap<String, RequestBody>,
foodPics: List<MultipartBody.Part> foodPics: List<MultipartBody.Part>
): UiState<List<String>?> { ): UiState<List<String>?> =
return try { safeApiCall { apiService.uploadFoodVectorData(params = params, foodPics = foodPics) }
val resp = apiService.uploadFoodVectorData(params = params, foodPics = foodPics)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
} }
@@ -71,7 +71,7 @@ class CollectedFoodActivity : BaseActivity() {
.setContent(content) .setContent(content)
.setNegativeButton("取消") .setNegativeButton("取消")
.setPositiveButton("确认") { .setPositiveButton("确认") {
appViewModel.removeCollectedFood( dbViewModel.removeCollectedFood(
foodName = foodName, foodName = foodName,
nameFilter = binding.editSearch.text.toString() nameFilter = binding.editSearch.text.toString()
) )
@@ -95,7 +95,7 @@ class CollectedFoodActivity : BaseActivity() {
} }
}, titleAction = { }, titleAction = {
it.text = "已采集食材" it.text = "已采集食材"
}, rightIconActon = { }, rightIconAction = {
it.gone() it.gone()
}, backAction = { }, backAction = {
it.visible() it.visible()
@@ -110,7 +110,7 @@ class CollectedFoodActivity : BaseActivity() {
} }
private fun loadCollectedFoodList(name: String = "") { private fun loadCollectedFoodList(name: String = "") {
appViewModel.loadCollectedFoodList(nameFilter = name) dbViewModel.loadCollectedFoodList(nameFilter = name)
} }
/** /**
@@ -120,7 +120,7 @@ class CollectedFoodActivity : BaseActivity() {
private fun initObserver() { private fun initObserver() {
lifecycleScope.launch { lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { repeatOnLifecycle(Lifecycle.State.STARTED) {
appViewModel.collectedFoodListState.collect { items -> dbViewModel.collectedFoodListState.collect { items ->
foodList.clear() foodList.clear()
foodList.addAll(items) foodList.addAll(items)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
@@ -47,7 +47,7 @@ class CookingModeActivity : BaseActivity() {
it.visible() it.visible()
}, titleAction = { }, titleAction = {
it.text = "选择菜品" it.text = "选择菜品"
}, rightIconActon = { }, rightIconAction = {
it.visible() it.visible()
it.setImageResource(R.drawable.ic_setting) it.setImageResource(R.drawable.ic_setting)
it.setOnClickListener { it.setOnClickListener {
@@ -63,7 +63,7 @@ class CookingModeActivity : BaseActivity() {
fun getCookFoodList() { fun getCookFoodList() {
appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType) dbViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType)
} }
private var configFinished = false private var configFinished = false
@@ -73,7 +73,7 @@ class CookingModeActivity : BaseActivity() {
// block() // block()
// return // return
// } // }
// appViewModel.loadSeasoning { // dbViewModel.loadSeasoning {
// if (it.isEmpty()) { // if (it.isEmpty()) {
// showDeviceConfigDialog() // showDeviceConfigDialog()
// return@loadSeasoning // return@loadSeasoning
@@ -96,7 +96,7 @@ class CookingModeActivity : BaseActivity() {
/** 切换餐次时清空输入框,用此标志跳过 afterTextChanged 的查询触发 */ /** 切换餐次时清空输入框,用此标志跳过 afterTextChanged 的查询触发 */
private var isClearingByTabSwitch = false private var isClearingByTabSwitch = false
fun addViewListener() { private fun addViewListener() {
binding.dishRadioGroup.setOnCheckedChangeListener { _, checkedId -> binding.dishRadioGroup.setOnCheckedChangeListener { _, checkedId ->
dinnerType = when (checkedId) { dinnerType = when (checkedId) {
R.id.rbBreakfast -> "1" R.id.rbBreakfast -> "1"
@@ -157,7 +157,7 @@ class CookingModeActivity : BaseActivity() {
fun deleteCookFoodAndGoods(foodId: String, dinnerType: String, action: () -> Unit) { fun deleteCookFoodAndGoods(foodId: String, dinnerType: String, action: () -> Unit) {
lifecycleScope.launch { lifecycleScope.launch {
appViewModel.deleteCookFoodAndGoods(cookMode = 0, foodId = foodId, dinnerType = dinnerType) dbViewModel.deleteCookFoodAndGoods(cookMode = 0, foodId = foodId, dinnerType = dinnerType)
action() action()
} }
} }
@@ -77,7 +77,7 @@
// it.visible() // it.visible()
// }, titleAction = { // }, titleAction = {
// it.text = "设备配置" // it.text = "设备配置"
// }, rightIconActon = { // }, rightIconAction = {
// it.gone() // it.gone()
//// it.alpha = 0.0F //// it.alpha = 0.0F
//// it.setImageResource(R.drawable.ic_setting) //// it.setImageResource(R.drawable.ic_setting)
@@ -78,7 +78,7 @@ class FoodSearchActivity : BaseActivity() {
it.visible() it.visible()
}, titleAction = { }, titleAction = {
it.text = "菜品搜索" it.text = "菜品搜索"
}, rightIconActon = { }, rightIconAction = {
it.gone() it.gone()
it.setImageResource(R.drawable.ic_setting) it.setImageResource(R.drawable.ic_setting)
it.setOnClickListener { it.setOnClickListener {
@@ -66,18 +66,18 @@ class HomeActivity : BaseActivity() {
// startActivity<SamplingListActivity>() // startActivity<SamplingListActivity>()
// } // }
// appViewModel.deleteSeasoningBySort(12) // dbViewModel.deleteSeasoningBySort(12)
// appViewModel.deleteSeasoningBySort(11) // dbViewModel.deleteSeasoningBySort(11)
// appViewModel.deleteSeasoningBySort(13) // dbViewModel.deleteSeasoningBySort(13)
// appViewModel.deleteSeasoningBySort(14) // dbViewModel.deleteSeasoningBySort(14)
// appViewModel.deleteCookFoodAndGoods(0,"") {} // dbViewModel.deleteCookFoodAndGoods(0,"") {}
// appViewModel.getCookFoodGoodsList("") {goodsList-> // dbViewModel.getCookFoodGoodsList("") {goodsList->
// goodsList.forEach { // goodsList.forEach {
// it.foodId = "1594990392084779010" // it.foodId = "1594990392084779010"
// appViewModel.updateGoods(it) // dbViewModel.updateGoods(it)
// } // }
// } // }
// appViewModel.clearAllSeasoning(){} // dbViewModel.clearAllSeasoning(){}
// binding.btnDeviceConfig.setOnClickListener { // binding.btnDeviceConfig.setOnClickListener {
@@ -141,14 +141,14 @@ class HomeActivity : BaseActivity() {
private fun goSampling() { private fun goSampling() {
lifecycleScope.launch { lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot() val slots = dbViewModel.loadSeasoningSlot()
if (slots.isEmpty()) { if (slots.isEmpty()) {
// 无调料信息,打开采样历史列表页面,点击设置配置调料信息 // 无调料信息,打开采样历史列表页面,点击设置配置调料信息
startActivity<SamplingModeActivity>() startActivity<SamplingModeActivity>()
finish() finish()
return@launch return@launch
} }
val count = appViewModel.countCookFood(cookMode = 1) val count = dbViewModel.countCookFood(cookMode = 1)
if (count > 0) { if (count > 0) {
// 有烹饪中数据,打开采样历史列表页面 // 有烹饪中数据,打开采样历史列表页面
startActivity<SamplingModeActivity>() startActivity<SamplingModeActivity>()
@@ -18,7 +18,7 @@ import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.scale.ScaleServiceManager import com.shuwei.dish.match.scale.ScaleServiceManager
import com.shuwei.dish.match.utils.AddressUtil import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.AppUtil import com.shuwei.dish.match.utils.AppUtil
import com.shuwei.dish.match.utils.NetworkUtils import com.shuwei.dish.match.utils.NetworkUtil
import com.shuwei.dish.match.utils.SpTool import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.startActivity
@@ -54,7 +54,7 @@ class InitActivity : BaseActivity() {
countdownHandler.postDelayed(this, 100) countdownHandler.postDelayed(this, 100)
} else { } else {
// 倒计时结束,最后检测一次网络 // 倒计时结束,最后检测一次网络
if (NetworkUtils.isNetworkConnected(this@InitActivity)) { if (NetworkUtil.isNetworkConnected(this@InitActivity)) {
startNextPage() startNextPage()
} else { } else {
// 网络未连接,显示"连接网络"按钮 // 网络未连接,显示"连接网络"按钮
@@ -116,13 +116,13 @@ class InitActivity : BaseActivity() {
private fun goSampling() { private fun goSampling() {
lifecycleScope.launch { lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot() val slots = dbViewModel.loadSeasoningSlot()
if (slots.isEmpty()) { if (slots.isEmpty()) {
// 无调料信息,打开采样历史列表页面,点击设置配置调料信息 // 无调料信息,打开采样历史列表页面,点击设置配置调料信息
startActivity<SamplingModeActivity>() startActivity<SamplingModeActivity>()
return@launch return@launch
} }
val count = appViewModel.countCookFood(cookMode = 1) val count = dbViewModel.countCookFood(cookMode = 1)
if (count > 0) { if (count > 0) {
// 有烹饪中数据,打开采样历史列表页面 // 有烹饪中数据,打开采样历史列表页面
startActivity<SamplingModeActivity>() startActivity<SamplingModeActivity>()
@@ -169,7 +169,7 @@ class InitActivity : BaseActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (NetworkUtils.isNetworkConnected(this)) { if (NetworkUtil.isNetworkConnected(this)) {
// 有网络时隐藏网络检测区域,直接跳转 // 有网络时隐藏网络检测区域,直接跳转
binding.llNetwork.gone() binding.llNetwork.gone()
startNextPage(withDelay = false) startNextPage(withDelay = false)
@@ -213,7 +213,7 @@ class InitActivity : BaseActivity() {
* 检测网络连接 * 检测网络连接
*/ */
private fun checkNetworkConnection() { private fun checkNetworkConnection() {
if (NetworkUtils.isNetworkConnected(this)) { if (NetworkUtil.isNetworkConnected(this)) {
// 网络连接成功,停止倒计时并跳转到 HomeActivity // 网络连接成功,停止倒计时并跳转到 HomeActivity
countdownHandler.removeCallbacks(countdownTask) countdownHandler.removeCallbacks(countdownTask)
startNextPage() startNextPage()
@@ -81,7 +81,7 @@ class MasterScaleActivity : BaseActivity() {
/** 从 Room 加载槽位配置到 slotNameMap,加载完成后启动数据观测 */ /** 从 Room 加载槽位配置到 slotNameMap,加载完成后启动数据观测 */
private fun loadSlotNames() { private fun loadSlotNames() {
lifecycleScope.launch { lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot() val slots = dbViewModel.loadSeasoningSlot()
slots.forEach { slot -> slots.forEach { slot ->
slotNameMap["${slot.deviceId}#${slot.address}"] = slot.goodsName slotNameMap["${slot.deviceId}#${slot.address}"] = slot.goodsName
} }
@@ -85,7 +85,7 @@ class PrepareFoodActivity : BaseActivity() {
it.visible() it.visible()
}, titleAction = { }, titleAction = {
it.text = titleText it.text = titleText
}, rightIconActon = { }, rightIconAction = {
it.gone() it.gone()
}, backAction = { }, backAction = {
it.setOnClickListener { it.setOnClickListener {
@@ -57,7 +57,7 @@ class SamplingModeActivity : BaseActivity() {
it.visible() it.visible()
}, titleAction = { }, titleAction = {
it.text = "采样历史" it.text = "采样历史"
}, rightIconActon = { }, rightIconAction = {
it.visible() it.visible()
it.setImageResource(R.drawable.ic_setting) it.setImageResource(R.drawable.ic_setting)
it.setOnClickListener { it.setOnClickListener {
@@ -70,7 +70,7 @@ class SamplingModeActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun getCookFoodList() { fun getCookFoodList() {
appViewModel.getCookFoodListWithCallback(cookMode = 1) { cookFoodEntities -> dbViewModel.getCookFoodListWithCallback(cookMode = 1) { cookFoodEntities ->
// 回调期间用户可能已切换 Tab,需再次校验 // 回调期间用户可能已切换 Tab,需再次校验
if (binding.rbSamplingCooking.isChecked.not()) return@getCookFoodListWithCallback if (binding.rbSamplingCooking.isChecked.not()) return@getCookFoodListWithCallback
delayDismissLoading() delayDismissLoading()
@@ -103,7 +103,7 @@ class SamplingModeActivity : BaseActivity() {
fun deleteCookFoodAndGoods(foodId: String, dinnerType: String, action: () -> Unit) { fun deleteCookFoodAndGoods(foodId: String, dinnerType: String, action: () -> Unit) {
lifecycleScope.launch { lifecycleScope.launch {
appViewModel.deleteCookFoodAndGoods(cookMode = 1, foodId = foodId, dinnerType = dinnerType) dbViewModel.deleteCookFoodAndGoods(cookMode = 1, foodId = foodId, dinnerType = dinnerType)
action() action()
} }
} }
@@ -399,7 +399,7 @@ class SamplingModeActivity : BaseActivity() {
return return
} }
lifecycleScope.launch { lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot() val slots = dbViewModel.loadSeasoningSlot()
if (slots.isEmpty()) { if (slots.isEmpty()) {
showDeviceConfigDialog() showDeviceConfigDialog()
return@launch return@launch
@@ -44,7 +44,7 @@ class SettingActivity : BaseActivity() {
it.visible() it.visible()
}, titleAction = { }, titleAction = {
it.text = "设备设置" it.text = "设备设置"
}, rightIconActon = { }, rightIconAction = {
it.visible() it.visible()
it.clickWithDebounce { it.clickWithDebounce {
binding.llDbInspect.visible() binding.llDbInspect.visible()
@@ -107,7 +107,7 @@ class SettingActivity : BaseActivity() {
.setContent("将物理删除全部 4 张表数据(菜品、食材、调料、槽位),此操作不可恢复,同时重置菜品模式和食堂id,确认继续?") .setContent("将物理删除全部 4 张表数据(菜品、食材、调料、槽位),此操作不可恢复,同时重置菜品模式和食堂id,确认继续?")
.setNegativeButton("取消") .setNegativeButton("取消")
.setPositiveButton("确认") { .setPositiveButton("确认") {
appViewModel.clearAllData { dbViewModel.clearAllData {
SpTool.cookMode = -1 SpTool.cookMode = -1
SpTool.canteenId = "0" SpTool.canteenId = "0"
// 通知所有已连接子设备同步清除数据 // 通知所有已连接子设备同步清除数据
@@ -81,7 +81,7 @@ class SingleFragmentActivity : BaseActivity() {
setTitleBar( setTitleBar(
titleBarAction = { it.visible() }, titleBarAction = { it.visible() },
titleAction = { it.text = getTitleForPage(pageType) }, titleAction = { it.text = getTitleForPage(pageType) },
rightIconActon = { it.gone() } rightIconAction = { it.gone() }
) )
// 首次创建时加载 Fragment,避免屏幕旋转重复添加 // 首次创建时加载 Fragment,避免屏幕旋转重复添加
@@ -162,7 +162,7 @@ class SlaveActivity : BaseActivity() {
*/ */
private fun loadSlotsFromDb() { private fun loadSlotsFromDb() {
lifecycleScope.launch { lifecycleScope.launch {
val slots = appViewModel.getSeasoningSlotsByDeviceId(GlobalData.deviceId) val slots = dbViewModel.getSeasoningSlotsByDeviceId(GlobalData.deviceId)
applySlots(slots) applySlots(slots)
} }
} }
@@ -179,8 +179,8 @@ class SlaveActivity : BaseActivity() {
?: return@onSeasoningConfig ?: return@onSeasoningConfig
lifecycleScope.launch { lifecycleScope.launch {
// 全量替换:先删除本机所有旧槽位,再写入最新配置 // 全量替换:先删除本机所有旧槽位,再写入最新配置
appViewModel.deleteAllSlotsByDeviceId(GlobalData.deviceId) dbViewModel.deleteAllSlotsByDeviceId(GlobalData.deviceId)
appViewModel.upsertAllSeasoningSlots(mySlots) dbViewModel.upsertAllSeasoningSlots(mySlots)
applySlots(mySlots) applySlots(mySlots)
} }
} }
@@ -194,7 +194,7 @@ class SlaveActivity : BaseActivity() {
private fun listenClearData() { private fun listenClearData() {
ScaleServiceManager.onClearData = { ScaleServiceManager.onClearData = {
lifecycleScope.launch { lifecycleScope.launch {
appViewModel.clearAllData { dbViewModel.clearAllData {
SpTool.cookMode = -1 SpTool.cookMode = -1
SpTool.canteenId = "0" SpTool.canteenId = "0"
runOnUiThread { applySlots(emptyList()) } runOnUiThread { applySlots(emptyList()) }
@@ -126,7 +126,7 @@ class SubmitFoodActivity : BaseActivity() {
it.visible() it.visible()
}, titleAction = { }, titleAction = {
it.text = food?.foodName it.text = food?.foodName
}, rightIconActon = { }, rightIconAction = {
it.gone() it.gone()
}, backAction = { }, backAction = {
it.setOnClickListener { it.setOnClickListener {
@@ -179,7 +179,7 @@ class SubmitFoodActivity : BaseActivity() {
*/ */
private fun loadSlotsThenObserveScales() { private fun loadSlotsThenObserveScales() {
lifecycleScope.launch { lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot() val slots = dbViewModel.loadSeasoningSlot()
slots.forEach { slot -> slots.forEach { slot ->
slotMap["${slot.deviceId}#${slot.address}"] = slot.goodsId to slot.goodsName slotMap["${slot.deviceId}#${slot.address}"] = slot.goodsId to slot.goodsName
} }
@@ -188,7 +188,7 @@ class SubmitFoodActivity : BaseActivity() {
val foodId = food?.foodId ?: return@launch val foodId = food?.foodId ?: return@launch
val cookMode = food?.cookMode ?: return@launch val cookMode = food?.cookMode ?: return@launch
val list = val list =
appViewModel.getCookFoodGoodsList(foodId, cookMode, food?.dinnerType ?: "0") dbViewModel.getCookFoodGoodsList(foodId, cookMode, food?.dinnerType ?: "0")
goodsList = list goodsList = list
prefillSeasoningFromGoodsList(list) prefillSeasoningFromGoodsList(list)
} else { } else {
@@ -387,7 +387,7 @@ class SubmitFoodActivity : BaseActivity() {
seasoningAdapter.items seasoningAdapter.items
.filter { it.useWeight > 0.0 } .filter { it.useWeight > 0.0 }
.forEach { goodsList?.add(buildSeasoningEntity(it)) } .forEach { goodsList?.add(buildSeasoningEntity(it)) }
appViewModel.saveCookFoodAndGoods( dbViewModel.saveCookFoodAndGoods(
cookMode = food!!.cookMode, cookMode = food!!.cookMode,
entity = cookFoodEntity, entity = cookFoodEntity,
list = goodsList list = goodsList
@@ -440,7 +440,7 @@ class SubmitFoodActivity : BaseActivity() {
* 从本地 dm_seasoning 表补充完整字段(popularName、zjmCode、materId、goodsCode 等) * 从本地 dm_seasoning 表补充完整字段(popularName、zjmCode、materId、goodsCode 等)
*/ */
private suspend fun buildSeasoningEntity(item: SeasoningWeightAdapter.Item): CookFoodGoodsEntity { private suspend fun buildSeasoningEntity(item: SeasoningWeightAdapter.Item): CookFoodGoodsEntity {
val seasoning = appViewModel.getSeasoningByGoodsId(item.goodsId) val seasoning = dbViewModel.getSeasoningByGoodsId(item.goodsId)
return CookFoodGoodsEntity().also { entity -> return CookFoodGoodsEntity().also { entity ->
entity.foodId = cookFoodEntity.foodId entity.foodId = cookFoodEntity.foodId
entity.goodsId = item.goodsId entity.goodsId = item.goodsId
@@ -465,7 +465,7 @@ class SubmitFoodActivity : BaseActivity() {
} }
// 用 viewModelScope 执行删除,不受当前 Activity 生命周期影响 // 用 viewModelScope 执行删除,不受当前 Activity 生命周期影响
// singleTask 跳转会销毁本 Activity 并取消 lifecycleScope,导致删除被中断 // singleTask 跳转会销毁本 Activity 并取消 lifecycleScope,导致删除被中断
appViewModel.markSubmittedAndDeleteAsync( dbViewModel.markSubmittedAndDeleteAsync(
cookMode = food!!.cookMode, cookMode = food!!.cookMode,
foodId = food!!.foodId!!, foodId = food!!.foodId!!,
dinnerType = food!!.dinnerType dinnerType = food!!.dinnerType
@@ -87,12 +87,12 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
// 加载更多按钮 // 加载更多按钮
binding.tvLoadMore.setOnClickListener { binding.tvLoadMore.setOnClickListener {
currentActivity.appViewModel.loadMoreDbInspect(currentTableIndex, showDel) currentActivity.dbViewModel.loadMoreDbInspect(currentTableIndex, showDel)
} }
// 订阅 UI 状态 // 订阅 UI 状态
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
currentActivity.appViewModel.dbInspectState.collect { state -> currentActivity.dbViewModel.dbInspectState.collect { state ->
renderState(state) renderState(state)
} }
} }
@@ -105,7 +105,7 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
} }
private fun loadFirstPage() { private fun loadFirstPage() {
currentActivity.appViewModel.loadDbInspect(currentTableIndex, showDel) currentActivity.dbViewModel.loadDbInspect(currentTableIndex, showDel)
} }
private fun renderState(state: DbViewModel.DbInspectUiState) { private fun renderState(state: DbViewModel.DbInspectUiState) {
@@ -194,7 +194,7 @@ class FoodListFragment : BaseFragment<FragmentFoodListBinding>() {
) )
foodName?.let { param["foodName"] = it } foodName?.let { param["foodName"] = it }
if (pageNo == 1) { if (pageNo == 1) {
pendingLocalList = activity.appViewModel.getCookFoodListDirect( pendingLocalList = activity.dbViewModel.getCookFoodListDirect(
cookMode = 0, cookMode = 0,
dinnerType = dinnerType dinnerType = dinnerType
) )
@@ -285,17 +285,32 @@ class FoodListFragment : BaseFragment<FragmentFoodListBinding>() {
if (pageNo == 1) list.clear() if (pageNo == 1) list.clear()
val mergedList = records?.toMutableList() ?: mutableListOf() val mergedList = records?.toMutableList() ?: mutableListOf()
mergeCookingItems(mergedList, effectiveLocalList)
deduplicateForPaging(mergedList, localList)
renderList(mergedList, records)
} catch (e: Exception) {
e.printStackTrace()
}
}
if (!effectiveLocalList.isNullOrEmpty()) { /**
* 将本地烹饪中条目置顶合并到网络列表:
* 网络列表中已有的条目标记 isCooking 并移至顶部,没有的则新建占位条目
*/
private fun mergeCookingItems(
mergedList: MutableList<FoodRecord>,
cookingEntities: List<CookFoodEntity>?
) {
if (cookingEntities.isNullOrEmpty()) return
val cookingItems = mutableListOf<FoodRecord>() val cookingItems = mutableListOf<FoodRecord>()
effectiveLocalList.forEachIndexed { index, entity -> cookingEntities.forEachIndexed { index, entity ->
val food = mergedList.firstOrNull { it.foodId == entity.foodId } val existing = mergedList.firstOrNull { it.foodId == entity.foodId }
if (food != null) { if (existing != null) {
mergedList.remove(food) mergedList.remove(existing)
food.isCooking = true existing.isCooking = true
food.sort = index existing.sort = index
food.dinnerType = entity.dinnerType ?: "0" existing.dinnerType = entity.dinnerType ?: "0"
cookingItems.add(food) cookingItems.add(existing)
} else { } else {
cookingItems.add( cookingItems.add(
FoodRecord( FoodRecord(
@@ -311,19 +326,28 @@ class FoodListFragment : BaseFragment<FragmentFoodListBinding>() {
mergedList.addAll(0, cookingItems) mergedList.addAll(0, cookingItems)
} }
if (pageNo >= 2 && !localList.isNullOrEmpty()) { /**
* 翻页时从网络结果中剔除第一页已加载的本地烹饪中条目,避免重复出现
*/
private fun deduplicateForPaging(
mergedList: MutableList<FoodRecord>,
localList: List<CookFoodEntity>?
) {
if (pageNo < 2 || localList.isNullOrEmpty()) return
val localIds = localList.map { it.foodId }.toHashSet() val localIds = localList.map { it.foodId }.toHashSet()
mergedList.removeAll { it.foodId in localIds } mergedList.removeAll { it.foodId in localIds }
} }
/**
* 将合并结果追加到列表并刷新 UI,按网络返回条数决定是否开启加载更多
*/
@SuppressLint("NotifyDataSetChanged")
private fun renderList(mergedList: List<FoodRecord>, records: List<FoodRecord>?) {
list.addAll(mergedList) list.addAll(mergedList)
dishAdapter.notifyDataSetChanged() dishAdapter.notifyDataSetChanged()
val isLoadMoreEnable = (records?.size ?: 0) >= pageSize val isLoadMoreEnable = (records?.size ?: 0) >= pageSize
binding.refreshLayout.setEnableRefresh(true) binding.refreshLayout.setEnableRefresh(true)
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable) binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) pageNo++ if (isLoadMoreEnable) pageNo++
} catch (e: Exception) {
e.printStackTrace()
}
} }
} }
@@ -50,7 +50,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
) = FragmentSeasoningConfigBinding.inflate(inflater, container, false) ) = FragmentSeasoningConfigBinding.inflate(inflater, container, false)
private lateinit var currentActivity: SingleFragmentActivity private lateinit var currentActivity: SingleFragmentActivity
// private lateinit var appViewModel: DbViewModel // private lateinit var dbViewModel: DbViewModel
private val adapter22 by lazy { buildAdapter22() } private val adapter22 by lazy { buildAdapter22() }
private val adapter18 by lazy { private val adapter18 by lazy {
@@ -186,7 +186,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
onUpdateName(name) onUpdateName(name)
lifecycleScope.launch { lifecycleScope.launch {
// 保存槽位配置 // 保存槽位配置
currentActivity.appViewModel.upsertSeasoningSlot( currentActivity.dbViewModel.upsertSeasoningSlot(
SeasoningSlotEntity( SeasoningSlotEntity(
deviceId = deviceId, deviceId = deviceId,
address = address, address = address,
@@ -195,7 +195,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
) )
) )
// 按 goodsId 去重保存完整调料信息到 dm_seasoning // 按 goodsId 去重保存完整调料信息到 dm_seasoning
currentActivity.appViewModel.upsertSeasoningByGoodsId(item.toSeasoningEntity()) currentActivity.dbViewModel.upsertSeasoningByGoodsId(item.toSeasoningEntity())
broadcastAllSlots() broadcastAllSlots()
} }
} }
@@ -205,7 +205,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
/** 将单个槽位写入 Room,再广播全量配置给子设备 */ /** 将单个槽位写入 Room,再广播全量配置给子设备 */
private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) { private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) {
lifecycleScope.launch { lifecycleScope.launch {
currentActivity.appViewModel.upsertSeasoningSlot(slot) currentActivity.dbViewModel.upsertSeasoningSlot(slot)
broadcastAllSlots() broadcastAllSlots()
} }
} }
@@ -233,14 +233,14 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
/** 从 Room 删除指定槽位,再广播全量配置给子设备 */ /** 从 Room 删除指定槽位,再广播全量配置给子设备 */
private fun deleteSlotAndBroadcast(deviceId: String, address: Int) { private fun deleteSlotAndBroadcast(deviceId: String, address: Int) {
lifecycleScope.launch { lifecycleScope.launch {
currentActivity.appViewModel.deleteSeasoningSlot(deviceId, address) currentActivity.dbViewModel.deleteSeasoningSlot(deviceId, address)
broadcastAllSlots() broadcastAllSlots()
} }
} }
/** 读取全量槽位配置并通过 wsClient 推送给所有已连接子设备 */ /** 读取全量槽位配置并通过 wsClient 推送给所有已连接子设备 */
private suspend fun broadcastAllSlots() { private suspend fun broadcastAllSlots() {
val allSlots = currentActivity.appViewModel.loadSeasoningSlot().map { val allSlots = currentActivity.dbViewModel.loadSeasoningSlot().map {
SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName) SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName)
} }
ScaleServiceManager.sendSeasoningConfig(allSlots) ScaleServiceManager.sendSeasoningConfig(allSlots)
@@ -249,7 +249,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
// /** 初始化 ViewModel */ // /** 初始化 ViewModel */
// private fun initViewModel() { // private fun initViewModel() {
// val factory = AppFactory(DbRepository(BaseApp.instance!!.database.appDao())) // val factory = AppFactory(DbRepository(BaseApp.instance!!.database.appDao()))
// appViewModel = ViewModelProvider(this, factory)[DbViewModel::class.java] // dbViewModel = ViewModelProvider(this, factory)[DbViewModel::class.java]
// } // }
@@ -292,9 +292,9 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
adapter22.update(emptyList()) adapter22.update(emptyList())
adapter18.update(emptyList()) adapter18.update(emptyList())
lifecycleScope.launch { lifecycleScope.launch {
val slots22 = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_22) val slots22 = currentActivity.dbViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_22)
val slots18 = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_18) val slots18 = currentActivity.dbViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_18)
val slotsOil = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_1) val slotsOil = currentActivity.dbViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_1)
slots22.forEach { slot -> slots22.forEach { slot ->
val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address) val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address)
if (pos >= 0) adapter22.updateItemName(pos, slot.goodsName) if (pos >= 0) adapter22.updateItemName(pos, slot.goodsName)
@@ -3,15 +3,28 @@ package com.shuwei.dish.match.utils
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.LinkProperties import android.net.LinkProperties
import android.net.NetworkCapabilities
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
import java.net.Inet4Address import java.net.Inet4Address
/** /**
* 网络工具类 * 网络工具类,提供网络状态检测和本机 IP 获取功能
*/ */
object NetworkUtil { object NetworkUtil {
/**
* 检测设备是否连接到网络
* @param context 应用上下文
* @return true 表示已连接网络,false 表示未连接
*/
fun isNetworkConnected(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = cm.activeNetwork ?: return false
val capabilities = cm.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
/** /**
* 获取本机局域网 IPv4 地址 * 获取本机局域网 IPv4 地址
* - Android 12+API 31+):使用 ConnectivityManager + LinkProperties,避免废弃 API * - Android 12+API 31+):使用 ConnectivityManager + LinkProperties,避免废弃 API
@@ -60,3 +73,4 @@ object NetworkUtil {
} }
} }
} }
@@ -1,24 +0,0 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
/**
* 网络连接检测工具类
* 提供检测设备网络连接状态的功能
*/
object NetworkUtils {
/**
* 检测设备是否连接到网络
* @param context 应用上下文
* @return true 表示已连接网络,false 表示未连接
*/
fun isNetworkConnected(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}