From deb03a0c37f0dbf0379fb38e27efbd275945ec14 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Fri, 15 May 2026 11:24:44 +0800 Subject: [PATCH] =?UTF-8?q?refactor(base):=20=E9=87=8D=E6=9E=84=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E7=BB=84=E4=BB=B6=E5=92=8C=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 BaseActivity 中 rightIconActon 方法名拼写错误 - 重命名 appViewModel 为 dbViewModel 以统一数据库访问实例 - 优化 BaseApp 中的共享偏好设置获取逻辑 - 移除 GlobalData 中硬编码的应用ID和SDK密钥 - 重命名 NetworkUtils 为 NetworkUtil 并增强网络连接检测 - 重构 RemoteRepository 中的API调用方法结构 - 在多个Activity和Fragment中统一使用dbViewModel替代appViewModel - 优化食品列表合并烹饪中条目的逻辑处理 - 调整文件上传时的媒体类型定义 - 清理注释和代码结构以提高可维护性 --- .../shuwei/dish/match/base/BaseActivity.kt | 6 +- .../com/shuwei/dish/match/base/BaseApp.kt | 31 ++--- .../com/shuwei/dish/match/base/GlobalData.kt | 2 - .../com/shuwei/dish/match/net/NetViewModel.kt | 2 +- .../shuwei/dish/match/net/RemoteRepository.kt | 109 ++++-------------- .../dish/match/ui/CollectedFoodActivity.kt | 8 +- .../dish/match/ui/CookingModeActivity.kt | 10 +- .../dish/match/ui/DeviceConfigActivity.kt | 2 +- .../dish/match/ui/FoodSearchActivity.kt | 2 +- .../com/shuwei/dish/match/ui/HomeActivity.kt | 20 ++-- .../com/shuwei/dish/match/ui/InitActivity.kt | 12 +- .../dish/match/ui/MasterScaleActivity.kt | 2 +- .../dish/match/ui/PrepareFoodActivity.kt | 2 +- .../dish/match/ui/SamplingModeActivity.kt | 8 +- .../shuwei/dish/match/ui/SettingActivity.kt | 4 +- .../dish/match/ui/SingleFragmentActivity.kt | 2 +- .../com/shuwei/dish/match/ui/SlaveActivity.kt | 8 +- .../dish/match/ui/SubmitFoodActivity.kt | 12 +- .../match/ui/fragment/DbInspectFragment.kt | 6 +- .../match/ui/fragment/FoodListFragment.kt | 100 ++++++++++------ .../ui/fragment/SeasoningConfigFragment.kt | 20 ++-- .../shuwei/dish/match/utils/NetworkUtil.kt | 16 ++- .../shuwei/dish/match/utils/NetworkUtils.kt | 24 ---- 23 files changed, 172 insertions(+), 236 deletions(-) delete mode 100644 app/src/main/java/com/shuwei/dish/match/utils/NetworkUtils.kt diff --git a/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt b/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt index 1507785..522ee70 100644 --- a/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt @@ -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 diff --git a/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt b/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt index 207f326..9bbf6b7 100644 --- a/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt +++ b/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt @@ -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 } } diff --git a/app/src/main/java/com/shuwei/dish/match/base/GlobalData.kt b/app/src/main/java/com/shuwei/dish/match/base/GlobalData.kt index 5a76d89..a46548e 100644 --- a/app/src/main/java/com/shuwei/dish/match/base/GlobalData.kt +++ b/app/src/main/java/com/shuwei/dish/match/base/GlobalData.kt @@ -25,8 +25,6 @@ object GlobalData { * 当前设备角色,启动时从 SpTool 读取,默认为 SLAVE */ var deviceRole: DeviceRole = DeviceRole.SLAVE - var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj" - var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k" } /** diff --git a/app/src/main/java/com/shuwei/dish/match/net/NetViewModel.kt b/app/src/main/java/com/shuwei/dish/match/net/NetViewModel.kt index 7510e79..24c0a14 100644 --- a/app/src/main/java/com/shuwei/dish/match/net/NetViewModel.kt +++ b/app/src/main/java/com/shuwei/dish/match/net/NetViewModel.kt @@ -225,7 +225,7 @@ class NetViewModel( params: MutableMap ) { 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 { diff --git a/app/src/main/java/com/shuwei/dish/match/net/RemoteRepository.kt b/app/src/main/java/com/shuwei/dish/match/net/RemoteRepository.kt index 44f41b4..ea9f035 100644 --- a/app/src/main/java/com/shuwei/dish/match/net/RemoteRepository.kt +++ b/app/src/main/java/com/shuwei/dish/match/net/RemoteRepository.kt @@ -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 { + /** 统一封装 API 调用:处理响应映射和异常转换,返回 UiState */ + private suspend fun safeApiCall(call: suspend () -> ApiResponse): UiState { 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 { - 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 = + safeApiCall { apiService.submitCookFood(param = entity) } - /** - * 搜索菜品列表 - * @param param 查询参数 - * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息 - */ - suspend fun searchFoodList(param: MutableMap): UiState?> { - 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 = + safeApiCall { apiService.getFoodDetail(foodId = foodId) } - /** - * 查询采样数据列表 - * @param param 查询参数 - * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息 - */ - suspend fun getSamplingList(param: MutableMap): UiState?> { - 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): UiState?> = + safeApiCall { apiService.searchFoodList(param = param) } - /** - * 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用) - * @param param 查询参数 - * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息 - */ - suspend fun queryGoodsList(param: MutableMap): UiState?> { - 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): UiState?> = + safeApiCall { apiService.getSamplingList(param = param) } - /** - * 上次采集数据 - * @param params 参数 - * @param foodPics 图片 - * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息 - */ + /** 查询物品信息列表(食材 goodsType=0,调料 goodsType=1 共用) */ + suspend fun queryGoodsList(param: MutableMap): UiState?> = + safeApiCall { apiService.queryGoodsList(param = param) } + + /** 上传采集数据 */ suspend fun uploadFoodVectorData( params: MutableMap, foodPics: List - ): UiState?> { - 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?> = + safeApiCall { apiService.uploadFoodVectorData(params = params, foodPics = foodPics) } } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/CollectedFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/CollectedFoodActivity.kt index ea98732..ea8cb86 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/CollectedFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/CollectedFoodActivity.kt @@ -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() diff --git a/app/src/main/java/com/shuwei/dish/match/ui/CookingModeActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/CookingModeActivity.kt index 6f707cb..13132ce 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/CookingModeActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/CookingModeActivity.kt @@ -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() } } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/DeviceConfigActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/DeviceConfigActivity.kt index b620c6c..4d9c569 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/DeviceConfigActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/DeviceConfigActivity.kt @@ -77,7 +77,7 @@ // it.visible() // }, titleAction = { // it.text = "设备配置" -// }, rightIconActon = { +// }, rightIconAction = { // it.gone() //// it.alpha = 0.0F //// it.setImageResource(R.drawable.ic_setting) diff --git a/app/src/main/java/com/shuwei/dish/match/ui/FoodSearchActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/FoodSearchActivity.kt index e442bde..3bd8164 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/FoodSearchActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/FoodSearchActivity.kt @@ -78,7 +78,7 @@ class FoodSearchActivity : BaseActivity() { it.visible() }, titleAction = { it.text = "菜品搜索" - }, rightIconActon = { + }, rightIconAction = { it.gone() it.setImageResource(R.drawable.ic_setting) it.setOnClickListener { diff --git a/app/src/main/java/com/shuwei/dish/match/ui/HomeActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/HomeActivity.kt index 4875d99..6a25f08 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/HomeActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/HomeActivity.kt @@ -66,18 +66,18 @@ class HomeActivity : BaseActivity() { // startActivity() // } -// 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() finish() return@launch } - val count = appViewModel.countCookFood(cookMode = 1) + val count = dbViewModel.countCookFood(cookMode = 1) if (count > 0) { // 有烹饪中数据,打开采样历史列表页面 startActivity() diff --git a/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt index 3fffe41..f8e8a61 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt @@ -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() return@launch } - val count = appViewModel.countCookFood(cookMode = 1) + val count = dbViewModel.countCookFood(cookMode = 1) if (count > 0) { // 有烹饪中数据,打开采样历史列表页面 startActivity() @@ -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() diff --git a/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt index 2fb1c90..aa142f0 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/MasterScaleActivity.kt @@ -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 } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt index 11ce0c8..ccaa2f0 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt @@ -85,7 +85,7 @@ class PrepareFoodActivity : BaseActivity() { it.visible() }, titleAction = { it.text = titleText - }, rightIconActon = { + }, rightIconAction = { it.gone() }, backAction = { it.setOnClickListener { diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt index 9c50e47..061614c 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt @@ -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 diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt index 0164b5c..4c31aea 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt @@ -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" // 通知所有已连接子设备同步清除数据 diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SingleFragmentActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SingleFragmentActivity.kt index d7c016e..4a2dca3 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SingleFragmentActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SingleFragmentActivity.kt @@ -81,7 +81,7 @@ class SingleFragmentActivity : BaseActivity() { setTitleBar( titleBarAction = { it.visible() }, titleAction = { it.text = getTitleForPage(pageType) }, - rightIconActon = { it.gone() } + rightIconAction = { it.gone() } ) // 首次创建时加载 Fragment,避免屏幕旋转重复添加 diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt index b591d2d..dc23d94 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt @@ -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()) } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt index 7083a41..9249f2e 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt @@ -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 diff --git a/app/src/main/java/com/shuwei/dish/match/ui/fragment/DbInspectFragment.kt b/app/src/main/java/com/shuwei/dish/match/ui/fragment/DbInspectFragment.kt index 53d89e8..2814dbf 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/fragment/DbInspectFragment.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/fragment/DbInspectFragment.kt @@ -87,12 +87,12 @@ class DbInspectFragment : BaseFragment() { // 加载更多按钮 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() { } private fun loadFirstPage() { - currentActivity.appViewModel.loadDbInspect(currentTableIndex, showDel) + currentActivity.dbViewModel.loadDbInspect(currentTableIndex, showDel) } private fun renderState(state: DbViewModel.DbInspectUiState) { diff --git a/app/src/main/java/com/shuwei/dish/match/ui/fragment/FoodListFragment.kt b/app/src/main/java/com/shuwei/dish/match/ui/fragment/FoodListFragment.kt index 106de93..df98551 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/fragment/FoodListFragment.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/fragment/FoodListFragment.kt @@ -194,7 +194,7 @@ class FoodListFragment : BaseFragment() { ) 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() { if (pageNo == 1) list.clear() val mergedList = records?.toMutableList() ?: mutableListOf() - - if (!effectiveLocalList.isNullOrEmpty()) { - val cookingItems = mutableListOf() - 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, + cookingEntities: List? + ) { + if (cookingEntities.isNullOrEmpty()) return + val cookingItems = mutableListOf() + 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, + localList: List? + ) { + 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, records: List?) { + list.addAll(mergedList) + dishAdapter.notifyDataSetChanged() + val isLoadMoreEnable = (records?.size ?: 0) >= pageSize + binding.refreshLayout.setEnableRefresh(true) + binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable) + if (isLoadMoreEnable) pageNo++ + } } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/fragment/SeasoningConfigFragment.kt b/app/src/main/java/com/shuwei/dish/match/ui/fragment/SeasoningConfigFragment.kt index f574a50..a67ed2f 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/fragment/SeasoningConfigFragment.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/fragment/SeasoningConfigFragment.kt @@ -50,7 +50,7 @@ class SeasoningConfigFragment : BaseFragment() { ) = 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() { onUpdateName(name) lifecycleScope.launch { // 保存槽位配置 - currentActivity.appViewModel.upsertSeasoningSlot( + currentActivity.dbViewModel.upsertSeasoningSlot( SeasoningSlotEntity( deviceId = deviceId, address = address, @@ -195,7 +195,7 @@ class SeasoningConfigFragment : BaseFragment() { ) ) // 按 goodsId 去重保存完整调料信息到 dm_seasoning - currentActivity.appViewModel.upsertSeasoningByGoodsId(item.toSeasoningEntity()) + currentActivity.dbViewModel.upsertSeasoningByGoodsId(item.toSeasoningEntity()) broadcastAllSlots() } } @@ -205,7 +205,7 @@ class SeasoningConfigFragment : BaseFragment() { /** 将单个槽位写入 Room,再广播全量配置给子设备 */ private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) { lifecycleScope.launch { - currentActivity.appViewModel.upsertSeasoningSlot(slot) + currentActivity.dbViewModel.upsertSeasoningSlot(slot) broadcastAllSlots() } } @@ -233,14 +233,14 @@ class SeasoningConfigFragment : BaseFragment() { /** 从 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() { // /** 初始化 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() { 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) diff --git a/app/src/main/java/com/shuwei/dish/match/utils/NetworkUtil.kt b/app/src/main/java/com/shuwei/dish/match/utils/NetworkUtil.kt index 3a4ccd6..2ac55a7 100644 --- a/app/src/main/java/com/shuwei/dish/match/utils/NetworkUtil.kt +++ b/app/src/main/java/com/shuwei/dish/match/utils/NetworkUtil.kt @@ -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 { } } } + diff --git a/app/src/main/java/com/shuwei/dish/match/utils/NetworkUtils.kt b/app/src/main/java/com/shuwei/dish/match/utils/NetworkUtils.kt deleted file mode 100644 index 4a8c3eb..0000000 --- a/app/src/main/java/com/shuwei/dish/match/utils/NetworkUtils.kt +++ /dev/null @@ -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) - } -}