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