refactor(viewmodel): 将 searchFoodList/getCookFoodList 迁移至 StateFlow,DishListFragment 改用 combine 并行合并数据并修复分页本地数据混入问题
This commit is contained in:
@@ -6,6 +6,9 @@ import com.shuwei.dish.match.entity.CookFoodEntity
|
|||||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
import com.shuwei.dish.match.entity.FoodRecord
|
||||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,16 +45,19 @@ class NetViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索菜品列表的 UI 状态流,UI 层通过 collect 监听
|
||||||
|
*/
|
||||||
|
private val _searchFoodState = MutableStateFlow<UiState<MutableList<FoodRecord>?>>(UiState.Idle)
|
||||||
|
val searchFoodState: StateFlow<UiState<MutableList<FoodRecord>?>> = _searchFoodState.asStateFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 搜索菜品列表
|
* 搜索菜品列表
|
||||||
*/
|
*/
|
||||||
fun searchFoodList(
|
fun searchFoodList(param: MutableMap<String, Any>) {
|
||||||
param: MutableMap<String, Any>,
|
|
||||||
onSuccess: (MutableList<FoodRecord>?) -> Unit,
|
|
||||||
onFailure: (String, String) -> Unit
|
|
||||||
) {
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.searchFoodList(param, onSuccess, onFailure)
|
_searchFoodState.value = UiState.Loading
|
||||||
|
_searchFoodState.value = repository.searchFoodList(param)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,18 +46,18 @@ class RemoteRepository {
|
|||||||
/**
|
/**
|
||||||
* 搜索菜品列表
|
* 搜索菜品列表
|
||||||
* @param param 查询参数
|
* @param param 查询参数
|
||||||
* @param onSuccess 成功回调
|
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
|
||||||
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
|
|
||||||
*/
|
*/
|
||||||
suspend fun searchFoodList(
|
suspend fun searchFoodList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> {
|
||||||
param: MutableMap<String, Any>,
|
return try {
|
||||||
onSuccess: (MutableList<FoodRecord>?) -> Unit,
|
val resp = apiService.searchFoodList(param = param)
|
||||||
onFailure: (String, String) -> Unit
|
if (resp.isSuccess()) UiState.Success(resp.data)
|
||||||
) = request(
|
else UiState.Error(resp.code, resp.msg ?: "")
|
||||||
onRequest = { apiService.searchFoodList(param = param) },
|
} catch (e: Exception) {
|
||||||
onSuccess = onSuccess,
|
val ex = getApiException(e)
|
||||||
onFailure = onFailure
|
UiState.Error("-1", ex.errorMsg)
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询采样数据列表
|
* 查询采样数据列表
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.shuwei.dish.match.net
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 UI 状态模型,用于 ViewModel 向 UI 层传递请求状态
|
||||||
|
* Idle — 初始状态,未发起请求
|
||||||
|
* Loading — 请求进行中
|
||||||
|
* Success — 请求成功,携带数据
|
||||||
|
* Error — 请求失败,携带错误码和错误信息
|
||||||
|
*/
|
||||||
|
sealed class UiState<out T> {
|
||||||
|
object Idle : UiState<Nothing>()
|
||||||
|
object Loading : UiState<Nothing>()
|
||||||
|
data class Success<T>(val data: T) : UiState<T>()
|
||||||
|
data class Error(val code: String, val msg: String) : UiState<Nothing>()
|
||||||
|
}
|
||||||
@@ -107,8 +107,8 @@ class DishSamplingActivity : BaseActivity() {
|
|||||||
ViewModelProvider(this, factory)[AppViewModel::class.java]
|
ViewModelProvider(this, factory)[AppViewModel::class.java]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
|
fun getCookFoodList() {
|
||||||
appViewModel.getCookFoodList(cookMode = 1) { action(it) }
|
appViewModel.getCookFoodList(cookMode = 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
import com.shuwei.dish.match.adapter.FoodRecordAdapter
|
import com.shuwei.dish.match.adapter.FoodRecordAdapter
|
||||||
@@ -12,6 +15,7 @@ import com.shuwei.dish.match.base.BaseApp
|
|||||||
import com.shuwei.dish.match.databinding.ActivityFoodSearchBinding
|
import com.shuwei.dish.match.databinding.ActivityFoodSearchBinding
|
||||||
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
|
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
import com.shuwei.dish.match.entity.FoodRecord
|
||||||
|
import com.shuwei.dish.match.net.UiState
|
||||||
import com.shuwei.dish.match.utils.KeyboardUtil
|
import com.shuwei.dish.match.utils.KeyboardUtil
|
||||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||||
import com.shuwei.dish.match.utils.ext.gone
|
import com.shuwei.dish.match.utils.ext.gone
|
||||||
@@ -19,6 +23,7 @@ import com.shuwei.dish.match.utils.ext.startActivity
|
|||||||
import com.shuwei.dish.match.utils.ext.toast
|
import com.shuwei.dish.match.utils.ext.toast
|
||||||
import com.shuwei.dish.match.utils.ext.visible
|
import com.shuwei.dish.match.utils.ext.visible
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class FoodSearchActivity : BaseActivity() {
|
class FoodSearchActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -83,6 +88,31 @@ class FoodSearchActivity : BaseActivity() {
|
|||||||
adapter = recordAdapter
|
adapter = recordAdapter
|
||||||
}
|
}
|
||||||
addViewListener()
|
addViewListener()
|
||||||
|
initObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集 searchFoodState,统一处理 Loading / Success / Error 状态
|
||||||
|
*/
|
||||||
|
private fun initObserver() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
netViewModel.searchFoodState.collect { state ->
|
||||||
|
when (state) {
|
||||||
|
is UiState.Loading -> showLoading()
|
||||||
|
is UiState.Success -> handleSearchResult(state.data)
|
||||||
|
is UiState.Error -> {
|
||||||
|
delayDismissLoading()
|
||||||
|
finishRefresh()
|
||||||
|
binding.refreshLayout.setEnableRefresh(true)
|
||||||
|
toast(state.msg)
|
||||||
|
if (pageNo == 1) loadEmptyView()
|
||||||
|
}
|
||||||
|
is UiState.Idle -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addViewListener() {
|
private fun addViewListener() {
|
||||||
@@ -140,61 +170,6 @@ class FoodSearchActivity : BaseActivity() {
|
|||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun queryListInfo(input: String) {
|
private fun queryListInfo(input: String) {
|
||||||
showLoading()
|
|
||||||
// val map = mutableMapOf(
|
|
||||||
// "foodName" to input.trim(),
|
|
||||||
// "pageNo" to "$pageNo",
|
|
||||||
// "pageSize" to "$pageSize",
|
|
||||||
// "canteenId" to BaseApp.canteenId
|
|
||||||
// "dinnerType" to getDinnerTypeText()
|
|
||||||
// )
|
|
||||||
// val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
|
|
||||||
// append("?")
|
|
||||||
// map.forEach { (key, value) -> append("$key=$value&") }
|
|
||||||
// }
|
|
||||||
// sb.deleteCharAt(sb.length - 1)
|
|
||||||
// HttpUtil.get(
|
|
||||||
// url = sb.toString(),
|
|
||||||
// doSuccess = {
|
|
||||||
// delayDismissLoading()
|
|
||||||
// finishRefresh()
|
|
||||||
// binding.refreshLayout.run {
|
|
||||||
// setEnableRefresh(true)
|
|
||||||
// }
|
|
||||||
// val json = it.toJsonString()
|
|
||||||
// val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
|
|
||||||
// if (recordBean == null || recordBean.records.isNullOrEmpty()) {
|
|
||||||
//// toast("暂未搜索到相关菜品信息")
|
|
||||||
// if (pageNo == 1) {
|
|
||||||
// loadEmptyView()
|
|
||||||
// }
|
|
||||||
// return@get
|
|
||||||
// }
|
|
||||||
// val records = recordBean.records
|
|
||||||
// if (pageNo == 1) {
|
|
||||||
// list.clear()
|
|
||||||
// }
|
|
||||||
// list.addAll(records!!)
|
|
||||||
// recordAdapter.notifyDataSetChanged()
|
|
||||||
// val isLoadMoreEnable = records.size >= pageSize
|
|
||||||
// binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
|
|
||||||
// if (isLoadMoreEnable) {
|
|
||||||
// pageNo++
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// doFailure = { code, msg ->
|
|
||||||
// binding.refreshLayout.run {
|
|
||||||
// setEnableRefresh(true)
|
|
||||||
// }
|
|
||||||
// toast(msg)
|
|
||||||
// delayDismissLoading()
|
|
||||||
// finishRefresh()
|
|
||||||
// if (pageNo == 1) {
|
|
||||||
// loadEmptyView()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
|
|
||||||
val map = mutableMapOf<String, Any>(
|
val map = mutableMapOf<String, Any>(
|
||||||
"foodName" to input.trim(),
|
"foodName" to input.trim(),
|
||||||
"pageNum" to pageNo,
|
"pageNum" to pageNo,
|
||||||
@@ -202,40 +177,27 @@ class FoodSearchActivity : BaseActivity() {
|
|||||||
"placeId" to BaseApp.canteenId,
|
"placeId" to BaseApp.canteenId,
|
||||||
"dinnerType" to getDinnerTypeText()
|
"dinnerType" to getDinnerTypeText()
|
||||||
)
|
)
|
||||||
netViewModel.searchFoodList(
|
netViewModel.searchFoodList(param = map)
|
||||||
param = map,
|
}
|
||||||
onSuccess = {
|
|
||||||
delayDismissLoading()
|
/**
|
||||||
finishRefresh()
|
* 处理搜索结果,填充列表或展示空视图
|
||||||
binding.refreshLayout.setEnableRefresh(true)
|
*/
|
||||||
if (it.isNullOrEmpty()) {
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
// toast("暂未搜索到相关菜品信息")
|
private fun handleSearchResult(records: MutableList<FoodRecord>?) {
|
||||||
if (pageNo == 1) {
|
delayDismissLoading()
|
||||||
loadEmptyView()
|
finishRefresh()
|
||||||
}
|
binding.refreshLayout.setEnableRefresh(true)
|
||||||
return@searchFoodList
|
if (records.isNullOrEmpty()) {
|
||||||
}
|
if (pageNo == 1) loadEmptyView()
|
||||||
if (pageNo == 1) {
|
return
|
||||||
list.clear()
|
}
|
||||||
}
|
if (pageNo == 1) list.clear()
|
||||||
list.addAll(it)
|
list.addAll(records)
|
||||||
recordAdapter.notifyDataSetChanged()
|
recordAdapter.notifyDataSetChanged()
|
||||||
val isLoadMoreEnable = it.size >= pageSize
|
val isLoadMoreEnable = records.size >= pageSize
|
||||||
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
|
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
|
||||||
if (isLoadMoreEnable) {
|
if (isLoadMoreEnable) pageNo++
|
||||||
pageNo++
|
|
||||||
}
|
|
||||||
}, onFailure = { code, msg ->
|
|
||||||
binding.refreshLayout.run {
|
|
||||||
setEnableRefresh(true)
|
|
||||||
}
|
|
||||||
toast(msg)
|
|
||||||
delayDismissLoading()
|
|
||||||
finishRefresh()
|
|
||||||
if (pageNo == 1) {
|
|
||||||
loadEmptyView()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun finishRefresh() {
|
private fun finishRefresh() {
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
@@ -27,6 +30,7 @@ import com.shuwei.dish.match.utils.ext.visible
|
|||||||
import com.shuwei.dish.match.viewmodel.AppViewModel
|
import com.shuwei.dish.match.viewmodel.AppViewModel
|
||||||
import com.shuwei.dish.match.viewmodel.factory.AppFactory
|
import com.shuwei.dish.match.viewmodel.factory.AppFactory
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SamplingListActivity : BaseActivity() {
|
class SamplingListActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -47,10 +51,45 @@ class SamplingListActivity : BaseActivity() {
|
|||||||
initViewModel()
|
initViewModel()
|
||||||
initRecyclerView()
|
initRecyclerView()
|
||||||
addViewListener()
|
addViewListener()
|
||||||
|
initObserver()
|
||||||
requestData(true)
|
requestData(true)
|
||||||
onBackPressedDispatcher.addCallback(this) { }
|
onBackPressedDispatcher.addCallback(this) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集 cookFoodListState,将本地烹饪中数据渲染到列表
|
||||||
|
*/
|
||||||
|
private fun initObserver() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
appViewModel.cookFoodListState.collect { cookFoodEntities ->
|
||||||
|
delayDismissLoading()
|
||||||
|
if (cookFoodEntities.isNullOrEmpty()) {
|
||||||
|
loadEmptyView()
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
val tempList = mutableListOf<FoodRecord>()
|
||||||
|
cookFoodEntities.sortedByDescending { DateTimeUtil.convert(dateStr = it.createTime) }
|
||||||
|
.forEach { entity ->
|
||||||
|
tempList.add(FoodRecord().apply {
|
||||||
|
foodId = entity.foodId
|
||||||
|
foodName = entity.foodName
|
||||||
|
cookMode = 1
|
||||||
|
isCooking = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
list.clear()
|
||||||
|
list.addAll(tempList)
|
||||||
|
dishAdapter.notifyDataSetChanged()
|
||||||
|
binding.refreshLayout.run {
|
||||||
|
setEnableRefresh(false)
|
||||||
|
setEnableLoadMore(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun setTitleBar() {
|
private fun setTitleBar() {
|
||||||
setTitleBar(titleBarAction = {
|
setTitleBar(titleBarAction = {
|
||||||
@@ -78,8 +117,8 @@ class SamplingListActivity : BaseActivity() {
|
|||||||
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
|
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
|
fun getCookFoodList() {
|
||||||
appViewModel.getCookFoodList(cookMode = 1) { action(it) }
|
appViewModel.getCookFoodList(cookMode = 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCookFoodAndGoods(foodId: String, action: () -> Unit) {
|
fun deleteCookFoodAndGoods(foodId: String, action: () -> Unit) {
|
||||||
@@ -298,32 +337,7 @@ class SamplingListActivity : BaseActivity() {
|
|||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun refreshLocalData() {
|
fun refreshLocalData() {
|
||||||
showLoading()
|
showLoading()
|
||||||
//查询本地数据
|
getCookFoodList()
|
||||||
getCookFoodList { cookFoodEntities ->
|
|
||||||
delayDismissLoading()
|
|
||||||
if (cookFoodEntities.isNullOrEmpty()) {
|
|
||||||
loadEmptyView()
|
|
||||||
return@getCookFoodList
|
|
||||||
}
|
|
||||||
val tempList = mutableListOf<FoodRecord>()
|
|
||||||
|
|
||||||
cookFoodEntities.sortedByDescending { DateTimeUtil.convert(dateStr = it.createTime) }
|
|
||||||
.forEach { entity ->
|
|
||||||
tempList.add(FoodRecord().apply {
|
|
||||||
foodId = entity.foodId
|
|
||||||
foodName = entity.foodName
|
|
||||||
cookMode = 1
|
|
||||||
isCooking = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
list.clear()
|
|
||||||
list.addAll(tempList)
|
|
||||||
dishAdapter.notifyDataSetChanged()
|
|
||||||
binding.refreshLayout.run {
|
|
||||||
setEnableRefresh(false)
|
|
||||||
setEnableLoadMore(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestData(isCooking: Boolean) {
|
private fun requestData(isCooking: Boolean) {
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import com.shuwei.dish.match.base.BaseApp
|
|||||||
import com.shuwei.dish.match.databinding.ActivitySelectDishBinding
|
import com.shuwei.dish.match.databinding.ActivitySelectDishBinding
|
||||||
import com.shuwei.dish.match.db.AppRepository
|
import com.shuwei.dish.match.db.AppRepository
|
||||||
import com.shuwei.dish.match.dialog.CommonDialog
|
import com.shuwei.dish.match.dialog.CommonDialog
|
||||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
|
||||||
import com.shuwei.dish.match.ui.fragment.DishListFragment
|
import com.shuwei.dish.match.ui.fragment.DishListFragment
|
||||||
import com.shuwei.dish.match.utils.KeyboardUtil
|
import com.shuwei.dish.match.utils.KeyboardUtil
|
||||||
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
|
||||||
@@ -32,7 +30,8 @@ class SelectDishActivity : BaseActivity() {
|
|||||||
|
|
||||||
private lateinit var binding: ActivitySelectDishBinding
|
private lateinit var binding: ActivitySelectDishBinding
|
||||||
|
|
||||||
private lateinit var appViewModel: AppViewModel
|
lateinit var appViewModel: AppViewModel
|
||||||
|
private set
|
||||||
|
|
||||||
private var dinnerType: String = "1"
|
private var dinnerType: String = "1"
|
||||||
|
|
||||||
@@ -72,8 +71,8 @@ class SelectDishActivity : BaseActivity() {
|
|||||||
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
|
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
|
fun getCookFoodList() {
|
||||||
appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType) { action(it) }
|
appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var configFinished = false
|
private var configFinished = false
|
||||||
@@ -189,16 +188,8 @@ class SelectDishActivity : BaseActivity() {
|
|||||||
isVisible = false
|
isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun getFoodList(
|
public fun getFoodList(param: MutableMap<String, Any>) {
|
||||||
param: MutableMap<String, Any>,
|
netViewModel.searchFoodList(param = param)
|
||||||
onSuccess: (MutableList<FoodRecord>?) -> Unit,
|
|
||||||
onFailure: (String, String) -> Unit
|
|
||||||
) {
|
|
||||||
netViewModel.searchFoodList(
|
|
||||||
param = param,
|
|
||||||
onSuccess = onSuccess,
|
|
||||||
onFailure = onFailure
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,9 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.chad.library.adapter4.util.setOnDebouncedItemClick
|
import com.chad.library.adapter4.util.setOnDebouncedItemClick
|
||||||
@@ -13,7 +16,9 @@ import com.shuwei.dish.match.base.BaseApp
|
|||||||
import com.shuwei.dish.match.base.BaseFragment
|
import com.shuwei.dish.match.base.BaseFragment
|
||||||
import com.shuwei.dish.match.databinding.FragmentDishListBinding
|
import com.shuwei.dish.match.databinding.FragmentDishListBinding
|
||||||
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
|
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
|
||||||
|
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
import com.shuwei.dish.match.entity.FoodRecord
|
||||||
|
import com.shuwei.dish.match.net.UiState
|
||||||
import com.shuwei.dish.match.ui.PrepareCookActivity
|
import com.shuwei.dish.match.ui.PrepareCookActivity
|
||||||
import com.shuwei.dish.match.ui.SelectDishActivity
|
import com.shuwei.dish.match.ui.SelectDishActivity
|
||||||
import com.shuwei.dish.match.ui.SubmitFoodActivity
|
import com.shuwei.dish.match.ui.SubmitFoodActivity
|
||||||
@@ -21,6 +26,8 @@ import com.shuwei.dish.match.utils.SwipeCallback
|
|||||||
import com.shuwei.dish.match.utils.ext.startActivity
|
import com.shuwei.dish.match.utils.ext.startActivity
|
||||||
import com.shuwei.dish.match.utils.ext.toast
|
import com.shuwei.dish.match.utils.ext.toast
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
||||||
|
|
||||||
@@ -108,6 +115,7 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
|||||||
itemTouchHelper.attachToRecyclerView(this)
|
itemTouchHelper.attachToRecyclerView(this)
|
||||||
}
|
}
|
||||||
addViewListener()
|
addViewListener()
|
||||||
|
initObserver()
|
||||||
|
|
||||||
pageNo = 1
|
pageNo = 1
|
||||||
activity.showLoading()
|
activity.showLoading()
|
||||||
@@ -115,6 +123,39 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
|||||||
binding.refreshLayout.setEnableRefresh(true)
|
binding.refreshLayout.setEnableRefresh(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用 combine 同时监听网络结果与本地数据,网络成功时统一合并渲染
|
||||||
|
* pageNo > 1(加载更多)时 cookFoodListState 不参与合并,直接追加网络数据
|
||||||
|
*/
|
||||||
|
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()
|
||||||
|
if (pageNo == 1) loadEmptyView()
|
||||||
|
activity.delayDismissLoading()
|
||||||
|
}
|
||||||
|
is UiState.Idle -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addViewListener() {
|
private fun addViewListener() {
|
||||||
binding.refreshLayout.run {
|
binding.refreshLayout.run {
|
||||||
setEnableRefresh(true)
|
setEnableRefresh(true)
|
||||||
@@ -147,72 +188,15 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
|||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@Suppress("unchecked_cast")
|
@Suppress("unchecked_cast")
|
||||||
fun getDishList() {
|
fun getDishList() {
|
||||||
activity.showLoading()
|
val param = mutableMapOf<String, Any>(
|
||||||
activity.getFoodList(
|
"pageNum" to pageNo,
|
||||||
param = mutableMapOf(
|
"pageSize" to pageSize,
|
||||||
"pageNum" to pageNo,
|
"placeId" to BaseApp.canteenId,
|
||||||
"pageSize" to pageSize,
|
"dinnerType" to getDinnerTypeText()
|
||||||
"placeId" to BaseApp.canteenId,
|
|
||||||
"dinnerType" to getDinnerTypeText()
|
|
||||||
),
|
|
||||||
onSuccess = { records ->
|
|
||||||
activity.delayDismissLoading()
|
|
||||||
finishRefresh()
|
|
||||||
loadDishList(records)
|
|
||||||
},
|
|
||||||
onFailure = { code, msg ->
|
|
||||||
if (isAdded.not()) {
|
|
||||||
return@getFoodList
|
|
||||||
}
|
|
||||||
toast(msg)
|
|
||||||
finishRefresh()
|
|
||||||
if (pageNo == 1) {
|
|
||||||
loadEmptyView()
|
|
||||||
}
|
|
||||||
activity.delayDismissLoading()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
// val map = mapOf(
|
// 网络与本地并行触发,pageNo > 1 时不重复查询本地数据
|
||||||
//// "foodName" to input,
|
activity.getFoodList(param = param)
|
||||||
// "dinnerType" to getDinnerTypeText(),
|
if (pageNo == 1) activity.getCookFoodList()
|
||||||
// "pageNo" to "$pageNo",
|
|
||||||
// "pageSize" to "$pageSize",
|
|
||||||
// "canteenId" to BaseApp.canteenId
|
|
||||||
// )
|
|
||||||
// val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
|
|
||||||
// append("?")
|
|
||||||
// map.forEach { (key, value) -> append("$key=$value&") }
|
|
||||||
// }
|
|
||||||
// sb.deleteCharAt(sb.length - 1)
|
|
||||||
// HttpUtil.get(
|
|
||||||
// url = sb.toString(),
|
|
||||||
// doSuccess = { data ->
|
|
||||||
// binding.refreshLayout.run {
|
|
||||||
// setEnableRefresh(true)
|
|
||||||
// }
|
|
||||||
// activity.delayDismissLoading()
|
|
||||||
// finishRefresh()
|
|
||||||
// val json = data.toJsonString()
|
|
||||||
// val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
|
|
||||||
// loadDishList(recordBean)
|
|
||||||
// }, doFailure = { code, msg ->
|
|
||||||
// try {
|
|
||||||
// if (isAdded.not()) {
|
|
||||||
// return@get
|
|
||||||
// }
|
|
||||||
// binding.refreshLayout.run {
|
|
||||||
// setEnableRefresh(true)
|
|
||||||
// }
|
|
||||||
// toast(msg)
|
|
||||||
// finishRefresh()
|
|
||||||
// if (pageNo == 1) {
|
|
||||||
// loadEmptyView()
|
|
||||||
// }
|
|
||||||
// activity.delayDismissLoading()
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// e.printStackTrace()
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var emptyViewBinding: LayoutEmptyViewBinding? = null
|
private var emptyViewBinding: LayoutEmptyViewBinding? = null
|
||||||
@@ -248,64 +232,65 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将网络数据与本地烹饪中数据合并后渲染列表
|
||||||
|
* pageNo > 1 时忽略本地数据,直接追加网络数据,避免 combine 带入旧的本地状态
|
||||||
|
*/
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun refreshLocalData(list: MutableList<FoodRecord>) {
|
private fun loadAndMergeDishList(
|
||||||
activity.getCookFoodList { cookFoodEntities ->
|
records: MutableList<FoodRecord>?,
|
||||||
if (cookFoodEntities.isNullOrEmpty()) {
|
localList: MutableList<CookFoodEntity>?
|
||||||
return@getCookFoodList
|
) {
|
||||||
}
|
|
||||||
val tempList = mutableListOf<FoodRecord>()
|
|
||||||
|
|
||||||
cookFoodEntities.forEachIndexed { index, entity ->
|
|
||||||
val food = list.firstOrNull { it.foodId == entity.foodId }
|
|
||||||
if (food != null) {
|
|
||||||
food.isCooking = true
|
|
||||||
food.sort = index
|
|
||||||
food.dinnerType = entity.dinnerType ?: "0"
|
|
||||||
tempList.add(food)
|
|
||||||
} else {
|
|
||||||
//当前查询到的list不包括本地数据id,可能数据在很多页以后了,暂时只能直接构造数据
|
|
||||||
tempList.add(
|
|
||||||
FoodRecord(
|
|
||||||
foodId = entity.foodId,
|
|
||||||
foodName = entity.foodName,
|
|
||||||
sort = index,
|
|
||||||
dinnerType = entity.dinnerType ?: "0",
|
|
||||||
isCooking = true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.removeAll(tempList)
|
|
||||||
list.addAll(0, tempList)
|
|
||||||
dishAdapter.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
private fun loadDishList(records: MutableList<FoodRecord>?) {
|
|
||||||
try {
|
try {
|
||||||
if (isAdded.not()) {
|
if (isAdded.not()) return
|
||||||
|
// pageNo > 1 时不参与本地合并,避免 combine 带入旧的本地状态
|
||||||
|
val effectiveLocalList = if (pageNo == 1) localList else null
|
||||||
|
if (records.isNullOrEmpty() && effectiveLocalList.isNullOrEmpty()) {
|
||||||
|
if (pageNo == 1) loadEmptyView()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (records.isNullOrEmpty()) {
|
if (pageNo == 1) list.clear()
|
||||||
//toast("暂未搜索到相关菜品信息")
|
|
||||||
if (pageNo == 1) {
|
val mergedList = records?.toMutableList() ?: mutableListOf()
|
||||||
loadEmptyView()
|
|
||||||
|
// 仅 pageNo == 1 时才有本地数据参与合并
|
||||||
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
mergedList.addAll(0, cookingItems)
|
||||||
}
|
}
|
||||||
if (pageNo == 1) {
|
|
||||||
list.clear()
|
if (pageNo >= 2 && !localList.isNullOrEmpty()) {
|
||||||
|
//第2页起,从mergedList中移除localList中的同foodId数据
|
||||||
|
val localIds = localList.map { it.foodId }.toHashSet()
|
||||||
|
mergedList.removeAll { it.foodId in localIds }
|
||||||
}
|
}
|
||||||
list.addAll(records)
|
|
||||||
|
list.addAll(mergedList)
|
||||||
dishAdapter.notifyDataSetChanged()
|
dishAdapter.notifyDataSetChanged()
|
||||||
val isLoadMoreEnable = records.size >= pageSize
|
val isLoadMoreEnable = (records?.size ?: 0) >= pageSize
|
||||||
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
|
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
|
||||||
if (isLoadMoreEnable) {
|
if (isLoadMoreEnable) pageNo++
|
||||||
pageNo++
|
|
||||||
}
|
|
||||||
refreshLocalData(list)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import com.shuwei.dish.match.db.AppRepository
|
|||||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||||
import com.shuwei.dish.match.entity.SeasoningEntity
|
import com.shuwei.dish.match.entity.SeasoningEntity
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
@@ -26,14 +29,15 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val _cookFoodListState = MutableStateFlow<MutableList<CookFoodEntity>?>(null)
|
||||||
|
val cookFoodListState: StateFlow<MutableList<CookFoodEntity>?> = _cookFoodListState.asStateFlow()
|
||||||
|
|
||||||
fun getCookFoodList(
|
fun getCookFoodList(
|
||||||
cookMode: Int,
|
cookMode: Int,
|
||||||
dinnerType: String = "0",
|
dinnerType: String = "0"
|
||||||
action: (MutableList<CookFoodEntity>?) -> Unit
|
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val data = rep.getCookFoodList(cookMode, dinnerType)
|
_cookFoodListState.value = rep.getCookFoodList(cookMode, dinnerType)
|
||||||
action(data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user