diff --git a/app/src/main/java/com/shuwei/dish/match/dialog/EnvSwitchDialog.kt b/app/src/main/java/com/shuwei/dish/match/dialog/EnvSwitchDialog.kt new file mode 100644 index 0000000..60e39ce --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/dialog/EnvSwitchDialog.kt @@ -0,0 +1,70 @@ +package com.sw.inbound.dialog + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import com.sw.inbound.GlobalData +import com.sw.inbound.GlobalKey +import com.sw.inbound.databinding.DialogEnvSwitchBinding +import com.sw.inbound.utils.SPUtil +import com.sw.inbound.utils.ext.dp +import com.sw.inbound.utils.ext.toast + +/** + * 环境切换弹窗 + * + * 提供 TEST / UAT / PROD 三套环境的切换功能, + * 初始化时自动根据 [GlobalData.appBaseUrl] 选中当前环境, + * 确认后更新 [GlobalData.appBaseUrl] 并通过 [onEnvChanged] 回调通知外部。 + * + * @param context 上下文 + * @param onEnvChanged 确认切换后的回调,参数为新的 baseUrl + */ +class EnvSwitchDialog( + context: Context, + private val onEnvChanged: (newBaseUrl: String) -> Unit = {} +) : BaseDialog( + context, + defWidth = 600.dp, + defHeight = WindowManager.LayoutParams.WRAP_CONTENT +) { + + private lateinit var binding: DialogEnvSwitchBinding + + override fun getRootView(): View { + binding = DialogEnvSwitchBinding.inflate(LayoutInflater.from(context)) + return binding.root + } + + override fun initView() { + // 根据当前 appBaseUrl 预选对应 RadioButton,不匹配则不选 + when (GlobalData.appBaseUrl) { + GlobalData.TEST_BASE_URL -> binding.rbTest.isChecked = true + GlobalData.UAT_BASE_URL -> binding.rbUat.isChecked = true + GlobalData.PROD_BASE_URL -> binding.rbProd.isChecked = true + } + + // 取消按钮 + binding.btnCancel.setOnClickListener { dismiss() } + + // 确认按钮 + binding.btnConfirm.setOnClickListener { + // 未选中任何选项时提示用户 + if (binding.rgEnv.checkedRadioButtonId == -1) { + context.toast("请选择环境") + return@setOnClickListener + } + val newUrl = when (binding.rgEnv.checkedRadioButtonId) { + binding.rbTest.id -> GlobalData.TEST_BASE_URL + binding.rbUat.id -> GlobalData.UAT_BASE_URL + binding.rbProd.id -> GlobalData.PROD_BASE_URL + else -> return@setOnClickListener + } + GlobalData.appBaseUrl = newUrl + SPUtil.getInstance().put(GlobalKey.KEY_BASE_URL, newUrl) + onEnvChanged(newUrl) + dismiss() + } + } +} 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 805705d..d63487f 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 @@ -28,7 +28,6 @@ import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.toast import com.yanzhenjie.recyclerview.SwipeMenuItem import java.io.Serializable -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch class FoodListFragment : BaseFragment() { @@ -53,6 +52,12 @@ class FoodListFragment : BaseFragment() { private lateinit var activity: CookingModeActivity + /** 第一页时先查数据库存入此字段,网络结果回来后直接使用,避免时序问题 */ + private var pendingLocalList: MutableList? = null + + /** 记录本次请求对应的餐次,响应回来时比对,忽略非本 Fragment 发起的请求结果 */ + private var pendingDinnerType: String = "0" + private var list: MutableList = mutableListOf() private val dishAdapter by lazy { FoodListAdapter(list = list).apply { @@ -147,40 +152,43 @@ class FoodListFragment : BaseFragment() { } /** - * 用 combine 同时监听网络结果与本地数据,网络成功时统一合并渲染 - * pageNo > 1(加载更多)时 cookFoodListState 不参与合并,直接追加网络数据 + * 只监听 searchFoodState,避免 cookFoodListState 变化触发重复渲染。 + * 第一页时使用 getDishList() 中预先查好的 pendingLocalList 参与合并;加载更多时直接追加。 + * 三个 Fragment 共享同一个 searchFoodState,通过 pendingDinnerType 过滤非本 Fragment 发起的请求。 */ private fun initObserver() { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - combine( - activity.netViewModel.searchFoodState, - activity.appViewModel.cookFoodListState - ) { networkState, localList -> Pair(networkState, localList) } - .collect { (networkState, localList) -> - when (networkState) { - is UiState.Loading -> activity.showLoading() - is UiState.Success -> { - activity.delayDismissLoading() - finishRefresh() - loadAndMergeDishList(networkState.data, localList) - } - is UiState.Error -> { - if (isAdded.not()) return@collect - toast(networkState.msg) - finishRefresh() - activity.delayDismissLoading() - if (pageNo == 1) { - if (localList.isNullOrEmpty()) { - loadEmptyView() - } else { - loadAndMergeDishList(null, localList) - } + activity.netViewModel.searchFoodState.collect { networkState -> + when (networkState) { + is UiState.Loading -> activity.showLoading() + is UiState.Success -> { + // 餐次不匹配,忽略(其他 Fragment 发起的请求) + if (dinnerType != pendingDinnerType) return@collect + activity.delayDismissLoading() + finishRefresh() + // 第一页使用预查好的本地数据合并;加载更多传 null 直接追加 + val localList = if (pageNo == 1) pendingLocalList else null + loadAndMergeDishList(networkState.data, localList) + } + is UiState.Error -> { + // 餐次不匹配,忽略 + if (dinnerType != pendingDinnerType) return@collect + if (isAdded.not()) return@collect + toast(networkState.msg) + finishRefresh() + activity.delayDismissLoading() + if (pageNo == 1) { + if (pendingLocalList.isNullOrEmpty()) { + loadEmptyView() + } else { + loadAndMergeDishList(null, pendingLocalList) } } - is UiState.Idle -> {} } + is UiState.Idle -> {} } + } } } } @@ -217,16 +225,26 @@ class FoodListFragment : BaseFragment() { @SuppressLint("NotifyDataSetChanged") @Suppress("unchecked_cast") fun getDishList(foodName: String? = null) { - val param = mutableMapOf( - "pageNum" to pageNo, - "pageSize" to pageSize, - "placeId" to BaseApp.canteenId, - "dinnerType" to getDinnerTypeText() - ) - // foodName 不为空时按名称过滤,为 null 时查全部 - foodName?.let { param["foodName"] = it } - activity.getFoodList(param = param) - if (pageNo == 1) activity.getCookFoodList() + viewLifecycleOwner.lifecycleScope.launch { + val param = mutableMapOf( + "pageNum" to pageNo, + "pageSize" to pageSize, + "placeId" to BaseApp.canteenId, + "dinnerType" to getDinnerTypeText() + ) + // foodName 不为空时按名称过滤,为 null 时查全部 + foodName?.let { param["foodName"] = it } + if (pageNo == 1) { + // 先查本地数据库,完成后再发网络请求,确保合并时本地数据已就绪 + pendingLocalList = activity.appViewModel.getCookFoodListDirect( + cookMode = 0, + dinnerType = dinnerType + ) + } + // 记录本次请求的餐次,用于响应回来时过滤其他 Fragment 发起的请求 + pendingDinnerType = dinnerType + activity.getFoodList(param = param) + } } private var emptyViewBinding: LayoutEmptyViewBinding? = null