12 Commits
Author SHA1 Message Date
lvmengandClaude Sonnet 4.6 f3d57177b8 feat(activity): 制作/提交按钮增加二次确认弹窗,调整弹窗标题颜色及列表项背景属性
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 17:41:26 +08:00
lvmengandClaude Sonnet 4.6 267628411a fix(viewmodel): 修复切换 tab 返回后数据消失及 loading 不消失的问题
切换 tab 时重置对方的 StateFlow 状态为初始值,避免 repeatOnLifecycle
重启后 replay 残留状态导致 UI 异常覆盖或 loading 无法关闭

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 17:41:12 +08:00
lvmeng 58fb78c7bd feat(activity): 新增已采集食材列表页,支持搜索、删除及空视图展示 2026-04-23 16:51:57 +08:00
lvmeng 9f0671410f feat(ui): 将采集页红色主题图标统一替换为绿色 2026-04-23 14:39:49 +08:00
lvmengandClaude Sonnet 4.6 0243fe0dc8 refactor(viewmodel): 将回调方式统一改为 StateFlow + UiState,消除竞态问题
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 14:01:24 +08:00
lvmengandClaude Sonnet 4.6 df0580d68f refactor(viewmodel): 统一 DbViewModel 初始化并拆分 DAO 结构
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 11:58:14 +08:00
lvmengandClaude Sonnet 4.6 deab289747 refactor(rename): 将 AppRepository 重命名为 DbRepository,AppViewModel 重命名为 DbViewModel
- DatabaseRepository.kt → DbRepository.kt,类名 AppRepository → DbRepository
- AppViewModel.kt → DbViewModel.kt,类名 AppViewModel → DbViewModel
- VMFactory 构造参数与创建类型同步更新
- 所有引用文件(UI 层、Fragment、ViewModel 工厂)批量更新 import 与使用处

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 11:06:59 +08:00
lvmeng debc7a0740 refactor(viewmodel): 封装调料槽位读写至 AppViewModel,消除页面直接调用 DAO 的情况 2026-04-23 10:54:59 +08:00
lvmeng 441cffe4da fix(submit): 移除调料数据为空的检查限制
- 注释掉调味料数据为空时的验证逻辑
- 允许在没有调料信息的情况下继续提交流程
- 保持原有的数据转换和追加功能不变
2026-04-23 10:31:02 +08:00
lvmeng 3581975ab0 refactor(viewmodel): 新增 seasoningSlotState 替代 loadSeasoning,将 getSamplingList 迁移至 StateFlow 2026-04-23 10:29:17 +08:00
lvmeng 9e99e6ef6d refactor(viewmodel): 将 searchFoodList/getCookFoodList 迁移至 StateFlow,DishListFragment 改用 combine 并行合并数据并修复分页本地数据混入问题 2026-04-23 09:54:34 +08:00
lvmengandClaude Sonnet 4.6 16fe64e9fe refactor(viewmodel): 修复 loadSeasoning 防重入缺陷并移除 BaseActivity 网络包装方法
- AppViewModel.loadSeasoning 用 Mutex 替换 isProcessing 标志位,避免异常时状态永久锁死
- 删除 BaseActivity 中 queryGoodsList / querySeasoningList 包装方法,减少隐式依赖
- FoodSearchDialog / SeasoningSearchDialog 改为直接调用 activity.netViewModel.xxx()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 08:56:13 +08:00
55 changed files with 1638 additions and 1312 deletions
+5
View File
@@ -115,6 +115,11 @@
android:theme="@style/Theme.DishMatch.NoSplash" android:theme="@style/Theme.DishMatch.NoSplash"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" /> tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name="com.shuwei.dish.match.ui.CollectedFoodActivity"
android:theme="@style/Theme.DishMatch.NoSplash"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity <activity
android:name="com.shuwei.dish.match.ui.SlaveActivity" android:name="com.shuwei.dish.match.ui.SlaveActivity"
android:theme="@style/Theme.DishMatch.NoSplash" android:theme="@style/Theme.DishMatch.NoSplash"
@@ -0,0 +1,31 @@
package com.shuwei.dish.match.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.databinding.ListItemCollectedFoodBinding
import com.shuwei.dish.match.entity.CollectedFoodItem
/**
* 已采集食材列表适配器
* 展示食材名称和已采集数量,支持删除操作
*/
class CollectedFoodAdapter(list: MutableList<CollectedFoodItem>) :
BaseQuickAdapter<CollectedFoodItem, CollectedFoodAdapter.VH>(list) {
/** ViewHolder,持有列表项 ViewBinding */
inner class VH(val binding: ListItemCollectedFoodBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val binding = ListItemCollectedFoodBinding.inflate(LayoutInflater.from(context), parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: CollectedFoodItem?) {
item ?: return
holder.binding.tvFoodName.text = item.foodName.split("WP").first()
holder.binding.tvCollectCount.text = "已采集${item.collectCount}"
}
}
@@ -21,9 +21,8 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.shuwei.dish.match.R import com.shuwei.dish.match.R
import com.shuwei.dish.match.databinding.ActivityBaseBinding import com.shuwei.dish.match.databinding.ActivityBaseBinding
import com.shuwei.dish.match.dialog.Loading import com.shuwei.dish.match.dialog.Loading
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.net.NetViewModel import com.shuwei.dish.match.net.NetViewModel
import com.shuwei.dish.match.db.DbViewModel
import com.shuwei.dish.match.ui.InitActivity import com.shuwei.dish.match.ui.InitActivity
import com.shuwei.dish.match.utils.ActivityManager import com.shuwei.dish.match.utils.ActivityManager
import com.shuwei.dish.match.utils.ext.dp import com.shuwei.dish.match.utils.ext.dp
@@ -41,8 +40,6 @@ open class BaseActivity : AppCompatActivity() {
private lateinit var binding: ActivityBaseBinding private lateinit var binding: ActivityBaseBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// 提前隐藏系统栏,避免窗口创建时导航栏闪烁 // 提前隐藏系统栏,避免窗口创建时导航栏闪烁
@@ -183,22 +180,7 @@ open class BaseActivity : AppCompatActivity() {
val netViewModel: NetViewModel by viewModels() val netViewModel: NetViewModel by viewModels()
fun queryGoodsList( val appViewModel: DbViewModel by viewModels()
param: MutableMap<String, Any>,
onSuccess: (MutableList<CookFoodGoodsEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
netViewModel.queryGoodsList(param, onSuccess, onFailure)
}
fun querySeasoningList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<SeasoningEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
netViewModel.querySeasoningList(param, onSuccess, onFailure)
}
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var activityCallback: ((intent: Intent?) -> Unit)? = null private var activityCallback: ((intent: Intent?) -> Unit)? = null
@@ -6,7 +6,9 @@ import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import com.shuwei.dish.match.db.dao.AppDao import com.shuwei.dish.match.db.dao.CookFoodDao
import com.shuwei.dish.match.db.dao.CookFoodGoodsDao
import com.shuwei.dish.match.db.dao.SeasoningDao
import com.shuwei.dish.match.db.dao.SeasoningSlotDao import com.shuwei.dish.match.db.dao.SeasoningSlotDao
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
@@ -27,11 +29,12 @@ import com.shuwei.dish.match.entity.SeasoningSlotEntity
exportSchema = true exportSchema = true
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
// abstract fun seasoningDao(): SeasoningDao
// abstract fun cookFoodDao(): CookFoodDao
// abstract fun cookFoodGoodsDao(): CookFoodGoodsDao
abstract fun appDao(): AppDao abstract fun cookFoodDao(): CookFoodDao
abstract fun cookFoodGoodsDao(): CookFoodGoodsDao
abstract fun seasoningDao(): SeasoningDao
abstract fun seasoningSlotDao(): SeasoningSlotDao abstract fun seasoningSlotDao(): SeasoningSlotDao
@@ -1,110 +1,103 @@
package com.shuwei.dish.match.db package com.shuwei.dish.match.db
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.db.dao.AppDao
import com.shuwei.dish.match.db.dao.SeasoningSlotDao
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 com.shuwei.dish.match.entity.SeasoningSlotEntity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class AppRepository(val appDao: AppDao, val seasoningSlotDao: SeasoningSlotDao) { class DbRepository {
private val db = BaseApp.instance!!.database
suspend fun insertCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) { suspend fun insertCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) {
appDao.insertCookFood(item) db.cookFoodDao().insertCookFood(item)
} }
suspend fun updateCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) { suspend fun updateCookFood(item: CookFoodEntity) = withContext(Dispatchers.IO) {
appDao.updateCookFood(item) db.cookFoodDao().updateCookFood(item)
} }
suspend fun getCookFoodById(foodId: String, cookMode: Int) = withContext(Dispatchers.IO) { suspend fun getCookFoodById(foodId: String, cookMode: Int) = withContext(Dispatchers.IO) {
appDao.getCookFoodById(BaseApp.canteenId, foodId, cookMode) db.cookFoodDao().getCookFoodById(BaseApp.canteenId, foodId, cookMode)
} }
suspend fun getCookFoodList(cookMode: Int, dinnerType: String) = withContext(Dispatchers.IO) { suspend fun getCookFoodList(cookMode: Int, dinnerType: String) = withContext(Dispatchers.IO) {
appDao.getCookFoodList(BaseApp.canteenId, cookMode, dinnerType) db.cookFoodDao().getCookFoodList(BaseApp.canteenId, cookMode, dinnerType)
} }
suspend fun countCookFood(cookMode: Int) = withContext(Dispatchers.IO) { suspend fun countCookFood(cookMode: Int) = withContext(Dispatchers.IO) {
appDao.countCookFood(BaseApp.canteenId, cookMode) db.cookFoodDao().countCookFood(BaseApp.canteenId, cookMode)
} }
suspend fun deleteCookFood(foodId: String, cookMode: Int) = withContext(Dispatchers.IO) { suspend fun deleteCookFood(foodId: String, cookMode: Int) = withContext(Dispatchers.IO) {
appDao.deleteCookFood(BaseApp.canteenId, foodId, cookMode) db.cookFoodDao().deleteCookFood(BaseApp.canteenId, foodId, cookMode)
} }
suspend fun deleteCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) { suspend fun deleteCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) {
appDao.deleteCookFoodGoodsList(pid, foodId) db.cookFoodGoodsDao().deleteCookFoodGoodsList(pid, foodId)
} }
//-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------
// suspend fun insertGoods(item: CookFoodGoodsEntity) = withContext(Dispatchers.IO) {
// appDao.insertGoods(item)
// }
suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>) = suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
appDao.insertGoodsList(items) db.cookFoodGoodsDao().insertGoodsList(items)
} }
// suspend fun updateGoods(item: CookFoodGoodsEntity) = withContext(Dispatchers.IO) {
// appDao.updateGoods(item)
// }
suspend fun getCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) { suspend fun getCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) {
appDao.getCookFoodGoodsList(pid, foodId) db.cookFoodGoodsDao().getCookFoodGoodsList(pid, foodId)
} }
suspend fun getCookFoodGoodsListByType( suspend fun getCookFoodGoodsListByType(
foodId: String, foodId: String,
materialType: Int materialType: Int
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
appDao.getCookFoodGoodsListByType(foodId, materialType) db.cookFoodGoodsDao().getCookFoodGoodsListByType(foodId, materialType)
} }
//-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------
suspend fun getSeasoningById(id: Long) = withContext(Dispatchers.IO) { suspend fun getSeasoningById(id: Long) = withContext(Dispatchers.IO) {
appDao.getSeasoningById(id) db.seasoningDao().getSeasoningById(id)
} }
suspend fun getSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) { suspend fun getSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) {
appDao.getSeasoningBySort(sort) db.seasoningDao().getSeasoningBySort(sort)
} }
suspend fun getSeasoningByGoodsId(goodsId: Int) = withContext(Dispatchers.IO) { suspend fun getSeasoningByGoodsId(goodsId: Int) = withContext(Dispatchers.IO) {
appDao.getSeasoningByGoodsId(goodsId) db.seasoningDao().getSeasoningByGoodsId(goodsId)
} }
suspend fun getAllStream() = withContext(Dispatchers.IO) { suspend fun getAllStream() = withContext(Dispatchers.IO) {
appDao.getAllStream() db.seasoningDao().getAllStream()
} }
suspend fun search(query: String) = withContext(Dispatchers.IO) { suspend fun search(query: String) = withContext(Dispatchers.IO) {
appDao.search(query) db.seasoningDao().search(query)
} }
suspend fun clearAllSeasoning() = withContext(Dispatchers.IO) { suspend fun clearAllSeasoning() = withContext(Dispatchers.IO) {
appDao.clearAllSeasoning() db.seasoningDao().clearAllSeasoning()
} }
suspend fun deleteSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) { suspend fun deleteSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) {
appDao.deleteSeasoningBySort(sort) db.seasoningDao().deleteSeasoningBySort(sort)
} }
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>) = suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
appDao.insertSeasoningList(items) db.seasoningDao().insertSeasoningList(items)
} }
suspend fun updateSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) { suspend fun updateSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
appDao.updateSeasoning(item) db.seasoningDao().updateSeasoning(item)
} }
suspend fun insertSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) { suspend fun insertSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
appDao.insertSeasoning(item) db.seasoningDao().insertSeasoning(item)
} }
/** /**
@@ -113,7 +106,36 @@ class AppRepository(val appDao: AppDao, val seasoningSlotDao: SeasoningSlotDao)
* @return true 表示已有配置false 表示未配置 * @return true 表示已有配置false 表示未配置
*/ */
suspend fun hasSeasoningSlotConfig(deviceId: String) = withContext(Dispatchers.IO) { suspend fun hasSeasoningSlotConfig(deviceId: String) = withContext(Dispatchers.IO) {
seasoningSlotDao.queryByDeviceId(deviceId).isNotEmpty() db.seasoningSlotDao().queryByDeviceId(deviceId).isNotEmpty()
}
/**
* 查询所有设备的调料槽位配置
*/
suspend fun getAllSeasoningSlots() = withContext(Dispatchers.IO) {
db.seasoningSlotDao().queryAll()
}
/**
* 查询指定设备的调料槽位配置
* @param deviceId 目标设备 ID
*/
suspend fun getSeasoningSlotsByDeviceId(deviceId: String) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().queryByDeviceId(deviceId)
}
/**
* 插入或更新单个调料槽位配置
*/
suspend fun upsertSeasoningSlot(slot: SeasoningSlotEntity) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().upsert(slot)
}
/**
* 批量插入或更新调料槽位配置
*/
suspend fun upsertAllSeasoningSlots(slots: List<SeasoningSlotEntity>) = withContext(Dispatchers.IO) {
db.seasoningSlotDao().upsertAll(slots)
} }
} }
// //
@@ -0,0 +1,290 @@
package com.shuwei.dish.match.db
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.dish.match.entity.CollectedFoodItem
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.objbox.ObjectBox
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.SeasoningSlotEntity
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class DbViewModel : ViewModel() {
private val rep = DbRepository()
fun updateCookFood(entity: CookFoodEntity) {
viewModelScope.launch {
rep.updateCookFood(entity)
}
}
private val _cookFoodDetailState = MutableStateFlow<CookFoodEntity?>(null)
val cookFoodDetailState: StateFlow<CookFoodEntity?> = _cookFoodDetailState.asStateFlow()
fun getCookFoodById(foodId: String, cookMode: Int) {
viewModelScope.launch {
_cookFoodDetailState.value = rep.getCookFoodById(foodId, cookMode)
}
}
private val _cookFoodListState = MutableStateFlow<MutableList<CookFoodEntity>?>(null)
val cookFoodListState: StateFlow<MutableList<CookFoodEntity>?> = _cookFoodListState.asStateFlow()
fun resetCookFoodList() {
_cookFoodListState.value = null
}
fun getCookFoodList(
cookMode: Int,
dinnerType: String = "0"
) {
viewModelScope.launch {
_cookFoodListState.value = rep.getCookFoodList(cookMode, dinnerType)
}
}
suspend fun countCookFood(cookMode: Int): Int {
return rep.countCookFood(cookMode)
}
// fun saveCookFood(cookMode: Int, entity: CookFoodEntity) {
// viewModelScope.launch {
// val data = rep.getCookFoodById(entity.foodId, cookMode)
// if (data == null) {
// rep.insertCookFood(entity)
// return@launch
// }
// rep.updateCookFood(entity)
// }
// }
suspend fun saveCookFoodAndGoods(
cookMode: Int,
entity: CookFoodEntity,
list: MutableList<CookFoodGoodsEntity>?
) {
val data = rep.getCookFoodById(entity.foodId, cookMode)
if (data != null) {
// 存在数据先删除
rep.deleteCookFood(entity.foodId, cookMode)
rep.deleteCookFoodGoodsList(data.id, entity.foodId)
}
// 重新保存数据
rep.insertCookFood(entity)
val newData = rep.getCookFoodById(entity.foodId, cookMode)
list?.let { it ->
it.forEach { goods ->
goods.id = 0
goods.pid = newData?.id ?: 0
}
rep.insertGoodsList(it)
}
}
suspend fun deleteCookFoodAndGoods(cookMode: Int, foodId: String) {
val data = rep.getCookFoodById(foodId, cookMode)
if (data != null) {
rep.deleteCookFood(foodId = foodId, cookMode = cookMode)
rep.deleteCookFoodGoodsList(pid = data.id, foodId = foodId)
}
}
//--------------------------------------------------------------------------------------------------------
suspend fun getCookFoodGoodsList(
foodId: String,
cookMode: Int
): MutableList<CookFoodGoodsEntity> {
val cookFood = rep.getCookFoodById(foodId, cookMode)
return if (cookFood != null) rep.getCookFoodGoodsList(pid = cookFood.id, foodId = cookFood.foodId)
else mutableListOf()
}
// fun updateGoods(entity: CookFoodGoodsEntity) {
// viewModelScope.launch {
// rep.updateGoods(entity)
// }
// }
// fun saveCookFoodGoods(list: MutableList<CookFoodGoodsEntity>) {
// viewModelScope.launch {
// val foodId = list[0].foodId
// val data: MutableList<CookFoodGoodsEntity>? =
// rep.getCookFoodGoodsList(foodId!!)
// if (data.isNullOrEmpty()) {
// rep.insertGoodsList(list)
// return@launch
// }
// val localDbSeasoningList = rep.getCookFoodGoodsListByType(foodId, 3)
// localDbSeasoningList.forEach {
// it.isDel = 1
// //更新本地调料数据为已删除的状态
// rep.updateGoods(it)
// }
// //筛选出调料数据保存,主辅材不会变更不需要再次处理
// val saveSeasoningList = list.filter { it.materialType == 3 }
// rep.insertGoodsList(saveSeasoningList.toMutableList())
// }
// }
//--------------------------------------------------------------------------------------------------------
// fun addSeasoning(list: MutableList<SeasoningEntity>, callback: () -> Unit) {
// viewModelScope.launch {
// rep.deleteBatch()
// rep.insertSeasoningList(items = list)
// callback()
// }
// }
private val loadSeasoningMutex = Mutex()
/**
* 本地调料列表状态流
*/
private val _seasoningState = MutableStateFlow<MutableList<SeasoningEntity>>(mutableListOf())
val seasoningState: StateFlow<MutableList<SeasoningEntity>> = _seasoningState.asStateFlow()
fun loadSeasoning() {
if (loadSeasoningMutex.isLocked) return
viewModelScope.launch {
loadSeasoningMutex.withLock {
_seasoningState.value = rep.getAllStream()
}
}
}
/**
* 所有设备调料槽位配置的 UI 状态流
*/
private val _seasoningSlotState = MutableStateFlow<List<SeasoningSlotEntity>>(emptyList())
val seasoningSlotState: StateFlow<List<SeasoningSlotEntity>> = _seasoningSlotState.asStateFlow()
/**
* 查询所有调料槽位配置,更新 seasoningSlotState 并返回结果
* 声明为 suspend,调用方可在同一协程中直接使用返回值,无需等待 StateFlow 更新
*/
suspend fun loadSeasoningSlot(): List<SeasoningSlotEntity> {
val slots = rep.getAllSeasoningSlots()
_seasoningSlotState.value = slots
return slots
}
fun deleteSeasoningBySort(sort: Int) {
viewModelScope.launch {
rep.deleteSeasoningBySort(sort)
}
}
private val _clearAllSeasoningState = MutableStateFlow(false)
val clearAllSeasoningState: StateFlow<Boolean> = _clearAllSeasoningState.asStateFlow()
fun clearAllSeasoning() {
viewModelScope.launch {
rep.clearAllSeasoning()
_clearAllSeasoningState.value = true
}
}
private val _hasSeasoningSlotConfigState = MutableStateFlow(false)
val hasSeasoningSlotConfigState: StateFlow<Boolean> = _hasSeasoningSlotConfigState.asStateFlow()
/**
* 查询指定设备是否已配置调料槽位
* @param deviceId 目标设备 ID
*/
fun hasSeasoningSlotConfig(deviceId: String) {
viewModelScope.launch {
_hasSeasoningSlotConfigState.value = rep.hasSeasoningSlotConfig(deviceId)
}
}
/**
* 查询指定设备的调料槽位配置
*/
suspend fun getSeasoningSlotsByDeviceId(deviceId: String) =
rep.getSeasoningSlotsByDeviceId(deviceId)
/**
* 插入或更新单个调料槽位配置
*/
suspend fun upsertSeasoningSlot(slot: SeasoningSlotEntity) =
rep.upsertSeasoningSlot(slot)
/**
* 批量插入或更新调料槽位配置
*/
suspend fun upsertAllSeasoningSlots(slots: List<SeasoningSlotEntity>) =
rep.upsertAllSeasoningSlots(slots)
// fun updateAll(list: MutableList<SeasoningEntity>) {
// viewModelScope.launch {
// list.forEach {
// rep.updateSeasoning(it)
// }
// }
// }
fun saveSeasoning(entity: SeasoningEntity, block: () -> Unit) {
viewModelScope.launch {
val list = rep.getSeasoningBySort(entity.sort)
val size = list?.size?:0
if (size == 1) {
rep.updateSeasoning(entity.also { it.id = list!![0].id })
block()
return@launch
}
if (size > 1) {
list?.forEach {
rep.deleteSeasoningBySort(it.sort)
}
}
entity.id = 0
val id = rep.insertSeasoning(entity)
entity.id = id
block()
}
}
//--------------------------------------------------------------------------------------------------------
/**
* 已采集食材列表 UI 状态流
* 元素为按名称分组后的(名称,数量)数据
*/
private val _collectedFoodListState = MutableStateFlow<List<CollectedFoodItem>>(emptyList())
val collectedFoodListState: StateFlow<List<CollectedFoodItem>> = _collectedFoodListState.asStateFlow()
/**
* 加载已采集食材列表,按名称分组统计数量
* 使用属性查询只读 name 字段,不加载 foodVector,节省内存
* @param nameFilter 名称过滤关键词,为空时加载全部
*/
fun loadCollectedFoodList(nameFilter: String? = null) {
viewModelScope.launch {
val result = ObjectBox.queryFoodNameCounts(nameFilter)
_collectedFoodListState.value = result.map { (name, count) ->
CollectedFoodItem(foodName = name, collectCount = count)
}
}
}
/**
* 删除指定名称的所有采集记录,删除完成后刷新列表
* @param foodName 食材名称
* @param nameFilter 当前搜索关键词,用于删除后刷新列表
*/
fun removeCollectedFood(foodName: String, nameFilter: String? = null) {
viewModelScope.launch {
ObjectBox.remove(foodName)
loadCollectedFoodList(nameFilter)
}
}
//--------------------------------------------------------------------------------------------------------
}
@@ -1,92 +0,0 @@
package com.shuwei.dish.match.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface AppDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCookFood(item: CookFoodEntity): Long
@Update
suspend fun updateCookFood(item: CookFoodEntity)
@Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode")
suspend fun getCookFoodById(canteenId:String, foodId: String, cookMode: Int): CookFoodEntity?
@Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode AND dinnerType = :dinnerType ORDER BY createTime DESC")
suspend fun getCookFoodList(canteenId:String, cookMode: Int, dinnerType:String): MutableList<CookFoodEntity>?
@Query("SELECT count(1) FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode")
suspend fun countCookFood(canteenId:String, cookMode: Int): Int
@Query("DELETE FROM dm_cook_food WHERE canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode")
suspend fun deleteCookFood(canteenId:String, foodId: String, cookMode: Int)
//--------------------------------------------------------------------------------------------
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGoods(item: CookFoodGoodsEntity): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>): Array<Long>
@Update
suspend fun updateGoods(item: CookFoodGoodsEntity)
@Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND pid = :pid AND foodId = :foodId")
suspend fun getCookFoodGoodsList(pid:Long, foodId: String): MutableList<CookFoodGoodsEntity>
@Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId AND materialType = :materialType")
suspend fun getCookFoodGoodsListByType(
foodId: String,
materialType: Int
): MutableList<CookFoodGoodsEntity>
@Query("DELETE FROM dm_cook_food_goods WHERE pid = :pid AND foodId = :foodId")
suspend fun deleteCookFoodGoodsList(pid:Long, foodId: String)
//--------------------------------------------------------------------------------------------
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND id = :id")
suspend fun getSeasoningById(id: Long): SeasoningEntity?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
suspend fun getSeasoningBySort(sort: Int): MutableList<SeasoningEntity>?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId = :goodsId")
suspend fun getSeasoningByGoodsId(goodsId: Int): SeasoningEntity?
// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId != -1 AND goodsName != '' ORDER BY sort ASC")
// fun getAllStream(): Flow<MutableList<SeasoningEntity>>
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId != -1 AND goodsName != '' ORDER BY sort ASC")
fun getAllStream(): MutableList<SeasoningEntity>
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsName LIKE '%' || :query || '%'")
suspend fun search(query: String): MutableList<SeasoningEntity>
@Query("DELETE FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
suspend fun deleteSeasoningBySort(sort:Int): Int
@Query("DELETE FROM dm_seasoning WHERE isDel = 0")
suspend fun clearAllSeasoning(): Int
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>): Array<Long>
@Update
suspend fun updateSeasoning(item: SeasoningEntity)
@Insert
suspend fun insertSeasoning(item: SeasoningEntity): Long
//--------------------------------------------------------------------------------------------
}
@@ -1,20 +1,30 @@
//package com.shuwei.dish.match.db.dao package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao import androidx.room.Dao
//import androidx.room.Query import androidx.room.Insert
//import com.shuwei.dish.match.db.BaseDao import androidx.room.OnConflictStrategy
//import com.shuwei.dish.match.entity.CookFoodEntity import androidx.room.Query
// import androidx.room.Update
//@Dao import com.shuwei.dish.match.entity.CookFoodEntity
//interface CookFoodDao : BaseDao<CookFoodEntity> {
// @Dao
// @Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND foodId = :foodId AND cookMode = :cookMode") interface CookFoodDao {
// suspend fun getCookFoodById(foodId: String, cookMode:Int): CookFoodEntity?
// @Insert(onConflict = OnConflictStrategy.REPLACE)
// @Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND cookMode = :cookMode") suspend fun insertCookFood(item: CookFoodEntity): Long
// suspend fun getCookFoodList(cookMode:Int): MutableList<CookFoodEntity>?
// @Update
// @Query("SELECT count(1) FROM dm_cook_food WHERE isDel = 0 AND cookMode = :cookMode") suspend fun updateCookFood(item: CookFoodEntity)
// suspend fun countCookFood(cookMode:Int): Int
// @Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode")
//} suspend fun getCookFoodById(canteenId: String, foodId: String, cookMode: Int): CookFoodEntity?
@Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode AND dinnerType = :dinnerType ORDER BY createTime DESC")
suspend fun getCookFoodList(canteenId: String, cookMode: Int, dinnerType: String): MutableList<CookFoodEntity>?
@Query("SELECT count(1) FROM dm_cook_food WHERE isDel = 0 AND canteenId = :canteenId AND cookMode = :cookMode")
suspend fun countCookFood(canteenId: String, cookMode: Int): Int
@Query("DELETE FROM dm_cook_food WHERE canteenId = :canteenId AND foodId = :foodId AND cookMode = :cookMode")
suspend fun deleteCookFood(canteenId: String, foodId: String, cookMode: Int)
}
@@ -1,29 +1,30 @@
//package com.shuwei.dish.match.db.dao package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao import androidx.room.Dao
//import androidx.room.Query import androidx.room.Insert
//import com.shuwei.dish.match.db.BaseDao import androidx.room.OnConflictStrategy
//import com.shuwei.dish.match.entity.CookFoodGoodsEntity import androidx.room.Query
// import androidx.room.Update
//@Dao import com.shuwei.dish.match.entity.CookFoodGoodsEntity
//interface CookFoodGoodsDao : BaseDao<CookFoodGoodsEntity> {
// @Dao
// @Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId") interface CookFoodGoodsDao {
// suspend fun getCookFoodGoodsList(foodId: String): MutableList<CookFoodGoodsEntity>
// @Insert(onConflict = OnConflictStrategy.REPLACE)
// @Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId AND materialType = :materialType") suspend fun insertGoods(item: CookFoodGoodsEntity): Long
// suspend fun getCookFoodGoodsListByType(foodId: String, materialType:Int): MutableList<CookFoodGoodsEntity>
// @Insert(onConflict = OnConflictStrategy.REPLACE)
//// @Query( suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>): Array<Long>
//// """
//// UPDATE dm_cook_food_goods @Update
//// SET goodsName=:entity.goodsName, foodId=:entity.foodId, relateionType=:entity.relateionType, sort=:entity.sort suspend fun updateGoods(item: CookFoodGoodsEntity)
//// allEdible=:entity.allEdible, useWeight=:entity.useWeight, isDel=:entity.isDel, createTime=:entity.createTime
//// WHERE isDel = 0 @Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND pid = :pid AND foodId = :foodId")
//// AND foodId=:entity.foodId suspend fun getCookFoodGoodsList(pid: Long, foodId: String): MutableList<CookFoodGoodsEntity>
//// AND goodsId=:goodsId
//// """ @Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId AND materialType = :materialType")
//// ) suspend fun getCookFoodGoodsListByType(foodId: String, materialType: Int): MutableList<CookFoodGoodsEntity>
//// fun updateByGoodsId(entity: CookFoodGoodsEntity)
// @Query("DELETE FROM dm_cook_food_goods WHERE pid = :pid AND foodId = :foodId")
//} suspend fun deleteCookFoodGoodsList(pid: Long, foodId: String)
}
@@ -1,25 +1,42 @@
//package com.shuwei.dish.match.db.dao package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao import androidx.room.Dao
//import androidx.room.Query import androidx.room.Insert
//import com.shuwei.dish.match.db.BaseDao import androidx.room.OnConflictStrategy
//import com.shuwei.dish.match.entity.SeasoningEntity import androidx.room.Query
//import kotlinx.coroutines.flow.Flow import androidx.room.Update
// import com.shuwei.dish.match.entity.SeasoningEntity
//@Dao
//interface SeasoningDao : BaseDao<SeasoningEntity> { @Dao
// interface SeasoningDao {
//// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND id = :id")
//// suspend fun getSeasoningById(id: Long): SeasoningEntity? @Insert(onConflict = OnConflictStrategy.REPLACE)
//// suspend fun insertSeasoning(item: SeasoningEntity): Long
//// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 ORDER BY sort ASC")
//// fun getAllStream(): Flow<MutableList<SeasoningEntity>> @Insert(onConflict = OnConflictStrategy.REPLACE)
//// suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>): Array<Long>
//// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND name LIKE '%' || :query || '%'")
//// suspend fun search(query: String): MutableList<SeasoningEntity> @Update
//// suspend fun updateSeasoning(item: SeasoningEntity)
//// @Query("DELETE FROM dm_seasoning WHERE rowid IN " +
//// "(SELECT rowid FROM dm_seasoning LIMIT 1000)") @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND id = :id")
//// suspend fun deleteBatch(): Int suspend fun getSeasoningById(id: Long): SeasoningEntity?
//
//} @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
suspend fun getSeasoningBySort(sort: Int): MutableList<SeasoningEntity>?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId = :goodsId")
suspend fun getSeasoningByGoodsId(goodsId: Int): SeasoningEntity?
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsId != -1 AND goodsName != '' ORDER BY sort ASC")
fun getAllStream(): MutableList<SeasoningEntity>
@Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND goodsName LIKE '%' || :query || '%'")
suspend fun search(query: String): MutableList<SeasoningEntity>
@Query("DELETE FROM dm_seasoning WHERE isDel = 0 AND sort = :sort")
suspend fun deleteSeasoningBySort(sort: Int): Int
@Query("DELETE FROM dm_seasoning WHERE isDel = 0")
suspend fun clearAllSeasoning(): Int
}
@@ -23,7 +23,7 @@ open class CommonDialog(
private lateinit var binding: DialogCommonBinding private lateinit var binding: DialogCommonBinding
private var titleText: String? = null private var titleText: String? = null
private var contentText: String? = null private var contentText: CharSequence? = null
private var negativeText = "取消" private var negativeText = "取消"
private var positiveText = "确认" private var positiveText = "确认"
private var neutralText: String? = null private var neutralText: String? = null
@@ -36,7 +36,7 @@ open class CommonDialog(
fun setTitle(text: String): CommonDialog = apply { titleText = text } fun setTitle(text: String): CommonDialog = apply { titleText = text }
/** 设置内容,为空时隐藏 */ /** 设置内容,为空时隐藏 */
fun setContent(text: String): CommonDialog = apply { contentText = text } fun setContent(text: CharSequence): CommonDialog = apply { contentText = text }
/** 设置左侧取消按钮文字及点击回调(3按钮模式下对应顶部按钮) */ /** 设置左侧取消按钮文字及点击回调(3按钮模式下对应顶部按钮) */
fun setNegativeButton(text: String, onClick: (() -> Unit)? = null): CommonDialog = apply { fun setNegativeButton(text: String, onClick: (() -> Unit)? = null): CommonDialog = apply {
@@ -8,6 +8,9 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.view.isEmpty import androidx.core.view.isEmpty
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
@@ -17,9 +20,12 @@ import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogFoodSearchBinding import com.shuwei.dish.match.databinding.DialogFoodSearchBinding
import com.shuwei.dish.match.entity.CookFoodGoodsEntity import com.shuwei.dish.match.entity.CookFoodGoodsEntity
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.toast import com.shuwei.dish.match.utils.ext.toast
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/** /**
* 食材搜索弹窗,继承 BottomSheetDialog 确保只初始化一次 * 食材搜索弹窗,继承 BottomSheetDialog 确保只初始化一次
@@ -95,7 +101,9 @@ class FoodSearchDialog(
} }
binding.ivSearch.setOnClickListener { searchGoods(it) } binding.ivSearch.setOnClickListener { searchGoods(it) }
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it.context, it) } binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
initObserver()
// 若有默认食材名称,自动填充并触发搜索 // 若有默认食材名称,自动填充并触发搜索
if (!defGoodsName.isNullOrBlank()) { if (!defGoodsName.isNullOrBlank()) {
@@ -104,6 +112,26 @@ class FoodSearchDialog(
} }
} }
/**
* 收集 goodsListState,统一处理 Loading / Success / Error 状态
*/
private fun initObserver() {
activity.lifecycleScope.launch {
activity.repeatOnLifecycle(Lifecycle.State.STARTED) {
activity.netViewModel.goodsListState.collect { state ->
when (state) {
is UiState.Success -> loadGoodsList(state.data)
is UiState.Error -> {
activity.toast(state.msg)
finishRefresh()
}
else -> {}
}
}
}
}
}
/** /**
* 触发搜索:校验输入、隐藏键盘、发起请求 * 触发搜索:校验输入、隐藏键盘、发起请求
*/ */
@@ -114,7 +142,7 @@ class FoodSearchDialog(
} }
pageNo = 1 pageNo = 1
getGoodsList() getGoodsList()
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard(v)
} }
/** /**
@@ -130,12 +158,7 @@ class FoodSearchDialog(
if (!goodsName.isNullOrBlank()) { if (!goodsName.isNullOrBlank()) {
param["goodsName"] = goodsName!! param["goodsName"] = goodsName!!
} }
activity.queryGoodsList(param = param, onSuccess = { activity.netViewModel.queryGoodsList(param)
loadGoodsList(it)
}, onFailure = { _, msg ->
activity.toast(msg)
finishRefresh()
})
} }
/** /**
@@ -18,6 +18,9 @@ import android.view.WindowManager
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import androidx.core.view.isEmpty import androidx.core.view.isEmpty
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
@@ -27,6 +30,7 @@ import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogSeasoningSearchBinding import com.shuwei.dish.match.databinding.DialogSeasoningSearchBinding
import com.shuwei.dish.match.entity.SeasoningEntity import com.shuwei.dish.match.entity.SeasoningEntity
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.WeightUtil import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
@@ -35,6 +39,7 @@ import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.dp import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.toast
import kotlinx.coroutines.launch
/** /**
* 调料搜索弹窗,继承 BottomSheetDialog 确保只初始化一次 * 调料搜索弹窗,继承 BottomSheetDialog 确保只初始化一次
@@ -106,10 +111,10 @@ class SeasoningSearchDialog(
} }
// RecyclerView 初始化及滑动冲突处理 // RecyclerView 初始化及滑动冲突处理
binding.recyclerView.run { binding.recyclerView.let {
layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false) it.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
adapter = this@SeasoningSearchDialog.adapter it.adapter = adapter
addOnScrollListener(object : RecyclerView.OnScrollListener() { it.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(rv, dx, dy) super.onScrolled(rv, dx, dy)
// 解决 RecyclerView 与 SmartRefreshLayout 滑动冲突 // 解决 RecyclerView 与 SmartRefreshLayout 滑动冲突
@@ -127,7 +132,7 @@ class SeasoningSearchDialog(
} }
binding.ivSearch.setOnClickListener { searchGoods(it) } binding.ivSearch.setOnClickListener { searchGoods(it) }
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it.context, it) } binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
// 去皮按钮 // 去皮按钮
binding.tvClear.setOnClickListener { binding.tvClear.setOnClickListener {
@@ -147,6 +152,8 @@ class SeasoningSearchDialog(
) )
binding.tvWeight.text = getTextSpan(0.0) binding.tvWeight.text = getTextSpan(0.0)
initObserver()
// 若有默认调料名称,自动填充并触发搜索 // 若有默认调料名称,自动填充并触发搜索
if (!clickName.isNullOrBlank()) { if (!clickName.isNullOrBlank()) {
binding.etSheetInput.setText(clickName.trim()) binding.etSheetInput.setText(clickName.trim())
@@ -155,6 +162,26 @@ class SeasoningSearchDialog(
} }
} }
/**
* 收集 seasoningListState,统一处理 Loading / Success / Error 状态
*/
private fun initObserver() {
activity.lifecycleScope.launch {
activity.repeatOnLifecycle(Lifecycle.State.STARTED) {
activity.netViewModel.seasoningListState.collect { state ->
when (state) {
is UiState.Success -> loadGoodsList(state.data)
is UiState.Error -> {
activity.toast(state.msg)
finishRefresh()
}
else -> {}
}
}
}
}
}
/** /**
* 触发搜索:校验输入、隐藏键盘、发起请求 * 触发搜索:校验输入、隐藏键盘、发起请求
*/ */
@@ -165,7 +192,7 @@ class SeasoningSearchDialog(
} }
pageNo = 1 pageNo = 1
getGoodsList() getGoodsList()
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard(v)
} }
/** /**
@@ -181,12 +208,7 @@ class SeasoningSearchDialog(
if (!goodsName.isNullOrBlank()) { if (!goodsName.isNullOrBlank()) {
param["goodsName"] = goodsName!! param["goodsName"] = goodsName!!
} }
activity.querySeasoningList(param = param, onSuccess = { activity.netViewModel.querySeasoningList(param)
loadGoodsList(it)
}, onFailure = { _, msg ->
activity.toast(msg)
finishRefresh()
})
} }
/** /**
@@ -106,7 +106,7 @@ class SeasoningSelectDialog(
} }
binding.ivSearch.setOnClickListener { searchGoods(it) } binding.ivSearch.setOnClickListener { searchGoods(it) }
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it.context, it) } binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
} }
/** 弹窗显示时自动加载默认调料列表 */ /** 弹窗显示时自动加载默认调料列表 */
@@ -126,7 +126,7 @@ class SeasoningSelectDialog(
} }
pageNo = 1 pageNo = 1
getGoodsList() getGoodsList()
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard( v)
} }
/** /**
@@ -0,0 +1,11 @@
package com.shuwei.dish.match.entity
/**
* 已采集食材列表项数据模型
* @param foodName 食材名称
* @param collectCount 已采集数量
*/
data class CollectedFoodItem(
val foodName: String = "",
val collectCount: Int = 0
)
@@ -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
/** /**
@@ -16,81 +19,102 @@ class NetViewModel(
private val repository: RemoteRepository = RemoteRepository() private val repository: RemoteRepository = RemoteRepository()
) : ViewModel() { ) : ViewModel() {
/**
* 提交制作菜品的 UI 状态流,UI 层通过 collect 监听
*/
private val _submitCookFoodState = MutableStateFlow<UiState<Any?>>(UiState.Idle)
val submitCookFoodState: StateFlow<UiState<Any?>> = _submitCookFoodState.asStateFlow()
/** /**
* 提交制作菜品 * 提交制作菜品
*/ */
fun submitCookFood( fun submitCookFood(entity: CookFoodEntity) {
entity: CookFoodEntity,
onSuccess: (Any?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch { viewModelScope.launch {
repository.submitCookFood(entity, onSuccess, onFailure) _submitCookFoodState.value = UiState.Loading
_submitCookFoodState.value = repository.submitCookFood(entity)
} }
} }
/**
* 查询菜品详情的 UI 状态流,UI 层通过 collect 监听
*/
private val _foodDetailState = MutableStateFlow<UiState<CookFoodEntity?>>(UiState.Idle)
val foodDetailState: StateFlow<UiState<CookFoodEntity?>> = _foodDetailState.asStateFlow()
/** /**
* 查询菜品详情 * 查询菜品详情
*/ */
fun getFoodDetail( fun getFoodDetail(foodId: String) {
foodId: String,
onSuccess: (CookFoodEntity?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch { viewModelScope.launch {
repository.getFoodDetail(foodId, onSuccess, onFailure) _foodDetailState.value = UiState.Loading
_foodDetailState.value = repository.getFoodDetail(foodId)
} }
} }
/**
* 搜索菜品列表的 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)
} }
} }
/**
* 查询采样数据列表的 UI 状态流,UI 层通过 collect 监听
*/
private val _samplingListState = MutableStateFlow<UiState<MutableList<FoodRecord>?>>(UiState.Idle)
val samplingListState: StateFlow<UiState<MutableList<FoodRecord>?>> = _samplingListState.asStateFlow()
fun resetSamplingListState() {
_samplingListState.value = UiState.Idle
}
/** /**
* 查询采样数据列表 * 查询采样数据列表
*/ */
fun getSamplingList( fun getSamplingList(param: MutableMap<String, Any>) {
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch { viewModelScope.launch {
repository.getSamplingList(param, onSuccess, onFailure) _samplingListState.value = UiState.Loading
_samplingListState.value = repository.getSamplingList(param)
} }
} }
/**
* 查询物品信息列表的 UI 状态流,UI 层通过 collect 监听
*/
private val _goodsListState = MutableStateFlow<UiState<MutableList<CookFoodGoodsEntity>?>>(UiState.Idle)
val goodsListState: StateFlow<UiState<MutableList<CookFoodGoodsEntity>?>> = _goodsListState.asStateFlow()
/** /**
* 查询物品信息列表 * 查询物品信息列表
*/ */
fun queryGoodsList( fun queryGoodsList(param: MutableMap<String, Any>) {
param: MutableMap<String, Any>,
onSuccess: (MutableList<CookFoodGoodsEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch { viewModelScope.launch {
repository.queryGoodsList(param, onSuccess, onFailure) _goodsListState.value = UiState.Loading
_goodsListState.value = repository.queryGoodsList(param)
} }
} }
/**
* 查询调料信息列表的 UI 状态流,UI 层通过 collect 监听
*/
private val _seasoningListState = MutableStateFlow<UiState<MutableList<SeasoningEntity>?>>(UiState.Idle)
val seasoningListState: StateFlow<UiState<MutableList<SeasoningEntity>?>> = _seasoningListState.asStateFlow()
/** /**
* 查询调料信息列表 * 查询调料信息列表
*/ */
fun querySeasoningList( fun querySeasoningList(param: MutableMap<String, Any>) {
param: MutableMap<String, Any>,
onSuccess: (MutableList<SeasoningEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch { viewModelScope.launch {
repository.querySeasoningList(param, onSuccess, onFailure) _seasoningListState.value = UiState.Loading
_seasoningListState.value = repository.querySeasoningList(param)
} }
} }
} }
@@ -14,96 +14,98 @@ class RemoteRepository {
/** /**
* 提交制作菜品 * 提交制作菜品
* @param entity 菜品实体 * @param entity 菜品实体
* @param onSuccess 成功回调 * @return UiState 包装的结果,Success 表示提交成功,Error 携带错误信息
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
*/ */
suspend fun submitCookFood( suspend fun submitCookFood(entity: CookFoodEntity): UiState<Any?> {
entity: CookFoodEntity, return try {
onSuccess: (Any?) -> Unit, val resp = apiService.submitCookFood(param = entity)
onFailure: (String, String) -> Unit if (resp.isSuccess()) UiState.Success(resp.data)
) = request( else UiState.Error(resp.code, resp.msg ?: "")
onRequest = { apiService.submitCookFood(param = entity) }, } catch (e: Exception) {
onSuccess = onSuccess, val ex = getApiException(e)
onFailure = onFailure UiState.Error("-1", ex.errorMsg)
) }
}
/** /**
* 查询菜品详情 * 查询菜品详情
* @param foodId 菜品 ID * @param foodId 菜品 ID
* @param onSuccess 成功回调 * @return UiState 包装的结果,Success 携带详情数据,Error 携带错误信息
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
*/ */
suspend fun getFoodDetail( suspend fun getFoodDetail(foodId: String): UiState<CookFoodEntity?> {
foodId: String, return try {
onSuccess: (CookFoodEntity?) -> Unit, val resp = apiService.getFoodDetail(foodId = foodId)
onFailure: (String, String) -> Unit if (resp.isSuccess()) UiState.Success(resp.data)
) = request( else UiState.Error(resp.code, resp.msg ?: "")
onRequest = { apiService.getFoodDetail(foodId = foodId) }, } catch (e: Exception) {
onSuccess = onSuccess, val ex = getApiException(e)
onFailure = onFailure UiState.Error("-1", ex.errorMsg)
) }
}
/** /**
* 搜索菜品列表 * 搜索菜品列表
* @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)
) }
}
/** /**
* 查询采样数据列表 * 查询采样数据列表
* @param param 查询参数 * @param param 查询参数
* @param onSuccess 成功回调 * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
*/ */
suspend fun getSamplingList( suspend fun getSamplingList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> {
param: MutableMap<String, Any>, return try {
onSuccess: (MutableList<FoodRecord>?) -> Unit, val resp = apiService.getSamplingList(param = param)
onFailure: (String, String) -> Unit if (resp.isSuccess()) UiState.Success(resp.data)
) = request( else UiState.Error(resp.code, resp.msg ?: "")
onRequest = { apiService.getSamplingList(param = param) }, } catch (e: Exception) {
onSuccess = onSuccess, val ex = getApiException(e)
onFailure = onFailure UiState.Error("-1", ex.errorMsg)
) }
}
/** /**
* 查询物品信息列表 * 查询物品信息列表
* @param param 查询参数 * @param param 查询参数
* @param onSuccess 成功回调 * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
*/ */
suspend fun queryGoodsList( suspend fun queryGoodsList(param: MutableMap<String, Any>): UiState<MutableList<CookFoodGoodsEntity>?> {
param: MutableMap<String, Any>, return try {
onSuccess: (MutableList<CookFoodGoodsEntity>?) -> Unit, val resp = apiService.queryGoodsList(param = param)
onFailure: (String, String) -> Unit if (resp.isSuccess()) UiState.Success(resp.data)
) = request( else UiState.Error(resp.code, resp.msg ?: "")
onRequest = { apiService.queryGoodsList(param = param) }, } catch (e: Exception) {
onSuccess = onSuccess, val ex = getApiException(e)
onFailure = onFailure UiState.Error("-1", ex.errorMsg)
) }
}
/** /**
* 查询调料信息列表 * 查询调料信息列表
* @param param 查询参数 * @param param 查询参数
* @param onSuccess 成功回调 * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
*/ */
suspend fun querySeasoningList( suspend fun querySeasoningList(param: MutableMap<String, Any>): UiState<MutableList<SeasoningEntity>?> {
param: MutableMap<String, Any>, return try {
onSuccess: (MutableList<SeasoningEntity>?) -> Unit, val resp = apiService.querySeasoningList(param = param)
onFailure: (String, String) -> Unit if (resp.isSuccess()) UiState.Success(resp.data)
) = request( else UiState.Error(resp.code, resp.msg ?: "")
onRequest = { apiService.querySeasoningList(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>()
}
@@ -213,6 +213,31 @@ object ObjectBox {
getBox<Food>()?.query(Food_.name.equal(name))?.build()?.remove() getBox<Food>()?.query(Food_.name.equal(name))?.build()?.remove()
} }
/**
* 按名称分组统计采集数量
* 使用属性查询只加载 name 字段,不加载 foodVector,节省大量内存
* @param nameFilter 名称过滤关键词,为空时查询全部
* @return 按名称分组后的 (名称, 数量) 列表,按名称排序
*/
suspend fun queryFoodNameCounts(nameFilter: String? = null) = safeDbOp {
val box = getBox<Food>() ?: return@safeDbOp emptyList<Pair<String, Int>>()
val query = if (!nameFilter.isNullOrBlank()) {
box.query(Food_.name.contains(nameFilter)).build()
} else {
box.query().build()
}
try {
// 只查 name 字段,完全不加载 foodVector
query.property(Food_.name).findStrings()
.groupingBy { it }
.eachCount()
.map { (name, count) -> Pair(name, count) }
.sortedBy { it.first }
} finally {
query.close()
}
} ?: emptyList()
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性 private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
private const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录 private const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录
private const val BACKUP_DIR_NAME = "objectbox_backup" // 备份目录 private const val BACKUP_DIR_NAME = "objectbox_backup" // 备份目录
@@ -0,0 +1,192 @@
package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.inputmethod.EditorInfo
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.CollectedFoodAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.databinding.ActivityCollectedFoodBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.CollectedFoodItem
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import kotlinx.coroutines.launch
/**
* 已采集食材列表页面
* 展示所有已采集的食材及其采集数量,支持搜索和删除
*/
class CollectedFoodActivity : BaseActivity() {
private lateinit var binding: ActivityCollectedFoodBinding
private val foodList = mutableListOf<CollectedFoodItem>()
private var emptyViewBinding: LayoutEmptyViewBinding? = null
private val adapter by lazy {
CollectedFoodAdapter(foodList).apply {
isStateViewEnable = true
// 点击删除按钮,移除对应食材
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
showDeleteDialog(foodList[position].foodName)
}
setOnItemClickListener { _, view, _ ->
KeyboardUtil.hideKeyboard(view)
}
}
}
/**
* 删除弹窗
*/
private fun showDeleteDialog(foodName: String) {
val showFoodName = foodName.split("WP").first()
val separator = if (showFoodName.length <= 10) "\n" else ""
val content = buildSpannableString {
appendText("食材:")
appendText(showFoodName, StyleSpan(Typeface.BOLD), ForegroundColorSpan(Color.BLACK))
appendText(" 删除后无法恢复,${separator}确认要删除吗?")
}
CommonDialog(this)
.setTitle("温馨提示")
.setContent(content)
.setNegativeButton("取消")
.setPositiveButton("确认") {
appViewModel.removeCollectedFood(
foodName = foodName,
nameFilter = binding.editSearch.text.toString()
)
}
.show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
statusBarDarkFont(enable = true)
binding = ActivityCollectedFoodBinding.inflate(layoutInflater)
setContentView(binding.root)
setHeaderBackground()
binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v)
}
setTitleBar(titleBarAction = {
it.visible()
it.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v)
}
}, titleAction = {
it.text = "已采集食材"
}, rightIconActon = {
it.gone()
}, backAction = {
it.visible()
it.setOnClickListener {
finish()
}
})
initRecyclerView()
initSearch()
initObserver()
loadCollectedFoodList()
}
private fun loadCollectedFoodList(name: String = "") {
appViewModel.loadCollectedFoodList(nameFilter = name)
}
/**
* 收集 searchFoodState,统一处理 Loading / Success / Error 状态
*/
@SuppressLint("NotifyDataSetChanged")
private fun initObserver() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
appViewModel.collectedFoodListState.collect { items ->
foodList.clear()
foodList.addAll(items)
adapter.notifyDataSetChanged()
if (items.isEmpty()) {
loadEmptyView()
}
}
}
}
}
/** 展示空视图 */
private fun loadEmptyView() {
if (emptyViewBinding == null) {
emptyViewBinding = LayoutEmptyViewBinding.inflate(
LayoutInflater.from(this), binding.rvCollectedFood, false
)
}
emptyViewBinding!!.tvContent.text = "暂无数据"
emptyViewBinding!!.tvSubContent.text = "未查询到食材信息"
adapter.stateView = emptyViewBinding!!.root
}
/** 初始化列表 */
private fun initRecyclerView() {
binding.rvCollectedFood.run {
layoutManager = LinearLayoutManager(this@CollectedFoodActivity)
adapter = this@CollectedFoodActivity.adapter
}
}
/** 初始化搜索栏交互 */
private fun initSearch() {
binding.btnSearch.setOnClickListener { v ->
performSearch {
toast(binding.editSearch.hint.toString())
}
KeyboardUtil.hideKeyboard(v)
}
binding.editSearch.run {
addTextChangedListener {
performSearch {
loadCollectedFoodList()
}
}
setOnEditorActionListener { v, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
performSearch {
toast(binding.editSearch.hint.toString())
}
KeyboardUtil.hideKeyboard(v)
true
} else {
false
}
}
}
}
/** 根据关键词过滤食材列表 */
@SuppressLint("NotifyDataSetChanged")
private fun performSearch(action: () -> Unit) {
val keyword = binding.editSearch.text.toString().trim()
if (keyword.isEmpty()) {
action()
return
}
// 根据关键词从数据库查询并刷新列表
loadCollectedFoodList(keyword)
}
}
@@ -15,7 +15,7 @@
//import androidx.lifecycle.ViewModelProvider //import androidx.lifecycle.ViewModelProvider
//import com.shuwei.dish.match.base.BaseActivity //import com.shuwei.dish.match.base.BaseActivity
//import com.shuwei.dish.match.base.BaseApp //import com.shuwei.dish.match.base.BaseApp
//import com.shuwei.dish.match.db.AppRepository //import com.shuwei.dish.match.db.DbRepository
//import com.shuwei.dish.match.dialog.CommonDialog //import com.shuwei.dish.match.dialog.CommonDialog
//import com.shuwei.dish.match.entity.SeasoningEntity //import com.shuwei.dish.match.entity.SeasoningEntity
//import com.shuwei.dish.match.entity.ResetReasoningRecord //import com.shuwei.dish.match.entity.ResetReasoningRecord
@@ -29,8 +29,6 @@
//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 com.shuwei.dish.match.utils.ext.visible //import com.shuwei.dish.match.utils.ext.visible
//import com.shuwei.dish.match.viewmodel.AppViewModel
//import com.shuwei.dish.match.viewmodel.factory.AppFactory
//import com.shuwei.dish.match.adapter.TextCellAdapter //import com.shuwei.dish.match.adapter.TextCellAdapter
//import com.shuwei.dish.match.databinding.ActivityDeviceConfigBinding //import com.shuwei.dish.match.databinding.ActivityDeviceConfigBinding
//import com.shuwei.dish.match.dialog.SeasoningSearchDialog //import com.shuwei.dish.match.dialog.SeasoningSearchDialog
@@ -61,7 +59,7 @@
// //
// private lateinit var binding: ActivityDeviceConfigBinding // private lateinit var binding: ActivityDeviceConfigBinding
// //
// private lateinit var appViewModel: AppViewModel // private lateinit var appViewModel: DbViewModel
// //
// private var cookMode: Int = 0 // private var cookMode: Int = 0
//// private val weightArray = SparseIntArray() //// private val weightArray = SparseIntArray()
@@ -200,9 +198,9 @@
// private fun initViewModel() { // private fun initViewModel() {
// val db = BaseApp.instance!!.database // val db = BaseApp.instance!!.database
// val factory = // val factory =
// AppFactory(AppRepository(db.appDao())) // AppFactory(DbRepository(db.appDao()))
// appViewModel = // appViewModel =
// ViewModelProvider(this, factory)[AppViewModel::class.java] // ViewModelProvider(this, factory)[DbViewModel::class.java]
// } // }
// //
// private fun loadSeasoning() { // private fun loadSeasoning() {
@@ -7,7 +7,6 @@ import android.util.Log
import android.view.View import android.view.View
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.ViewModelProvider
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
@@ -15,7 +14,6 @@ import com.shuwei.dish.match.adapter.DishPartAdapter
import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityDishSamplingBinding import com.shuwei.dish.match.databinding.ActivityDishSamplingBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.FoodSearchDialog import com.shuwei.dish.match.dialog.FoodSearchDialog
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.CookFoodEntity
@@ -33,8 +31,6 @@ import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString import com.shuwei.dish.match.utils.ext.toJsonString
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 com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import java.io.Serializable import java.io.Serializable
class DishSamplingActivity : BaseActivity() { class DishSamplingActivity : BaseActivity() {
@@ -62,7 +58,6 @@ class DishSamplingActivity : BaseActivity() {
setTitleBar() setTitleBar()
setHeaderBackground() setHeaderBackground()
addViewClickListener() addViewClickListener()
initViewModel()
foodName = intent.getStringExtra(FOOD_NAME) foodName = intent.getStringExtra(FOOD_NAME)
binding.etInputDish.setText(foodName) binding.etInputDish.setText(foodName)
@@ -97,18 +92,10 @@ class DishSamplingActivity : BaseActivity() {
}) })
} }
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao(), db.seasoningSlotDao()))
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)
} }
/** /**
@@ -269,7 +256,7 @@ class DishSamplingActivity : BaseActivity() {
setSelection(text.length) setSelection(text.length)
} }
} }
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard(v)
} }
private fun searchDishType() { private fun searchDishType() {
@@ -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,13 +88,38 @@ 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() {
binding.ivDishSearch.setOnClickListener { v -> binding.ivDishSearch.setOnClickListener { v ->
pageNo = 1 pageNo = 1
getInputAndSearch() getInputAndSearch()
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard(v)
} }
binding.refreshLayout.setEnableRefresh(true) binding.refreshLayout.setEnableRefresh(true)
binding.refreshLayout.setEnableLoadMore(false) binding.refreshLayout.setEnableLoadMore(false)
@@ -106,13 +136,13 @@ class FoodSearchActivity : BaseActivity() {
queryListInfo(foodName!!) queryListInfo(foodName!!)
} }
binding.root.setOnClickListener { v -> binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard(v)
} }
binding.etInputDish.let { v -> binding.etInputDish.let { v ->
v.addOnActionSearchListener { v.addOnActionSearchListener {
pageNo = 1 pageNo = 1
getInputAndSearch() getInputAndSearch()
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard( v)
} }
} }
} }
@@ -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 = {
/**
* 处理搜索结果,填充列表或展示空视图
*/
@SuppressLint("NotifyDataSetChanged")
private fun handleSearchResult(records: MutableList<FoodRecord>?) {
delayDismissLoading() delayDismissLoading()
finishRefresh() finishRefresh()
binding.refreshLayout.setEnableRefresh(true) binding.refreshLayout.setEnableRefresh(true)
if (it.isNullOrEmpty()) { if (records.isNullOrEmpty()) {
// toast("暂未搜索到相关菜品信息") if (pageNo == 1) loadEmptyView()
if (pageNo == 1) { return
loadEmptyView()
} }
return@searchFoodList if (pageNo == 1) list.clear()
} list.addAll(records)
if (pageNo == 1) {
list.clear()
}
list.addAll(it)
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() {
@@ -3,20 +3,18 @@ package com.shuwei.dish.match.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope
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.HomeModeAdapter import com.shuwei.dish.match.adapter.HomeModeAdapter
import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityHomeBinding import com.shuwei.dish.match.databinding.ActivityHomeBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.entity.HomeModeBean import com.shuwei.dish.match.entity.HomeModeBean
import com.shuwei.dish.match.utils.SpTool import com.shuwei.dish.match.utils.SpTool
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 com.shuwei.dish.match.viewmodel.AppViewModel import kotlinx.coroutines.launch
import com.shuwei.dish.match.viewmodel.factory.AppFactory
class HomeActivity : BaseActivity() { class HomeActivity : BaseActivity() {
@@ -32,7 +30,6 @@ class HomeActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
statusBarDarkFont(enable = true) statusBarDarkFont(enable = true)
initViewModel()
// // 获取 ANDROID_ID // // 获取 ANDROID_ID
// val deviceId = // val deviceId =
// Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) // Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
@@ -134,36 +131,30 @@ class HomeActivity : BaseActivity() {
) )
} }
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao(), db.seasoningSlotDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
private fun goSampling() { private fun goSampling() {
appViewModel.loadSeasoning { lifecycleScope.launch {
if (it.isEmpty()) { val slots = appViewModel.loadSeasoningSlot()
//无调料信息打开采样历史列表页面,点击设置配置调料信息 if (slots.isEmpty()) {
// 无调料信息,打开采样历史列表页面,点击设置配置调料信息
startActivity<SamplingListActivity>() startActivity<SamplingListActivity>()
finish() finish()
return@loadSeasoning return@launch
} }
appViewModel.countCookFood(cookMode = 1) { count -> val count = appViewModel.countCookFood(cookMode = 1)
if (count > 0) { if (count > 0) {
//有烹饪中数据打开采样历史列表页面 // 有烹饪中数据打开采样历史列表页面
startActivity<SamplingListActivity>() startActivity<SamplingListActivity>()
finish() finish()
return@countCookFood return@launch
} }
//无烹饪数据打开新增采样页面 // 无烹饪数据打开新增采样页面
startActivity<PrepareCookActivity> { startActivity<PrepareCookActivity> {
putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME) putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME)
} }
finish() finish()
} }
} }
}
} }
@@ -9,14 +9,12 @@ import android.view.KeyEvent
import android.view.animation.RotateAnimation import android.view.animation.RotateAnimation
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.DeviceRole import com.shuwei.dish.match.base.DeviceRole
import com.shuwei.dish.match.base.GlobalData import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.databinding.ActivityInitBinding import com.shuwei.dish.match.databinding.ActivityInitBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.objbox.FoodModule import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.scale.ScaleServiceManager import com.shuwei.dish.match.scale.ScaleServiceManager
import com.shuwei.dish.match.utils.AppUtil import com.shuwei.dish.match.utils.AppUtil
@@ -27,8 +25,6 @@ import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.visible import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -38,7 +34,6 @@ class InitActivity : BaseActivity() {
} }
private lateinit var binding: ActivityInitBinding private lateinit var binding: ActivityInitBinding
private lateinit var appViewModel: AppViewModel
private var startTime = 0L // 倒计时开始时间 private var startTime = 0L // 倒计时开始时间
private var lastNetworkCheckTime = 0L // 上次检测网络的时间 private var lastNetworkCheckTime = 0L // 上次检测网络的时间
@@ -59,8 +54,6 @@ class InitActivity : BaseActivity() {
BaseApp.canteenId = "0" BaseApp.canteenId = "0"
initViewModel()
WeightUtil.init() WeightUtil.init()
WeightUtil.getWeight() WeightUtil.getWeight()
WeightUtil.startContinuousRead() WeightUtil.startContinuousRead()
@@ -68,11 +61,6 @@ class InitActivity : BaseActivity() {
initViews() initViews()
} }
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao(), db.seasoningSlotDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
private fun startNextPage(withDelay: Boolean = false) { private fun startNextPage(withDelay: Boolean = false) {
lifecycleScope.launch { lifecycleScope.launch {
@@ -100,25 +88,25 @@ class InitActivity : BaseActivity() {
} }
private fun goSampling() { private fun goSampling() {
appViewModel.loadSeasoning { lifecycleScope.launch {
if (it.isEmpty()) { val slots = appViewModel.loadSeasoningSlot()
//无调料信息打开采样历史列表页面,点击设置配置调料信息 if (slots.isEmpty()) {
// 无调料信息,打开采样历史列表页面,点击设置配置调料信息
startActivity<SamplingListActivity>() startActivity<SamplingListActivity>()
return@loadSeasoning return@launch
} }
appViewModel.countCookFood(cookMode = 1) { count -> val count = appViewModel.countCookFood(cookMode = 1)
if (count > 0) { if (count > 0) {
//有烹饪中数据打开采样历史列表页面 // 有烹饪中数据打开采样历史列表页面
startActivity<SamplingListActivity>() startActivity<SamplingListActivity>()
return@countCookFood return@launch
} }
//无烹饪数据打开新增采样页面 // 无烹饪数据打开新增采样页面
startActivity<PrepareCookActivity> { startActivity<PrepareCookActivity> {
putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME) putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME)
} }
} }
} }
}
override fun onDestroy() { override fun onDestroy() {
WeightUtil.stopContinuousRead() WeightUtil.stopContinuousRead()
@@ -30,8 +30,6 @@ import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
import com.shuwei.dish.match.scale.ScaleData import com.shuwei.dish.match.scale.ScaleData
import com.shuwei.dish.match.scale.ScaleDeviceConfig import com.shuwei.dish.match.scale.ScaleDeviceConfig
import com.shuwei.dish.match.scale.ScaleServiceManager import com.shuwei.dish.match.scale.ScaleServiceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.shuwei.dish.match.utils.NetworkUtil import com.shuwei.dish.match.utils.NetworkUtil
import com.shuwei.dish.match.utils.SizeTool import com.shuwei.dish.match.utils.SizeTool
import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.WeightUtil
@@ -53,6 +51,7 @@ class MasterScaleActivity : BaseActivity() {
private lateinit var binding: ActivityMasterScaleBinding private lateinit var binding: ActivityMasterScaleBinding
private val size by lazy { SizeTool.getFlexLayoutSize(10.dp) } private val size by lazy { SizeTool.getFlexLayoutSize(10.dp) }
private data class DeviceGroup( private data class DeviceGroup(
val deviceId: String, val deviceId: String,
val ip: String, val ip: String,
@@ -81,16 +80,14 @@ class MasterScaleActivity : BaseActivity() {
/** 从 Room 加载槽位配置到 slotNameMap,加载完成后启动数据观测 */ /** 从 Room 加载槽位配置到 slotNameMap,加载完成后启动数据观测 */
private fun loadSlotNames() { private fun loadSlotNames() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch {
val slots = BaseApp.instance!!.database.seasoningSlotDao().queryAll() val slots = appViewModel.loadSeasoningSlot()
withContext(Dispatchers.Main) {
slots.forEach { slot -> slots.forEach { slot ->
slotNameMap["${slot.deviceId}#${slot.address}"] = slot.goodsName slotNameMap["${slot.deviceId}#${slot.address}"] = slot.goodsName
} }
observeScaleData() observeScaleData()
} }
} }
}
/** 用 slotNameMap 为 ScaleData 注入名称 */ /** 用 slotNameMap 为 ScaleData 注入名称 */
private fun ScaleData.withSlotName(): ScaleData { private fun ScaleData.withSlotName(): ScaleData {
@@ -8,7 +8,9 @@ import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope 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
@@ -20,6 +22,7 @@ import com.shuwei.dish.match.dialog.CommonDialog
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.FoodRecord import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.objbox.FoodModule import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.utils.AddressUtil import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.CameraUtils import com.shuwei.dish.match.utils.CameraUtils
@@ -36,6 +39,7 @@ import com.shuwei.dish.match.utils.ext.toJsonString
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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.Serializable import java.io.Serializable
@@ -110,6 +114,7 @@ class PrepareCookActivity : BaseActivity() {
addBackKeyListener() addBackKeyListener()
initCamera(binding.flCameraContainer) initCamera(binding.flCameraContainer)
initRecyclerView() initRecyclerView()
initObserver()
if (!isSamplingMode) { if (!isSamplingMode) {
getDishDetail() getDishDetail()
@@ -267,6 +272,28 @@ class PrepareCookActivity : BaseActivity() {
} }
private fun initObserver() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
netViewModel.foodDetailState.collect { state ->
when (state) {
is UiState.Success -> {
val detail = state.data
if (detail == null) {
toast("查询菜品信息为空")
return@collect
}
loadDishDetail(detail)
}
is UiState.Error -> toast(state.msg)
else -> {}
}
}
}
}
}
private fun getDishDetail() { private fun getDishDetail() {
// //649 // //649
// val url = "${UrlConfig.DISH_DETAIL}?foodId=${food?.foodId}" // val url = "${UrlConfig.DISH_DETAIL}?foodId=${food?.foodId}"
@@ -285,17 +312,7 @@ class PrepareCookActivity : BaseActivity() {
// toast(msg) // toast(msg)
// } // }
netViewModel.getFoodDetail( netViewModel.getFoodDetail(food?.foodId ?: "")
food?.foodId ?: "",
onSuccess = { detail ->
if (detail == null) {
toast("查询菜品信息为空")
return@getFoodDetail
}
loadDishDetail(detail)
}, onFailure = { code, msg ->
toast(msg)
})
} }
@@ -534,7 +551,7 @@ class PrepareCookActivity : BaseActivity() {
setSelection(text.length) setSelection(text.length)
} }
} }
KeyboardUtil.hideKeyboard(v.context, v) KeyboardUtil.hideKeyboard(v)
} }
} }
@@ -5,7 +5,9 @@ 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.ViewModelProvider 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.shuwei.dish.match.R import com.shuwei.dish.match.R
@@ -14,7 +16,6 @@ import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivitySamplingBinding import com.shuwei.dish.match.databinding.ActivitySamplingBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
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.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord import com.shuwei.dish.match.entity.FoodRecord
@@ -24,9 +25,9 @@ import com.shuwei.dish.match.utils.ext.gone
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 com.shuwei.dish.match.utils.ext.visible import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import java.io.Serializable import java.io.Serializable
import kotlinx.coroutines.launch
import com.shuwei.dish.match.net.UiState
class SamplingListActivity : BaseActivity() { class SamplingListActivity : BaseActivity() {
@@ -44,13 +45,65 @@ class SamplingListActivity : BaseActivity() {
setContentView(binding.root) setContentView(binding.root)
setTitleBar() setTitleBar()
setHeaderBackground() setHeaderBackground()
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)
}
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
netViewModel.samplingListState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> loadDishList(state.data)
is UiState.Error -> {
binding.refreshLayout.setEnableRefresh(true)
toast(state.msg)
finishRefresh()
delayDismissLoading()
if (pageNo == 1) loadEmptyView()
}
is UiState.Idle -> {}
}
}
}
}
}
private fun setTitleBar() { private fun setTitleBar() {
setTitleBar(titleBarAction = { setTitleBar(titleBarAction = {
@@ -70,20 +123,17 @@ class SamplingListActivity : BaseActivity() {
} }
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao(), db.seasoningSlotDao()))
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) {
appViewModel.deleteCookFoodAndGoods(cookMode = 1, foodId = foodId, onFinish = action) lifecycleScope.launch {
appViewModel.deleteCookFoodAndGoods(cookMode = 1, foodId = foodId)
action()
}
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@@ -190,53 +240,12 @@ class SamplingListActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun getSamplingList() { fun getSamplingList() {
showLoading()
// val map = mapOf(
//// "foodName" to input,
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize",
// "canteenId" to BaseApp.canteenId
// )
// val sb = StringBuilder(UrlConfig.SAMPLING_LIST).apply {
// append("?")
// map.forEach { (key, value) -> append("$key=$value&") }
// }
// sb.deleteCharAt(sb.length - 1)
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = {
// loadDishList(it)
// }, doFailure = { code, msg ->
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// toast(msg)
// finishRefresh()
// binding.refreshLayout.setEnableRefresh(true)
// delayDismissLoading()
// if (pageNo == 1) {
// loadEmptyView()
// }
// })
netViewModel.getSamplingList( netViewModel.getSamplingList(
param = mutableMapOf( param = mutableMapOf(
"pageNum" to pageNo, "pageNum" to pageNo,
"pageSize" to pageSize, "pageSize" to pageSize,
"placeId" to BaseApp.canteenId "placeId" to BaseApp.canteenId
), )
onSuccess = {
loadDishList(it)
},
onFailure = { code, msg ->
binding.refreshLayout.setEnableRefresh(true)
toast(msg)
finishRefresh()
delayDismissLoading()
if (pageNo == 1) {
loadEmptyView()
}
}
) )
} }
@@ -298,38 +307,18 @@ class SamplingListActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun refreshLocalData() { fun refreshLocalData() {
showLoading() showLoading()
//查询本地数据 appViewModel.resetCookFoodList()
getCookFoodList { cookFoodEntities -> getCookFoodList()
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) {
if (isCooking) { if (isCooking) {
// 清除上次网络请求的残留状态
netViewModel.resetSamplingListState()
refreshLocalData() refreshLocalData()
} else { } else {
// 同理,清除本地数据残留状态
appViewModel.resetCookFoodList()
pageNo = 1 pageNo = 1
getSamplingList() getSamplingList()
} }
@@ -367,10 +356,11 @@ class SamplingListActivity : BaseActivity() {
block() block()
return return
} }
appViewModel.loadSeasoning { lifecycleScope.launch {
if (it.isEmpty()) { val slots = appViewModel.loadSeasoningSlot()
if (slots.isEmpty()) {
showDeviceConfigDialog() showDeviceConfigDialog()
return@loadSeasoning return@launch
} }
configFinished = true configFinished = true
block() block()
@@ -3,15 +3,13 @@ package com.shuwei.dish.match.ui
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import com.shuwei.dish.match.R import com.shuwei.dish.match.R
import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp 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.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
@@ -19,8 +17,6 @@ import com.shuwei.dish.match.utils.ext.gone
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 com.shuwei.dish.match.utils.ext.visible import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
class SelectDishActivity : BaseActivity() { class SelectDishActivity : BaseActivity() {
@@ -32,8 +28,6 @@ class SelectDishActivity : BaseActivity() {
private lateinit var binding: ActivitySelectDishBinding private lateinit var binding: ActivitySelectDishBinding
private lateinit var appViewModel: AppViewModel
private var dinnerType: String = "1" private var dinnerType: String = "1"
private val fragmentList = mutableListOf<DishListFragment>().apply { private val fragmentList = mutableListOf<DishListFragment>().apply {
@@ -60,20 +54,14 @@ class SelectDishActivity : BaseActivity() {
}, backAction = { }, backAction = {
it.gone() it.gone()
}) })
initViewModel()
addViewListener() addViewListener()
loadFragment() loadFragment()
onBackPressedDispatcher.addCallback(this) {} onBackPressedDispatcher.addCallback(this) {}
} }
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao(), db.seasoningSlotDao()))
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
@@ -132,7 +120,7 @@ class SelectDishActivity : BaseActivity() {
putExtra(FoodSearchActivity.DINNER_TYPE, dinnerType) putExtra(FoodSearchActivity.DINNER_TYPE, dinnerType)
putExtra(FoodSearchActivity.FOOD_NAME, searchText) putExtra(FoodSearchActivity.FOOD_NAME, searchText)
} }
KeyboardUtil.hideKeyboard(this, binding.etInputDish) KeyboardUtil.hideKeyboard(binding.etInputDish)
} }
private fun loadFragment() { private fun loadFragment() {
@@ -153,7 +141,10 @@ class SelectDishActivity : BaseActivity() {
} }
fun deleteCookFoodAndGoods(foodId: String, action: () -> Unit) { fun deleteCookFoodAndGoods(foodId: String, action: () -> Unit) {
appViewModel.deleteCookFoodAndGoods(cookMode = 0, foodId = foodId, onFinish = action) lifecycleScope.launch {
appViewModel.deleteCookFoodAndGoods(cookMode = 0, foodId = foodId)
action()
}
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@@ -189,16 +180,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,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.ui.fragment.CollectFragment import com.shuwei.dish.match.ui.fragment.CollectFragment
import com.shuwei.dish.match.ui.fragment.DeviceConfigFragment import com.shuwei.dish.match.ui.fragment.DeviceConfigFragment
import com.shuwei.dish.match.ui.fragment.SeasoningConfigFragment import com.shuwei.dish.match.ui.fragment.SeasoningConfigFragment
@@ -60,6 +61,7 @@ class SingleFragmentActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHeaderBackground() setHeaderBackground()
// 解析页面类型,非法值直接关闭 // 解析页面类型,非法值直接关闭
@@ -30,9 +30,7 @@ import com.shuwei.dish.match.utils.NetworkUtil
import com.shuwei.dish.match.utils.SizeTool import com.shuwei.dish.match.utils.SizeTool
import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.dp import com.shuwei.dish.match.utils.ext.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** /**
* 子设备专属页面(小屏幕设备使用) * 子设备专属页面(小屏幕设备使用)
@@ -152,10 +150,9 @@ class SlaveActivity : BaseActivity() {
* 主设备未开机时也能正常展示上次配置 * 主设备未开机时也能正常展示上次配置
*/ */
private fun loadSlotsFromDb() { private fun loadSlotsFromDb() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch {
val dao = BaseApp.instance!!.database.seasoningSlotDao() val slots = appViewModel.getSeasoningSlotsByDeviceId(GlobalData.deviceId)
val slots = dao.queryByDeviceId(GlobalData.deviceId) applySlots(slots)
withContext(Dispatchers.Main) { applySlots(slots) }
} }
} }
@@ -169,9 +166,9 @@ class SlaveActivity : BaseActivity() {
?.filter { it.deviceId == GlobalData.deviceId } ?.filter { it.deviceId == GlobalData.deviceId }
?.map { SeasoningSlotEntity(it.deviceId, it.address, it.goodsId, it.goodsName) } ?.map { SeasoningSlotEntity(it.deviceId, it.address, it.goodsId, it.goodsName) }
?: return@onSeasoningConfig ?: return@onSeasoningConfig
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch {
BaseApp.instance!!.database.seasoningSlotDao().upsertAll(mySlots) appViewModel.upsertAllSeasoningSlots(mySlots)
withContext(Dispatchers.Main) { applySlots(mySlots) } applySlots(mySlots)
} }
} }
} }
@@ -1,11 +1,16 @@
package com.shuwei.dish.match.ui package com.shuwei.dish.match.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle import android.os.Bundle
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.util.Log import android.util.Log
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.shuwei.dish.match.R import com.shuwei.dish.match.R
@@ -13,16 +18,18 @@ import com.shuwei.dish.match.adapter.SeasoningWeightAdapter
import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivitySubmitFoodBinding import com.shuwei.dish.match.databinding.ActivitySubmitFoodBinding
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.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.net.NetViewModel import com.shuwei.dish.match.net.NetViewModel
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.scale.ScaleDeviceConfig import com.shuwei.dish.match.scale.ScaleDeviceConfig
import com.shuwei.dish.match.scale.ScaleServiceManager import com.shuwei.dish.match.scale.ScaleServiceManager
import com.shuwei.dish.match.utils.AddressUtil import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.clickWithDebounce import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
@@ -32,8 +39,6 @@ import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toType import com.shuwei.dish.match.utils.ext.toType
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 com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -97,7 +102,6 @@ class SubmitFoodActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivitySubmitFoodBinding.inflate(layoutInflater) binding = ActivitySubmitFoodBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
initViewModel()
setHeaderBackground() setHeaderBackground()
intent.extras?.apply { intent.extras?.apply {
food = getSerializable(FOOD_ITEM) as FoodRecord? food = getSerializable(FOOD_ITEM) as FoodRecord?
@@ -121,12 +125,34 @@ class SubmitFoodActivity : BaseActivity() {
}) })
setupRecyclerView() setupRecyclerView()
addViewListener() addViewListener()
initObserver()
loadSlotsThenObserveScales() loadSlotsThenObserveScales()
onBackPressedDispatcher.addCallback(this) { onBackPressedDispatcher.addCallback(this) {
remindSaveDataDialog() remindSaveDataDialog()
} }
} }
/**
* 收集 submitCookFoodState,处理提交成功/失败
*/
private fun initObserver() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
netViewModel.submitCookFoodState.collect { state ->
when (state) {
is UiState.Success -> submitSuccess(food?.cookMode == 1)
is UiState.Error -> {
toast(state.msg)
dismissLoading()
}
else -> {}
}
}
}
}
}
/** 初始化 RecyclerView2列 GridLayoutManager */ /** 初始化 RecyclerView2列 GridLayoutManager */
private fun setupRecyclerView() { private fun setupRecyclerView() {
binding.rvSeasoning.itemAnimator = null binding.rvSeasoning.itemAnimator = null
@@ -139,17 +165,16 @@ class SubmitFoodActivity : BaseActivity() {
* isCooking=true 时先从数据库查询 goodsList,筛选调料预填充 adapter,再开始观测 * isCooking=true 时先从数据库查询 goodsList,筛选调料预填充 adapter,再开始观测
*/ */
private fun loadSlotsThenObserveScales() { private fun loadSlotsThenObserveScales() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch {
val slots = BaseApp.instance!!.database.seasoningSlotDao().queryAll() val slots = appViewModel.loadSeasoningSlot()
withContext(Dispatchers.Main) {
slots.forEach { slot -> slots.forEach { slot ->
slotMap["${slot.deviceId}#${slot.address}"] = slot.goodsId to slot.goodsName slotMap["${slot.deviceId}#${slot.address}"] = slot.goodsId to slot.goodsName
} }
if (food?.isCooking == true) { if (food?.isCooking == true) {
// 制作中:从数据库查询 goodsList,筛选调料数据预填充 adapter // 制作中:从数据库查询 goodsList,筛选调料数据预填充 adapter
val foodId = food?.foodId ?: return@withContext val foodId = food?.foodId ?: return@launch
val cookMode = food?.cookMode ?: return@withContext val cookMode = food?.cookMode ?: return@launch
appViewModel.getCookFoodGoodsList(foodId, cookMode) { list -> val list = appViewModel.getCookFoodGoodsList(foodId, cookMode)
goodsList = list goodsList = list
list.filter { it.materialType == 3 }.forEach { item -> list.filter { it.materialType == 3 }.forEach { item ->
val name = item.goodsName ?: "" val name = item.goodsName ?: ""
@@ -164,13 +189,11 @@ class SubmitFoodActivity : BaseActivity() {
) )
} }
observeScaleData() observeScaleData()
}
} else { } else {
observeScaleData() observeScaleData()
} }
} }
} }
}
/** /**
* 订阅 ScaleServiceManager.allScales,过滤子设备数据并更新 adapter * 订阅 ScaleServiceManager.allScales,过滤子设备数据并更新 adapter
@@ -240,14 +263,18 @@ class SubmitFoodActivity : BaseActivity() {
} }
private fun addViewListener() { private fun addViewListener() {
binding.btnCook.clickWithDebounce { cook() } binding.btnCook.clickWithDebounce {
val content = getRemindSpannable("开始制作")
showRemindDialog(content) { cook() }
}
binding.btnWeightClear.clickWithDebounce { WeightUtil.tareTwo(2) } binding.btnWeightClear.clickWithDebounce { WeightUtil.tareTwo(2) }
binding.btnSubmit.clickWithDebounce { binding.btnSubmit.clickWithDebounce {
if (cookFoodEntity.foodWeight <= 0.toDouble()) { if (cookFoodEntity.foodWeight <= 0.toDouble()) {
toast("未识别到菜品熟重") toast("未识别到菜品熟重")
return@clickWithDebounce return@clickWithDebounce
} }
submit() val content = getRemindSpannable("制作完成")
showRemindDialog(content) { submit() }
} }
// 监听主设备 2格秤的熟重 // 监听主设备 2格秤的熟重
@@ -271,10 +298,10 @@ class SubmitFoodActivity : BaseActivity() {
return return
} }
val seasoningData = seasoningAdapter.items val seasoningData = seasoningAdapter.items
if (seasoningData.isEmpty()) { // if (seasoningData.isEmpty()) {
toast("未获取到调料信息") // toast("未获取到调料信息")
return // return
} // }
showLoading() showLoading()
// 将调料数据转为 CookFoodGoodsEntity 追加到 goodsList // 将调料数据转为 CookFoodGoodsEntity 追加到 goodsList
@@ -305,16 +332,15 @@ class SubmitFoodActivity : BaseActivity() {
} }
goodsList?.forEach { it.foodId = cookFoodEntity.foodId } goodsList?.forEach { it.foodId = cookFoodEntity.foodId }
lifecycleScope.launch {
appViewModel.saveCookFoodAndGoods( appViewModel.saveCookFoodAndGoods(
cookMode = food!!.cookMode, cookMode = food!!.cookMode,
entity = cookFoodEntity, entity = cookFoodEntity,
list = goodsList, list = goodsList
onFinish = { )
runOnUiThread {
dismissLoading() dismissLoading()
jumpPage(isSamplingData, true) jumpPage(isSamplingData, true)
} }
})
} }
private fun submit() { private fun submit() {
@@ -354,13 +380,7 @@ class SubmitFoodActivity : BaseActivity() {
} }
Log.d(TAG, "submit: json=${cookFoodEntity.toJsonString()}") Log.d(TAG, "submit: json=${cookFoodEntity.toJsonString()}")
netViewModel.submitCookFood( netViewModel.submitCookFood(entity = cookFoodEntity)
entity = cookFoodEntity,
onSuccess = { submitSuccess(isSamplingData) },
onFailure = { _, msg ->
toast(msg)
dismissLoading()
})
} }
private fun submitSuccess(isSamplingData: Boolean) { private fun submitSuccess(isSamplingData: Boolean) {
@@ -369,10 +389,11 @@ class SubmitFoodActivity : BaseActivity() {
jumpPage(isSamplingData, false) jumpPage(isSamplingData, false)
return return
} }
lifecycleScope.launch {
appViewModel.deleteCookFoodAndGoods( appViewModel.deleteCookFoodAndGoods(
cookMode = food!!.cookMode, cookMode = food!!.cookMode,
foodId = food!!.foodId!! foodId = food!!.foodId!!
) { )
dismissLoading() dismissLoading()
jumpPage(isSamplingData, false) jumpPage(isSamplingData, false)
} }
@@ -391,13 +412,6 @@ class SubmitFoodActivity : BaseActivity() {
} }
} }
private lateinit var appViewModel: AppViewModel
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao(), db.seasoningSlotDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
override fun onDestroy() { override fun onDestroy() {
WeightUtil.removeWeightListener(TAG) WeightUtil.removeWeightListener(TAG)
@@ -417,4 +431,22 @@ class SubmitFoodActivity : BaseActivity() {
.show() .show()
} }
/**
* 点击制作和提交按钮确认dialog
*/
private fun showRemindDialog(content: CharSequence, action: () -> Unit) {
CommonDialog(this)
.setTitle("温馨提示")
.setContent(content)
.setNegativeButton("取消")
.setPositiveButton("确认") { action() }
.show()
}
private fun getRemindSpannable(cookStateText: String) = buildSpannableString {
appendText("请确认是否 ")
appendText(cookStateText, StyleSpan(Typeface.BOLD), ForegroundColorSpan(Color.BLACK))
appendText(" ")
}
} }
@@ -1,6 +1,7 @@
package com.shuwei.dish.match.ui.fragment package com.shuwei.dish.match.ui.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
@@ -22,6 +23,7 @@ import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
import com.shuwei.dish.match.dialog.Loading import com.shuwei.dish.match.dialog.Loading
import com.shuwei.dish.match.objbox.FoodCollectionBean import com.shuwei.dish.match.objbox.FoodCollectionBean
import com.shuwei.dish.match.objbox.FoodModule import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.ui.CollectedFoodActivity
import com.shuwei.dish.match.ui.SingleFragmentActivity import com.shuwei.dish.match.ui.SingleFragmentActivity
import com.shuwei.dish.match.utils.BitmapSaver import com.shuwei.dish.match.utils.BitmapSaver
import com.shuwei.dish.match.utils.CameraUtils import com.shuwei.dish.match.utils.CameraUtils
@@ -243,7 +245,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>(){
} }
binding.btnCollectedFood.setOnClickListener { binding.btnCollectedFood.setOnClickListener {
// startActivity(Intent(requireActivity(), CollectedFoodActivity::class.java)) startActivity(Intent(requireActivity(), CollectedFoodActivity::class.java))
} }
binding.btnTakePhoto.clickWithDebounce { binding.btnTakePhoto.clickWithDebounce {
val count = foodCollectionList.count { it.bitmap != null } val count = foodCollectionList.count { it.bitmap != null }
@@ -39,7 +39,7 @@ class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
var modeDesc: String var modeDesc: String
) )
// private lateinit var appViewModel: AppViewModel // private lateinit var appViewModel: DbViewModel
// private val weightArray = SparseArray<Double>() // private val weightArray = SparseArray<Double>()
// private val addressArray = AddressUtil.getWeighAddressArray() // private val addressArray = AddressUtil.getWeighAddressArray()
@@ -54,22 +54,12 @@ class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
} }
override fun initialize() { override fun initialize() {
initViewModel()
initUI() initUI()
// addWeighListener() // addWeighListener()
// addGridItemListener() // addGridItemListener()
// loadSeasoning() // loadSeasoning()
} }
/**
* 初始化ViewModel
*/
private fun initViewModel() {
// val db = BaseApp.instance!!.database
// val factory = AppFactory(AppRepository(db.appDao()))
// appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
/** /**
* 初始化UI * 初始化UI
*/ */
@@ -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(
param = mutableMapOf(
"pageNum" to pageNo, "pageNum" to pageNo,
"pageSize" to pageSize, "pageSize" to pageSize,
"placeId" to BaseApp.canteenId, "placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText() "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,24 +232,41 @@ 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 ) {
try {
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
} }
val tempList = mutableListOf<FoodRecord>() if (pageNo == 1) list.clear()
cookFoodEntities.forEachIndexed { index, entity -> val mergedList = records?.toMutableList() ?: mutableListOf()
val food = list.firstOrNull { it.foodId == entity.foodId }
// 仅 pageNo == 1 时才有本地数据参与合并
if (!effectiveLocalList.isNullOrEmpty()) {
val cookingItems = mutableListOf<FoodRecord>()
effectiveLocalList.forEachIndexed { index, entity ->
val food = mergedList.firstOrNull { it.foodId == entity.foodId }
if (food != null) { if (food != null) {
mergedList.remove(food)
food.isCooking = true food.isCooking = true
food.sort = index food.sort = index
food.dinnerType = entity.dinnerType ?: "0" food.dinnerType = entity.dinnerType ?: "0"
tempList.add(food) cookingItems.add(food)
} else { } else {
//当前查询到的list不包括本地数据id,可能数据在很多页以后了,暂时只能直接构造数据 // 当前网络数据不包含该本地记录,直接构造
tempList.add( cookingItems.add(
FoodRecord( FoodRecord(
foodId = entity.foodId, foodId = entity.foodId,
foodName = entity.foodName, foodName = entity.foodName,
@@ -276,36 +277,20 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
) )
} }
} }
list.removeAll(tempList) mergedList.addAll(0, cookingItems)
list.addAll(0, tempList)
dishAdapter.notifyDataSetChanged()
}
} }
@SuppressLint("NotifyDataSetChanged") if (pageNo >= 2 && !localList.isNullOrEmpty()) {
private fun loadDishList(records: MutableList<FoodRecord>?) { //第2页起,从mergedList中移除localList中的同foodId数据
try { val localIds = localList.map { it.foodId }.toHashSet()
if (isAdded.not()) { mergedList.removeAll { it.foodId in localIds }
return
} }
if (records.isNullOrEmpty()) {
//toast("暂未搜索到相关菜品信息") list.addAll(mergedList)
if (pageNo == 1) {
loadEmptyView()
}
return
}
if (pageNo == 1) {
list.clear()
}
list.addAll(records)
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()
} }
@@ -13,7 +13,6 @@ import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.FlexboxLayoutManager
import com.shuwei.dish.match.adapter.Seasoning18GridAdapter import com.shuwei.dish.match.adapter.Seasoning18GridAdapter
import com.shuwei.dish.match.adapter.Seasoning22GridAdapter import com.shuwei.dish.match.adapter.Seasoning22GridAdapter
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.base.DeviceRole import com.shuwei.dish.match.base.DeviceRole
import com.shuwei.dish.match.base.GlobalData import com.shuwei.dish.match.base.GlobalData
@@ -29,9 +28,7 @@ import com.shuwei.dish.match.ui.SingleFragmentActivity
import com.shuwei.dish.match.utils.SizeTool import com.shuwei.dish.match.utils.SizeTool
import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.dp import com.shuwei.dish.match.utils.ext.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() { class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
@@ -51,7 +48,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
) = FragmentSeasoningConfigBinding.inflate(inflater, container, false) ) = FragmentSeasoningConfigBinding.inflate(inflater, container, false)
private lateinit var currentActivity: SingleFragmentActivity private lateinit var currentActivity: SingleFragmentActivity
// private lateinit var appViewModel: AppViewModel // private lateinit var appViewModel: DbViewModel
private val adapter22 by lazy { buildAdapter22() } private val adapter22 by lazy { buildAdapter22() }
private val adapter18 by lazy { private val adapter18 by lazy {
@@ -149,11 +146,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
* 仅主设备执行广播,子设备不调用此方法 * 仅主设备执行广播,子设备不调用此方法
*/ */
private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) { private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch {
val dao = BaseApp.instance!!.database.seasoningSlotDao() currentActivity.appViewModel.upsertSeasoningSlot(slot)
dao.upsert(slot)
// 读取全量配置,通过 wsClient 推送给所有已连接子设备 // 读取全量配置,通过 wsClient 推送给所有已连接子设备
val allSlots = dao.queryAll().map { val allSlots = currentActivity.appViewModel.loadSeasoningSlot().map {
SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName) SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName)
} }
ScaleServiceManager.sendSeasoningConfig(allSlots) ScaleServiceManager.sendSeasoningConfig(allSlots)
@@ -162,8 +158,8 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
// /** 初始化 ViewModel */ // /** 初始化 ViewModel */
// private fun initViewModel() { // private fun initViewModel() {
// val factory = AppFactory(AppRepository(BaseApp.instance!!.database.appDao())) // val factory = AppFactory(DbRepository(BaseApp.instance!!.database.appDao()))
// appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java] // appViewModel = ViewModelProvider(this, factory)[DbViewModel::class.java]
// } // }
@@ -205,12 +201,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
private fun loadData() { private fun loadData() {
adapter22.update(emptyList()) adapter22.update(emptyList())
adapter18.update(emptyList()) adapter18.update(emptyList())
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch {
val dao = BaseApp.instance!!.database.seasoningSlotDao() val slots22 = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_22)
val slots22 = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_22) val slots18 = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_18)
val slots18 = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_18) val slotsOil = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_1)
val slotsOil = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_1)
withContext(Dispatchers.Main) {
slots22.forEach { slot -> slots22.forEach { slot ->
val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address) val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address)
if (pos >= 0) adapter22.updateItemName(pos, slot.goodsName) if (pos >= 0) adapter22.updateItemName(pos, slot.goodsName)
@@ -225,7 +219,6 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
if (oilSlot != null && oilSlot.goodsName.isNotEmpty()) oilSlot.goodsName else "-" if (oilSlot != null && oilSlot.goodsName.isNotEmpty()) oilSlot.goodsName else "-"
} }
} }
}
/** /**
* 注册重量监听,检测重量增加超过阈值的秤,高亮对应格子并通知主设备 * 注册重量监听,检测重量增加超过阈值的秤,高亮对应格子并通知主设备
@@ -7,7 +7,8 @@ import android.view.inputmethod.InputMethodManager
object KeyboardUtil { object KeyboardUtil {
fun hideKeyboard(context: Context, view: View) { fun hideKeyboard(view: View) {
val context = view.context
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0) imm.hideSoftInputFromWindow(view.windowToken, 0)
view.clearFocus() // 清除焦点避免键盘再次弹出 view.clearFocus() // 清除焦点避免键盘再次弹出
@@ -1,217 +0,0 @@
package com.shuwei.dish.match.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import kotlinx.coroutines.launch
import kotlin.collections.forEach
class AppViewModel(private val rep: AppRepository) : ViewModel() {
fun updateCookFood(entity: CookFoodEntity) {
viewModelScope.launch {
rep.updateCookFood(entity)
}
}
fun getCookFoodById(foodId: String, cookMode: Int, action: (CookFoodEntity?) -> Unit) {
viewModelScope.launch {
val data = rep.getCookFoodById(foodId, cookMode)
action(data)
}
}
fun getCookFoodList(
cookMode: Int,
dinnerType: String = "0",
action: (MutableList<CookFoodEntity>?) -> Unit
) {
viewModelScope.launch {
val data = rep.getCookFoodList(cookMode, dinnerType)
action(data)
}
}
fun countCookFood(cookMode: Int, action: (Int) -> Unit) {
viewModelScope.launch {
val count = rep.countCookFood(cookMode)
action(count)
}
}
// fun saveCookFood(cookMode: Int, entity: CookFoodEntity) {
// viewModelScope.launch {
// val data = rep.getCookFoodById(entity.foodId, cookMode)
// if (data == null) {
// rep.insertCookFood(entity)
// return@launch
// }
// rep.updateCookFood(entity)
// }
// }
fun saveCookFoodAndGoods(
cookMode: Int,
entity: CookFoodEntity,
list: MutableList<CookFoodGoodsEntity>?,
onFinish: () -> Unit
) {
viewModelScope.launch {
val data = rep.getCookFoodById(entity.foodId, cookMode)
if (data != null) {
//存在数据删除
rep.deleteCookFood(entity.foodId, cookMode)
rep.deleteCookFoodGoodsList(data.id, entity.foodId)
}
//重新保存数据
rep.insertCookFood(entity)
val newData = rep.getCookFoodById(entity.foodId, cookMode)
list?.let { it ->
it.forEach { goods ->
goods.id = 0
goods.pid = newData?.id?:0
}
rep.insertGoodsList(it)
}
onFinish()
}
}
fun deleteCookFoodAndGoods(
cookMode: Int,
foodId: String,
onFinish: () -> Unit
) {
viewModelScope.launch {
val data = rep.getCookFoodById(foodId, cookMode)
if (data != null) {
rep.deleteCookFood(foodId = foodId, cookMode = cookMode)
rep.deleteCookFoodGoodsList(pid = data.id, foodId = foodId)
}
onFinish()
}
}
//--------------------------------------------------------------------------------------------------------
fun getCookFoodGoodsList(
foodId: String,
cookMode: Int,
action: (list: MutableList<CookFoodGoodsEntity>) -> Unit
) {
viewModelScope.launch {
val cookFood = rep.getCookFoodById(foodId, cookMode)
cookFood?.let {
val list = rep.getCookFoodGoodsList(pid = it.id, foodId = it.foodId)
action(list)
}
}
}
// fun updateGoods(entity: CookFoodGoodsEntity) {
// viewModelScope.launch {
// rep.updateGoods(entity)
// }
// }
// fun saveCookFoodGoods(list: MutableList<CookFoodGoodsEntity>) {
// viewModelScope.launch {
// val foodId = list[0].foodId
// val data: MutableList<CookFoodGoodsEntity>? =
// rep.getCookFoodGoodsList(foodId!!)
// if (data.isNullOrEmpty()) {
// rep.insertGoodsList(list)
// return@launch
// }
// val localDbSeasoningList = rep.getCookFoodGoodsListByType(foodId, 3)
// localDbSeasoningList.forEach {
// it.isDel = 1
// //更新本地调料数据为已删除的状态
// rep.updateGoods(it)
// }
// //筛选出调料数据保存,主辅材不会变更不需要再次处理
// val saveSeasoningList = list.filter { it.materialType == 3 }
// rep.insertGoodsList(saveSeasoningList.toMutableList())
// }
// }
//--------------------------------------------------------------------------------------------------------
// fun addSeasoning(list: MutableList<SeasoningEntity>, callback: () -> Unit) {
// viewModelScope.launch {
// rep.deleteBatch()
// rep.insertSeasoningList(items = list)
// callback()
// }
// }
private var isProcessing = false
fun loadSeasoning(action: (MutableList<SeasoningEntity>) -> Unit) {
viewModelScope.launch {
if (isProcessing) return@launch
isProcessing = true
val list = rep.getAllStream()
action(list)
isProcessing = false
}
}
fun deleteSeasoningBySort(sort: Int) {
viewModelScope.launch {
rep.deleteSeasoningBySort(sort)
}
}
fun clearAllSeasoning(action:()->Unit) {
viewModelScope.launch {
rep.clearAllSeasoning()
action()
}
}
/**
* 查询指定设备是否已配置调料槽位
* @param deviceId 目标设备 ID
* @param action 回调,true 表示已配置,false 表示未配置
*/
fun hasSeasoningSlotConfig(deviceId: String, action: (Boolean) -> Unit) {
viewModelScope.launch {
action(rep.hasSeasoningSlotConfig(deviceId))
}
}
// fun updateAll(list: MutableList<SeasoningEntity>) {
// viewModelScope.launch {
// list.forEach {
// rep.updateSeasoning(it)
// }
// }
// }
fun saveSeasoning(entity: SeasoningEntity, block: () -> Unit) {
viewModelScope.launch {
val list = rep.getSeasoningBySort(entity.sort)
val size = list?.size?:0
if (size == 1) {
rep.updateSeasoning(entity.also { it.id = list!![0].id })
block()
return@launch
}
if (size > 1) {
list?.forEach {
rep.deleteSeasoningBySort(it.sort)
}
}
entity.id = 0
val id = rep.insertSeasoning(entity)
entity.id = id
block()
}
}
//--------------------------------------------------------------------------------------------------------
}
@@ -1,42 +0,0 @@
//package com.shuwei.dish.match.viewmodel
//
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.CookFoodGoodsRepository
//import com.shuwei.dish.match.entity.CookFoodGoodsEntity
//import kotlinx.coroutines.launch
//
//class CookFoodGoodsViewModel(private val rep: CookFoodGoodsRepository) : ViewModel() {
//
// fun getCookFoodGoodsList(
// foodId: String,
// action: (list: MutableList<CookFoodGoodsEntity>) -> Unit
// ) {
// viewModelScope.launch {
// val list = rep.cookFoodGoodsDao.getCookFoodGoodsList(foodId)
// action(list)
// }
// }
//
// fun saveCookFoodGoods(list: MutableList<CookFoodGoodsEntity>) {
// viewModelScope.launch {
// val foodId = list[0].foodId
// val data: MutableList<CookFoodGoodsEntity>? =
// rep.cookFoodGoodsDao.getCookFoodGoodsList(foodId!!)
// if (data.isNullOrEmpty()) {
// rep.cookFoodGoodsDao.insertAll(list)
// return@launch
// }
// val localDbSeasoningList = rep.cookFoodGoodsDao.getCookFoodGoodsListByType(foodId, 3)
// localDbSeasoningList.forEach {
// it.isDel = 1
// //更新本地调料数据为已删除的状态
// rep.cookFoodGoodsDao.update(it)
// }
// //筛选出调料数据保存,主辅材不会变更不需要再次处理
// val saveSeasoningList = list.filter { it.materialType == 3 }
// rep.cookFoodGoodsDao.insertAll(saveSeasoningList.toMutableList())
// }
// }
//
//}
@@ -1,44 +0,0 @@
//package com.shuwei.dish.match.viewmodel
//
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.CookFoodRepository
//import com.shuwei.dish.match.entity.CookFoodEntity
//import kotlinx.coroutines.launch
//
//class CookFoodViewModel(private val rep: CookFoodRepository) : ViewModel() {
//
// fun getCookFoodById(foodId: String, cookMode: Int, action: (CookFoodEntity?) -> Unit) {
// viewModelScope.launch {
// val data = rep.cookFoodDao.getCookFoodById(foodId, cookMode)
// action(data)
// }
// }
//
// fun getCookFoodList(cookMode: Int, action: (MutableList<CookFoodEntity>?) -> Unit) {
// viewModelScope.launch {
// val data = rep.cookFoodDao.getCookFoodList(cookMode)
// action(data)
// }
// }
//
// fun countCookFood(cookMode: Int, action: (Int) -> Unit) {
// viewModelScope.launch {
// val count = rep.cookFoodDao.countCookFood(cookMode)
// action(count)
// }
// }
//
// fun saveCookFood(cookMode: Int, entity: CookFoodEntity) {
// viewModelScope.launch {
// val data = rep.cookFoodDao.getCookFoodById(entity.foodId, cookMode)
// if (data == null) {
// rep.cookFoodDao.insert(entity)
// return@launch
// }
// rep.cookFoodDao.update(entity)
//
// }
// }
//
//}
@@ -1,47 +0,0 @@
//package com.shuwei.dish.match.viewmodel
//
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.SeasoningRepository
//import com.shuwei.dish.match.entity.SeasoningEntity
//import kotlinx.coroutines.delay
//import kotlinx.coroutines.launch
//
//class DeviceSettingViewModel(private val rep: SeasoningRepository) : ViewModel() {
// fun addSeasoning(list: MutableList<SeasoningEntity>, callback:()-> Unit) {
// viewModelScope.launch {
// rep.seasoningDao.deleteBatch()
// delay(500)
// val rows: Array<Long>? = rep.seasoningDao.insertAll(items = list)
// if (rows.isNullOrEmpty().not()) {
// callback()
// }
// }
// }
//
// private var isProcessing = false
// fun loadSeasoning(action: (list: MutableList<SeasoningEntity>) -> Unit) {
// viewModelScope.launch {
// if (isProcessing) return@launch
// isProcessing = true
// rep.seasoningDao.getAllStream().collect { list ->
// action(list)
// isProcessing = false
// }
// }
// }
//
// fun deleteAll() {
// viewModelScope.launch {
// rep.seasoningDao.deleteBatch()
// }
// }
//
// fun updateAll(list: MutableList<SeasoningEntity>) {
// viewModelScope.launch {
// list.forEach {
// rep.seasoningDao.update(it)
// }
// }
// }
//}
@@ -1,24 +0,0 @@
//package com.shuwei.dish.match.viewmodel
//
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import com.shuwei.dish.match.db.SeasoningRepository
//import com.shuwei.dish.match.entity.CookFoodEntity
//import com.shuwei.dish.match.entity.SeasoningEntity
//import kotlinx.coroutines.launch
//
//class SubmitDishViewModel(private val rep: SeasoningRepository) : ViewModel() {
//
// private var isProcessing = false
// fun loadSeasoning(action: (list: MutableList<SeasoningEntity>) -> Unit) {
// viewModelScope.launch {
// if (isProcessing) return@launch
// isProcessing = true
// rep.seasoningDao.getAllStream().collect { list ->
// action(list)
// isProcessing = false
// }
// }
// }
//
//}
@@ -1,44 +0,0 @@
package com.shuwei.dish.match.viewmodel.factory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.db.dao.SeasoningSlotDao
import com.shuwei.dish.match.viewmodel.AppViewModel
@Suppress("UNCHECKED_CAST")
class AppFactory(private val repo: AppRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return AppViewModel(repo) as T
}
}
//
//@Suppress("UNCHECKED_CAST")
//class DeviceSettingFactory(private val repo: SeasoningRepository) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return DeviceSettingViewModel(repo) as T
// }
//}
//
////@Suppress("UNCHECKED_CAST")
////class SubmitDishFactory(private val repo: SeasoningRepository) : ViewModelProvider.Factory {
//// override fun <T : ViewModel> create(modelClass: Class<T>): T {
//// return SubmitDishViewModel(repo) as T
//// }
////}
//
//@Suppress("UNCHECKED_CAST")
//class CookFoodFactory(private val repo: CookFoodRepository) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return CookFoodViewModel(repo) as T
// }
//}
//
//@Suppress("UNCHECKED_CAST")
//class CookFoodGoodsFactory(private val repo: CookFoodGoodsRepository) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return CookFoodGoodsViewModel(repo) as T
// }
//}
+2 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#FFFF3232"/> <!-- <solid android:color="#FFFF3232"/>-->
<solid android:color="@color/dish_green"/>
<corners android:radius="12dp"/> <corners android:radius="12dp"/>
</shape> </shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 拍照按钮背景:圆角矩形,填充色 #00BC71 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#00BC71" />
<corners android:radius="12dp" />
</shape>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 白色相机图标:外圆环 evenOdd 镂空,机身实心,镜头白色小圆 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<!-- 外圆环:大圆 + 小圆 evenOdd,中间透明 -->
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="
M28,4
A24,24 0 1,1 27.9999,4 Z
M28,10
A18,18 0 1,0 28.0001,10 Z" />
<!-- 相机机身:evenOdd 在镜头位置挖透明圆孔 -->
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="
M20,22 L22.5,18 L33.5,18 L36,22 L38,22
A2,2 0 0,1 40,24 L40,36
A2,2 0 0,1 38,38 L18,38
A2,2 0 0,1 16,36 L16,24
A2,2 0 0,1 18,22 Z
M28,23
A6,6 0 1,0 28.0001,23 Z" />
<!-- 镜头中心:白色小圆点 -->
<path
android:fillColor="#FFFFFF"
android:pathData="M28,25.5 A3.5,3.5 0 1,0 28.0001,25.5 Z" />
</vector>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 红色圆形背景 + 白色 X 图标,用于列表项删除按钮 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<!-- 红色圆形背景 #FFEE4444 -->
<path
android:fillColor="#00BC71"
android:pathData="M24,2C11.85,2 2,11.85 2,24C2,36.15 11.85,46 24,46C36.15,46 46,36.15 46,24C46,11.85 36.15,2 24,2Z" />
<!-- 白色 X 图标 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M33,16.41L31.59,15L24,22.59L16.41,15L15,16.41L22.59,24L15,31.59L16.41,33L24,25.41L31.59,33L33,31.59L25.41,24L33,16.41Z" />
</vector>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 绿色圆角背景 + 白色垃圾桶图标,替代红色的 ic_delete_red.png -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 绿色圆角背景 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#00BC71" />
<corners android:radius="10dp" />
</shape>
</item>
<!-- 白色垃圾桶图标,居中显示 -->
<item
android:drawable="@drawable/ic_trash_white"
android:gravity="center" />
</layer-list>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 白色垃圾桶图标:使用 evenOdd 填充规则实现条纹镂空,无硬编码背景色依赖 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<!-- 桶盖 -->
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="
M10,13 A1,1 0 0,1 10,11 L14,11 L14,9
A2,2 0 0,1 16,7 L24,7
A2,2 0 0,1 26,9 L26,11 L30,11
A1,1 0 0,1 30,13 Z
M16,9 L16,11 L24,11 L24,9 Z" />
<!-- 桶身:外轮廓 + 三条竖槽镂空,evenOdd 使竖槽透明 -->
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="
M12,15 L13.5,33
A2,2 0 0,0 15.5,35 L24.5,35
A2,2 0 0,0 26.5,33 L28,15 Z
M17,18 L17,32 L19,32 L19,18 Z
M19.5,18 L19.5,32 L21.5,32 L21.5,18 Z
M22,18 L22,32 L24,32 L24,18 Z" />
</vector>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#FFFF3232"/> <solid android:color="@color/dish_green"/>
<size android:width="10dp" android:height="40dp"/> <size android:width="10dp" android:height="40dp"/>
<corners android:radius="5dp"/> <corners android:radius="5dp"/>
</shape> </shape>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="@color/white"
android:orientation="vertical">
<!-- 搜索栏 -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="16dp">
<EditText
android:id="@+id/editSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/setting_border_gray3"
android:gravity="center_vertical"
android:hint="输入食材名称"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:paddingStart="47dp"
android:paddingEnd="80dp"
android:textColor="@color/black"
android:textColorHint="#FFC4CFDA"
android:textSize="36sp" />
<ImageView
android:id="@+id/btnSearch"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="15dp"
android:padding="10dp"
android:src="@drawable/ic_search2"
tools:ignore="ContentDescription" />
</FrameLayout>
<!-- 已采集食材列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCollectedFood"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never"
tools:itemCount="5"
tools:listitem="@layout/list_item_collected_food" />
</LinearLayout>
+1 -1
View File
@@ -15,7 +15,7 @@
android:layout_marginTop="55dp" android:layout_marginTop="55dp"
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:gravity="center" android:gravity="center"
android:textColor="@color/black" android:textColor="@color/black333"
android:textSize="36sp" android:textSize="36sp"
android:textStyle="bold" /> android:textStyle="bold" />
+25 -7
View File
@@ -20,7 +20,7 @@
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginTop="30dp" android:layout_marginTop="30dp"
android:drawablePadding="17dp" android:drawablePadding="17dp"
android:text="餐品采集" android:text="食材采集"
android:textColor="@color/black" android:textColor="@color/black"
android:textSize="36sp" android:textSize="36sp"
android:textStyle="bold" android:textStyle="bold"
@@ -35,7 +35,7 @@
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:includeFontPadding="false" android:includeFontPadding="false"
android:text="已采集餐品" android:text="已采集食材"
android:textColor="#ff5e7585" android:textColor="#ff5e7585"
android:textSize="30sp" android:textSize="30sp"
android:textStyle="bold" android:textStyle="bold"
@@ -98,15 +98,33 @@
android:foreground="@drawable/ripple_effect"/> android:foreground="@drawable/ripple_effect"/>
<!-- android:background="?android:attr/selectableItemBackgroundBorderless"--> <!-- android:background="?android:attr/selectableItemBackgroundBorderless"-->
<ImageView <!-- 拍照按钮:绿色圆角背景 + 相机图标 + 文字 -->
<LinearLayout
android:id="@+id/btnTakePhoto" android:id="@+id/btnTakePhoto"
android:layout_width="212dp" android:layout_width="212dp"
android:layout_height="155dp" android:layout_height="155dp"
android:src="@drawable/ic_take_photo" android:background="@drawable/bg_take_photo"
android:foreground="@drawable/ripple_effect"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@id/flPreview" app:layout_constraintBottom_toBottomOf="@id/flPreview"
app:layout_constraintEnd_toEndOf="@id/btnCollectedFood" app:layout_constraintEnd_toEndOf="@id/btnCollectedFood">
tools:ignore="ContentDescription"
android:foreground="@drawable/ripple_effect"/> <ImageView
android:layout_width="72dp"
android:layout_height="72dp"
android:src="@drawable/ic_camera_white"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="拍照"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList" android:id="@+id/rvFoodList"
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="32dp"
android:paddingVertical="24dp">
<!-- 食材名称和采集数量 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="32sp"
android:textStyle="bold"
tools:text="三河小炒" />
<TextView
android:id="@+id/tvCollectCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#ff5e7585"
android:textSize="26sp"
tools:text="已采集153" />
</LinearLayout>
<!-- 删除按钮 -->
<ImageView
android:id="@+id/ivDelete"
android:layout_width="100dp"
android:layout_height="100dp"
android:padding="25dp"
android:src="@drawable/ic_close_red"
tools:ignore="ContentDescription" />
</LinearLayout>
<!-- 分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="32dp"
android:background="#FFE8EDF2" />
</LinearLayout>
@@ -19,7 +19,7 @@
android:layout_height="52dp" android:layout_height="52dp"
android:layout_gravity="end|top" android:layout_gravity="end|top"
android:padding="6dp" android:padding="6dp"
android:src="@drawable/ic_delete_red" /> android:src="@drawable/ic_delete_green" />
<ImageView <ImageView
android:id="@+id/ivFinish" android:id="@+id/ivFinish"