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:screenOrientation="portrait"
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
android:name="com.shuwei.dish.match.ui.SlaveActivity"
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.databinding.ActivityBaseBinding
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.db.DbViewModel
import com.shuwei.dish.match.ui.InitActivity
import com.shuwei.dish.match.utils.ActivityManager
import com.shuwei.dish.match.utils.ext.dp
@@ -41,8 +40,6 @@ open class BaseActivity : AppCompatActivity() {
private lateinit var binding: ActivityBaseBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 提前隐藏系统栏,避免窗口创建时导航栏闪烁
@@ -183,22 +180,7 @@ open class BaseActivity : AppCompatActivity() {
val netViewModel: NetViewModel by viewModels()
fun queryGoodsList(
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)
}
val appViewModel: DbViewModel by viewModels()
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var activityCallback: ((intent: Intent?) -> Unit)? = null
@@ -6,7 +6,9 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
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.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
@@ -27,11 +29,12 @@ import com.shuwei.dish.match.entity.SeasoningSlotEntity
exportSchema = true
)
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
@@ -1,110 +1,103 @@
package com.shuwei.dish.match.db
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.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.SeasoningSlotEntity
import kotlinx.coroutines.Dispatchers
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) {
appDao.insertCookFood(item)
db.cookFoodDao().insertCookFood(item)
}
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) {
appDao.getCookFoodById(BaseApp.canteenId, foodId, cookMode)
db.cookFoodDao().getCookFoodById(BaseApp.canteenId, foodId, cookMode)
}
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) {
appDao.countCookFood(BaseApp.canteenId, cookMode)
db.cookFoodDao().countCookFood(BaseApp.canteenId, cookMode)
}
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) {
appDao.deleteCookFoodGoodsList(pid, foodId)
suspend fun deleteCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().deleteCookFoodGoodsList(pid, foodId)
}
//--------------------------------------------------------------------------------------------
// suspend fun insertGoods(item: CookFoodGoodsEntity) = withContext(Dispatchers.IO) {
// appDao.insertGoods(item)
// }
suspend fun insertGoodsList(items: MutableList<CookFoodGoodsEntity>) =
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) {
appDao.getCookFoodGoodsList(pid, foodId)
suspend fun getCookFoodGoodsList(pid: Long, foodId: String) = withContext(Dispatchers.IO) {
db.cookFoodGoodsDao().getCookFoodGoodsList(pid, foodId)
}
suspend fun getCookFoodGoodsListByType(
foodId: String,
materialType: Int
) = withContext(Dispatchers.IO) {
appDao.getCookFoodGoodsListByType(foodId, materialType)
db.cookFoodGoodsDao().getCookFoodGoodsListByType(foodId, materialType)
}
//--------------------------------------------------------------------------------------------
suspend fun getSeasoningById(id: Long) = withContext(Dispatchers.IO) {
appDao.getSeasoningById(id)
db.seasoningDao().getSeasoningById(id)
}
suspend fun getSeasoningBySort(sort:Int) = withContext(Dispatchers.IO) {
appDao.getSeasoningBySort(sort)
suspend fun getSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) {
db.seasoningDao().getSeasoningBySort(sort)
}
suspend fun getSeasoningByGoodsId(goodsId: Int) = withContext(Dispatchers.IO) {
appDao.getSeasoningByGoodsId(goodsId)
db.seasoningDao().getSeasoningByGoodsId(goodsId)
}
suspend fun getAllStream() = withContext(Dispatchers.IO) {
appDao.getAllStream()
db.seasoningDao().getAllStream()
}
suspend fun search(query: String) = withContext(Dispatchers.IO) {
appDao.search(query)
db.seasoningDao().search(query)
}
suspend fun clearAllSeasoning() = withContext(Dispatchers.IO) {
appDao.clearAllSeasoning()
db.seasoningDao().clearAllSeasoning()
}
suspend fun deleteSeasoningBySort(sort: Int) = withContext(Dispatchers.IO) {
appDao.deleteSeasoningBySort(sort)
db.seasoningDao().deleteSeasoningBySort(sort)
}
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>) =
withContext(Dispatchers.IO) {
appDao.insertSeasoningList(items)
db.seasoningDao().insertSeasoningList(items)
}
suspend fun updateSeasoning(item: SeasoningEntity) = withContext(Dispatchers.IO) {
appDao.updateSeasoning(item)
db.seasoningDao().updateSeasoning(item)
}
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 表示未配置
*/
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
//
//import androidx.room.Dao
//import androidx.room.Query
//import com.shuwei.dish.match.db.BaseDao
//import com.shuwei.dish.match.entity.CookFoodEntity
//
//@Dao
//interface CookFoodDao : BaseDao<CookFoodEntity> {
//
// @Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND foodId = :foodId AND cookMode = :cookMode")
// suspend fun getCookFoodById(foodId: String, cookMode:Int): CookFoodEntity?
//
// @Query("SELECT * FROM dm_cook_food WHERE isDel = 0 AND cookMode = :cookMode")
// suspend fun getCookFoodList(cookMode:Int): MutableList<CookFoodEntity>?
//
// @Query("SELECT count(1) FROM dm_cook_food WHERE isDel = 0 AND cookMode = :cookMode")
// suspend fun countCookFood(cookMode:Int): Int
//
//}
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
@Dao
interface CookFoodDao {
@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)
}
@@ -1,29 +1,30 @@
//package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao
//import androidx.room.Query
//import com.shuwei.dish.match.db.BaseDao
//import com.shuwei.dish.match.entity.CookFoodGoodsEntity
//
//@Dao
//interface CookFoodGoodsDao : BaseDao<CookFoodGoodsEntity> {
//
// @Query("SELECT * FROM dm_cook_food_goods WHERE isDel = 0 AND foodId = :foodId")
// suspend fun getCookFoodGoodsList(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(
//// """
//// UPDATE dm_cook_food_goods
//// SET goodsName=:entity.goodsName, foodId=:entity.foodId, relateionType=:entity.relateionType, sort=:entity.sort
//// allEdible=:entity.allEdible, useWeight=:entity.useWeight, isDel=:entity.isDel, createTime=:entity.createTime
//// WHERE isDel = 0
//// AND foodId=:entity.foodId
//// AND goodsId=:goodsId
//// """
//// )
//// fun updateByGoodsId(entity: CookFoodGoodsEntity)
//
//}
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.CookFoodGoodsEntity
@Dao
interface CookFoodGoodsDao {
@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)
}
@@ -1,25 +1,42 @@
//package com.shuwei.dish.match.db.dao
//
//import androidx.room.Dao
//import androidx.room.Query
//import com.shuwei.dish.match.db.BaseDao
//import com.shuwei.dish.match.entity.SeasoningEntity
//import kotlinx.coroutines.flow.Flow
//
//@Dao
//interface SeasoningDao : BaseDao<SeasoningEntity> {
//
//// @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 ORDER BY sort ASC")
//// fun getAllStream(): Flow<MutableList<SeasoningEntity>>
////
//// @Query("SELECT * FROM dm_seasoning WHERE isDel = 0 AND name LIKE '%' || :query || '%'")
//// suspend fun search(query: String): MutableList<SeasoningEntity>
////
//// @Query("DELETE FROM dm_seasoning WHERE rowid IN " +
//// "(SELECT rowid FROM dm_seasoning LIMIT 1000)")
//// suspend fun deleteBatch(): Int
//
//}
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.SeasoningEntity
@Dao
interface SeasoningDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSeasoning(item: SeasoningEntity): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSeasoningList(items: MutableList<SeasoningEntity>): Array<Long>
@Update
suspend fun updateSeasoning(item: SeasoningEntity)
@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(): 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 var titleText: String? = null
private var contentText: String? = null
private var contentText: CharSequence? = null
private var negativeText = "取消"
private var positiveText = "确认"
private var neutralText: String? = null
@@ -36,7 +36,7 @@ open class CommonDialog(
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按钮模式下对应顶部按钮) */
fun setNegativeButton(text: String, onClick: (() -> Unit)? = null): CommonDialog = apply {
@@ -8,6 +8,9 @@ import android.view.LayoutInflater
import android.view.View
import androidx.core.view.isEmpty
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.RecyclerView
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.databinding.DialogFoodSearchBinding
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.ext.addOnActionSearchListener
import com.shuwei.dish.match.utils.ext.toast
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/**
* 食材搜索弹窗,继承 BottomSheetDialog 确保只初始化一次
@@ -95,7 +101,9 @@ class FoodSearchDialog(
}
binding.ivSearch.setOnClickListener { searchGoods(it) }
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it.context, it) }
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
initObserver()
// 若有默认食材名称,自动填充并触发搜索
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
getGoodsList()
KeyboardUtil.hideKeyboard(v.context, v)
KeyboardUtil.hideKeyboard(v)
}
/**
@@ -130,12 +158,7 @@ class FoodSearchDialog(
if (!goodsName.isNullOrBlank()) {
param["goodsName"] = goodsName!!
}
activity.queryGoodsList(param = param, onSuccess = {
loadGoodsList(it)
}, onFailure = { _, msg ->
activity.toast(msg)
finishRefresh()
})
activity.netViewModel.queryGoodsList(param)
}
/**
@@ -18,6 +18,9 @@ import android.view.WindowManager
import androidx.core.graphics.toColorInt
import androidx.core.view.isEmpty
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.RecyclerView
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.databinding.DialogSeasoningSearchBinding
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.WeightUtil
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.roundedDecimalPlace
import com.shuwei.dish.match.utils.ext.toast
import kotlinx.coroutines.launch
/**
* 调料搜索弹窗,继承 BottomSheetDialog 确保只初始化一次
@@ -106,10 +111,10 @@ class SeasoningSearchDialog(
}
// RecyclerView 初始化及滑动冲突处理
binding.recyclerView.run {
layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
adapter = this@SeasoningSearchDialog.adapter
addOnScrollListener(object : RecyclerView.OnScrollListener() {
binding.recyclerView.let {
it.layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
it.adapter = adapter
it.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(rv, dx, dy)
// 解决 RecyclerView 与 SmartRefreshLayout 滑动冲突
@@ -127,7 +132,7 @@ class SeasoningSearchDialog(
}
binding.ivSearch.setOnClickListener { searchGoods(it) }
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it.context, it) }
binding.root.setOnClickListener { KeyboardUtil.hideKeyboard(it) }
// 去皮按钮
binding.tvClear.setOnClickListener {
@@ -147,6 +152,8 @@ class SeasoningSearchDialog(
)
binding.tvWeight.text = getTextSpan(0.0)
initObserver()
// 若有默认调料名称,自动填充并触发搜索
if (!clickName.isNullOrBlank()) {
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
getGoodsList()
KeyboardUtil.hideKeyboard(v.context, v)
KeyboardUtil.hideKeyboard(v)
}
/**
@@ -181,12 +208,7 @@ class SeasoningSearchDialog(
if (!goodsName.isNullOrBlank()) {
param["goodsName"] = goodsName!!
}
activity.querySeasoningList(param = param, onSuccess = {
loadGoodsList(it)
}, onFailure = { _, msg ->
activity.toast(msg)
finishRefresh()
})
activity.netViewModel.querySeasoningList(param)
}
/**
@@ -106,7 +106,7 @@ class SeasoningSelectDialog(
}
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
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.FoodRecord
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
/**
@@ -16,81 +19,102 @@ class NetViewModel(
private val repository: RemoteRepository = RemoteRepository()
) : ViewModel() {
/**
* 提交制作菜品的 UI 状态流,UI 层通过 collect 监听
*/
private val _submitCookFoodState = MutableStateFlow<UiState<Any?>>(UiState.Idle)
val submitCookFoodState: StateFlow<UiState<Any?>> = _submitCookFoodState.asStateFlow()
/**
* 提交制作菜品
*/
fun submitCookFood(
entity: CookFoodEntity,
onSuccess: (Any?) -> Unit,
onFailure: (String, String) -> Unit
) {
fun submitCookFood(entity: CookFoodEntity) {
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(
foodId: String,
onSuccess: (CookFoodEntity?) -> Unit,
onFailure: (String, String) -> Unit
) {
fun getFoodDetail(foodId: String) {
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(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
fun searchFoodList(param: MutableMap<String, Any>) {
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(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
fun getSamplingList(param: MutableMap<String, Any>) {
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(
param: MutableMap<String, Any>,
onSuccess: (MutableList<CookFoodGoodsEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
fun queryGoodsList(param: MutableMap<String, Any>) {
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(
param: MutableMap<String, Any>,
onSuccess: (MutableList<SeasoningEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
fun querySeasoningList(param: MutableMap<String, Any>) {
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 onSuccess 成功回调
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
* @return UiState 包装的结果,Success 表示提交成功,Error 携带错误信息
*/
suspend fun submitCookFood(
entity: CookFoodEntity,
onSuccess: (Any?) -> Unit,
onFailure: (String, String) -> Unit
) = request(
onRequest = { apiService.submitCookFood(param = entity) },
onSuccess = onSuccess,
onFailure = onFailure
)
suspend fun submitCookFood(entity: CookFoodEntity): UiState<Any?> {
return try {
val resp = apiService.submitCookFood(param = entity)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/**
* 查询菜品详情
* @param foodId 菜品 ID
* @param onSuccess 成功回调
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
* @return UiState 包装的结果,Success 携带详情数据,Error 携带错误信息
*/
suspend fun getFoodDetail(
foodId: String,
onSuccess: (CookFoodEntity?) -> Unit,
onFailure: (String, String) -> Unit
) = request(
onRequest = { apiService.getFoodDetail(foodId = foodId) },
onSuccess = onSuccess,
onFailure = onFailure
)
suspend fun getFoodDetail(foodId: String): UiState<CookFoodEntity?> {
return try {
val resp = apiService.getFoodDetail(foodId = foodId)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/**
* 搜索菜品列表
* @param param 查询参数
* @param onSuccess 成功回调
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
*/
suspend fun searchFoodList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) = request(
onRequest = { apiService.searchFoodList(param = param) },
onSuccess = onSuccess,
onFailure = onFailure
)
suspend fun searchFoodList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> {
return try {
val resp = apiService.searchFoodList(param = param)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/**
* 查询采样数据列表
* @param param 查询参数
* @param onSuccess 成功回调
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
*/
suspend fun getSamplingList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) = request(
onRequest = { apiService.getSamplingList(param = param) },
onSuccess = onSuccess,
onFailure = onFailure
)
suspend fun getSamplingList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> {
return try {
val resp = apiService.getSamplingList(param = param)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/**
* 查询物品信息列表
* @param param 查询参数
* @param onSuccess 成功回调
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
*/
suspend fun queryGoodsList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<CookFoodGoodsEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) = request(
onRequest = { apiService.queryGoodsList(param = param) },
onSuccess = onSuccess,
onFailure = onFailure
)
suspend fun queryGoodsList(param: MutableMap<String, Any>): UiState<MutableList<CookFoodGoodsEntity>?> {
return try {
val resp = apiService.queryGoodsList(param = param)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
/**
* 查询调料信息列表
* @param param 查询参数
* @param onSuccess 成功回调
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
*/
suspend fun querySeasoningList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<SeasoningEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) = request(
onRequest = { apiService.querySeasoningList(param = param) },
onSuccess = onSuccess,
onFailure = onFailure
)
suspend fun querySeasoningList(param: MutableMap<String, Any>): UiState<MutableList<SeasoningEntity>?> {
return try {
val resp = apiService.querySeasoningList(param = param)
if (resp.isSuccess()) UiState.Success(resp.data)
else UiState.Error(resp.code, resp.msg ?: "")
} catch (e: Exception) {
val ex = getApiException(e)
UiState.Error("-1", ex.errorMsg)
}
}
}
@@ -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()
}
/**
* 按名称分组统计采集数量
* 使用属性查询只加载 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 const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录
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 com.shuwei.dish.match.base.BaseActivity
//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.entity.SeasoningEntity
//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.toast
//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.databinding.ActivityDeviceConfigBinding
//import com.shuwei.dish.match.dialog.SeasoningSearchDialog
@@ -61,7 +59,7 @@
//
// private lateinit var binding: ActivityDeviceConfigBinding
//
// private lateinit var appViewModel: AppViewModel
// private lateinit var appViewModel: DbViewModel
//
// private var cookMode: Int = 0
//// private val weightArray = SparseIntArray()
@@ -200,9 +198,9 @@
// private fun initViewModel() {
// val db = BaseApp.instance!!.database
// val factory =
// AppFactory(AppRepository(db.appDao()))
// AppFactory(DbRepository(db.appDao()))
// appViewModel =
// ViewModelProvider(this, factory)[AppViewModel::class.java]
// ViewModelProvider(this, factory)[DbViewModel::class.java]
// }
//
// private fun loadSeasoning() {
@@ -7,7 +7,6 @@ import android.util.Log
import android.view.View
import androidx.activity.addCallback
import androidx.activity.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
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.BaseApp
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.CommonDialog
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.toast
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
class DishSamplingActivity : BaseActivity() {
@@ -62,7 +58,6 @@ class DishSamplingActivity : BaseActivity() {
setTitleBar()
setHeaderBackground()
addViewClickListener()
initViewModel()
foodName = intent.getStringExtra(FOOD_NAME)
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) {
appViewModel.getCookFoodList(cookMode = 1) { action(it) }
fun getCookFoodList() {
appViewModel.getCookFoodList(cookMode = 1)
}
/**
@@ -269,7 +256,7 @@ class DishSamplingActivity : BaseActivity() {
setSelection(text.length)
}
}
KeyboardUtil.hideKeyboard(v.context, v)
KeyboardUtil.hideKeyboard(v)
}
private fun searchDishType() {
@@ -4,6 +4,9 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
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.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.LayoutEmptyViewBinding
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.ext.addOnActionSearchListener
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.visible
import java.io.Serializable
import kotlinx.coroutines.launch
class FoodSearchActivity : BaseActivity() {
@@ -83,13 +88,38 @@ class FoodSearchActivity : BaseActivity() {
adapter = recordAdapter
}
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() {
binding.ivDishSearch.setOnClickListener { v ->
pageNo = 1
getInputAndSearch()
KeyboardUtil.hideKeyboard(v.context, v)
KeyboardUtil.hideKeyboard(v)
}
binding.refreshLayout.setEnableRefresh(true)
binding.refreshLayout.setEnableLoadMore(false)
@@ -106,13 +136,13 @@ class FoodSearchActivity : BaseActivity() {
queryListInfo(foodName!!)
}
binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v.context, v)
KeyboardUtil.hideKeyboard(v)
}
binding.etInputDish.let { v ->
v.addOnActionSearchListener {
pageNo = 1
getInputAndSearch()
KeyboardUtil.hideKeyboard(v.context, v)
KeyboardUtil.hideKeyboard( v)
}
}
}
@@ -140,61 +170,6 @@ class FoodSearchActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged")
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>(
"foodName" to input.trim(),
"pageNum" to pageNo,
@@ -202,40 +177,27 @@ class FoodSearchActivity : BaseActivity() {
"placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText()
)
netViewModel.searchFoodList(
param = map,
onSuccess = {
delayDismissLoading()
finishRefresh()
binding.refreshLayout.setEnableRefresh(true)
if (it.isNullOrEmpty()) {
// toast("暂未搜索到相关菜品信息")
if (pageNo == 1) {
loadEmptyView()
}
return@searchFoodList
}
if (pageNo == 1) {
list.clear()
}
list.addAll(it)
recordAdapter.notifyDataSetChanged()
val isLoadMoreEnable = it.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
}, onFailure = { code, msg ->
binding.refreshLayout.run {
setEnableRefresh(true)
}
toast(msg)
delayDismissLoading()
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
})
netViewModel.searchFoodList(param = map)
}
/**
* 处理搜索结果,填充列表或展示空视图
*/
@SuppressLint("NotifyDataSetChanged")
private fun handleSearchResult(records: MutableList<FoodRecord>?) {
delayDismissLoading()
finishRefresh()
binding.refreshLayout.setEnableRefresh(true)
if (records.isNullOrEmpty()) {
if (pageNo == 1) loadEmptyView()
return
}
if (pageNo == 1) list.clear()
list.addAll(records)
recordAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) pageNo++
}
private fun finishRefresh() {
@@ -3,20 +3,18 @@ package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.activity.addCallback
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.HomeModeAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
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.utils.SpTool
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import kotlinx.coroutines.launch
class HomeActivity : BaseActivity() {
@@ -32,7 +30,6 @@ class HomeActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
statusBarDarkFont(enable = true)
initViewModel()
// // 获取 ANDROID_ID
// val deviceId =
// Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
@@ -134,35 +131,29 @@ 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() {
appViewModel.loadSeasoning {
if (it.isEmpty()) {
//无调料信息打开采样历史列表页面,点击设置配置调料信息
lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot()
if (slots.isEmpty()) {
// 无调料信息,打开采样历史列表页面,点击设置配置调料信息
startActivity<SamplingListActivity>()
finish()
return@loadSeasoning
return@launch
}
appViewModel.countCookFood(cookMode = 1) { count ->
if (count > 0) {
//有烹饪中数据打开采样历史列表页面
startActivity<SamplingListActivity>()
finish()
return@countCookFood
}
//无烹饪数据打开新增采样页面
startActivity<PrepareCookActivity> {
putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME)
}
val count = appViewModel.countCookFood(cookMode = 1)
if (count > 0) {
// 有烹饪中数据打开采样历史列表页面
startActivity<SamplingListActivity>()
finish()
return@launch
}
// 无烹饪数据,打开新增采样页面
startActivity<PrepareCookActivity> {
putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME)
}
finish()
}
}
@@ -9,14 +9,12 @@ import android.view.KeyEvent
import android.view.animation.RotateAnimation
import androidx.activity.addCallback
import androidx.core.graphics.toColorInt
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.DeviceRole
import com.shuwei.dish.match.base.GlobalData
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.scale.ScaleServiceManager
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.startActivity
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.launch
@@ -38,7 +34,6 @@ class InitActivity : BaseActivity() {
}
private lateinit var binding: ActivityInitBinding
private lateinit var appViewModel: AppViewModel
private var startTime = 0L // 倒计时开始时间
private var lastNetworkCheckTime = 0L // 上次检测网络的时间
@@ -59,8 +54,6 @@ class InitActivity : BaseActivity() {
BaseApp.canteenId = "0"
initViewModel()
WeightUtil.init()
WeightUtil.getWeight()
WeightUtil.startContinuousRead()
@@ -68,11 +61,6 @@ class InitActivity : BaseActivity() {
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) {
lifecycleScope.launch {
@@ -100,22 +88,22 @@ class InitActivity : BaseActivity() {
}
private fun goSampling() {
appViewModel.loadSeasoning {
if (it.isEmpty()) {
//无调料信息打开采样历史列表页面,点击设置配置调料信息
lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot()
if (slots.isEmpty()) {
// 无调料信息,打开采样历史列表页面,点击设置配置调料信息
startActivity<SamplingListActivity>()
return@loadSeasoning
return@launch
}
appViewModel.countCookFood(cookMode = 1) { count ->
if (count > 0) {
//有烹饪中数据打开采样历史列表页面
startActivity<SamplingListActivity>()
return@countCookFood
}
//无烹饪数据打开新增采样页面
startActivity<PrepareCookActivity> {
putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME)
}
val count = appViewModel.countCookFood(cookMode = 1)
if (count > 0) {
// 有烹饪中数据打开采样历史列表页面
startActivity<SamplingListActivity>()
return@launch
}
// 无烹饪数据打开新增采样页面
startActivity<PrepareCookActivity> {
putExtra(PrepareCookActivity.PAGE_FROM, PrepareCookActivity.HOME)
}
}
}
@@ -30,8 +30,6 @@ import com.shuwei.dish.match.databinding.ListItemScale22RowBinding
import com.shuwei.dish.match.scale.ScaleData
import com.shuwei.dish.match.scale.ScaleDeviceConfig
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.SizeTool
import com.shuwei.dish.match.utils.WeightUtil
@@ -53,6 +51,7 @@ class MasterScaleActivity : BaseActivity() {
private lateinit var binding: ActivityMasterScaleBinding
private val size by lazy { SizeTool.getFlexLayoutSize(10.dp) }
private data class DeviceGroup(
val deviceId: String,
val ip: String,
@@ -81,14 +80,12 @@ class MasterScaleActivity : BaseActivity() {
/** 从 Room 加载槽位配置到 slotNameMap,加载完成后启动数据观测 */
private fun loadSlotNames() {
lifecycleScope.launch(Dispatchers.IO) {
val slots = BaseApp.instance!!.database.seasoningSlotDao().queryAll()
withContext(Dispatchers.Main) {
slots.forEach { slot ->
slotNameMap["${slot.deviceId}#${slot.address}"] = slot.goodsName
}
observeScaleData()
lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot()
slots.forEach { slot ->
slotNameMap["${slot.deviceId}#${slot.address}"] = slot.goodsName
}
observeScaleData()
}
}
@@ -8,7 +8,9 @@ import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.activity.addCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
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.CookFoodGoodsEntity
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.utils.AddressUtil
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.visible
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.Serializable
@@ -110,6 +114,7 @@ class PrepareCookActivity : BaseActivity() {
addBackKeyListener()
initCamera(binding.flCameraContainer)
initRecyclerView()
initObserver()
if (!isSamplingMode) {
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() {
// //649
// val url = "${UrlConfig.DISH_DETAIL}?foodId=${food?.foodId}"
@@ -285,17 +312,7 @@ class PrepareCookActivity : BaseActivity() {
// toast(msg)
// }
netViewModel.getFoodDetail(
food?.foodId ?: "",
onSuccess = { detail ->
if (detail == null) {
toast("查询菜品信息为空")
return@getFoodDetail
}
loadDishDetail(detail)
}, onFailure = { code, msg ->
toast(msg)
})
netViewModel.getFoodDetail(food?.foodId ?: "")
}
@@ -534,7 +551,7 @@ class PrepareCookActivity : BaseActivity() {
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.view.LayoutInflater
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.LinearLayoutManager
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.databinding.ActivitySamplingBinding
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.entity.CookFoodEntity
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.toast
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 kotlinx.coroutines.launch
import com.shuwei.dish.match.net.UiState
class SamplingListActivity : BaseActivity() {
@@ -44,13 +45,65 @@ class SamplingListActivity : BaseActivity() {
setContentView(binding.root)
setTitleBar()
setHeaderBackground()
initViewModel()
initRecyclerView()
addViewListener()
initObserver()
requestData(true)
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() {
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) {
appViewModel.getCookFoodList(cookMode = 1) { action(it) }
fun getCookFoodList() {
appViewModel.getCookFoodList(cookMode = 1)
}
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) {
@@ -190,53 +240,12 @@ class SamplingListActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged")
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(
param = mutableMapOf(
"pageNum" to pageNo,
"pageSize" to pageSize,
"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")
fun refreshLocalData() {
showLoading()
//查询本地数据
getCookFoodList { cookFoodEntities ->
delayDismissLoading()
if (cookFoodEntities.isNullOrEmpty()) {
loadEmptyView()
return@getCookFoodList
}
val tempList = mutableListOf<FoodRecord>()
cookFoodEntities.sortedByDescending { DateTimeUtil.convert(dateStr = it.createTime) }
.forEach { entity ->
tempList.add(FoodRecord().apply {
foodId = entity.foodId
foodName = entity.foodName
cookMode = 1
isCooking = true
})
}
list.clear()
list.addAll(tempList)
dishAdapter.notifyDataSetChanged()
binding.refreshLayout.run {
setEnableRefresh(false)
setEnableLoadMore(false)
}
}
appViewModel.resetCookFoodList()
getCookFoodList()
}
private fun requestData(isCooking: Boolean) {
if (isCooking) {
// 清除上次网络请求的残留状态
netViewModel.resetSamplingListState()
refreshLocalData()
} else {
// 同理,清除本地数据残留状态
appViewModel.resetCookFoodList()
pageNo = 1
getSamplingList()
}
@@ -367,10 +356,11 @@ class SamplingListActivity : BaseActivity() {
block()
return
}
appViewModel.loadSeasoning {
if (it.isEmpty()) {
lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot()
if (slots.isEmpty()) {
showDeviceConfigDialog()
return@loadSeasoning
return@launch
}
configFinished = true
block()
@@ -3,15 +3,13 @@ package com.shuwei.dish.match.ui
import android.content.Intent
import android.os.Bundle
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.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
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.entity.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.ui.fragment.DishListFragment
import com.shuwei.dish.match.utils.KeyboardUtil
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.toast
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() {
@@ -32,8 +28,6 @@ class SelectDishActivity : BaseActivity() {
private lateinit var binding: ActivitySelectDishBinding
private lateinit var appViewModel: AppViewModel
private var dinnerType: String = "1"
private val fragmentList = mutableListOf<DishListFragment>().apply {
@@ -60,20 +54,14 @@ class SelectDishActivity : BaseActivity() {
}, backAction = {
it.gone()
})
initViewModel()
addViewListener()
loadFragment()
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) {
appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType) { action(it) }
fun getCookFoodList() {
appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType)
}
private var configFinished = false
@@ -132,7 +120,7 @@ class SelectDishActivity : BaseActivity() {
putExtra(FoodSearchActivity.DINNER_TYPE, dinnerType)
putExtra(FoodSearchActivity.FOOD_NAME, searchText)
}
KeyboardUtil.hideKeyboard(this, binding.etInputDish)
KeyboardUtil.hideKeyboard(binding.etInputDish)
}
private fun loadFragment() {
@@ -153,7 +141,10 @@ class SelectDishActivity : BaseActivity() {
}
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) {
@@ -189,16 +180,8 @@ class SelectDishActivity : BaseActivity() {
isVisible = false
}
public fun getFoodList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
netViewModel.searchFoodList(
param = param,
onSuccess = onSuccess,
onFailure = onFailure
)
public fun getFoodList(param: MutableMap<String, Any>) {
netViewModel.searchFoodList(param = param)
}
}
@@ -5,6 +5,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
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.DeviceConfigFragment
import com.shuwei.dish.match.ui.fragment.SeasoningConfigFragment
@@ -60,6 +61,7 @@ class SingleFragmentActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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.WeightUtil
import com.shuwei.dish.match.utils.ext.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* 子设备专属页面(小屏幕设备使用)
@@ -152,10 +150,9 @@ class SlaveActivity : BaseActivity() {
* 主设备未开机时也能正常展示上次配置
*/
private fun loadSlotsFromDb() {
lifecycleScope.launch(Dispatchers.IO) {
val dao = BaseApp.instance!!.database.seasoningSlotDao()
val slots = dao.queryByDeviceId(GlobalData.deviceId)
withContext(Dispatchers.Main) { applySlots(slots) }
lifecycleScope.launch {
val slots = appViewModel.getSeasoningSlotsByDeviceId(GlobalData.deviceId)
applySlots(slots)
}
}
@@ -169,9 +166,9 @@ class SlaveActivity : BaseActivity() {
?.filter { it.deviceId == GlobalData.deviceId }
?.map { SeasoningSlotEntity(it.deviceId, it.address, it.goodsId, it.goodsName) }
?: return@onSeasoningConfig
lifecycleScope.launch(Dispatchers.IO) {
BaseApp.instance!!.database.seasoningSlotDao().upsertAll(mySlots)
withContext(Dispatchers.Main) { applySlots(mySlots) }
lifecycleScope.launch {
appViewModel.upsertAllSeasoningSlots(mySlots)
applySlots(mySlots)
}
}
}
@@ -1,11 +1,16 @@
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.util.Log
import androidx.activity.addCallback
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import com.google.gson.reflect.TypeToken
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.BaseApp
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.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
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.ScaleServiceManager
import com.shuwei.dish.match.utils.AddressUtil
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.gone
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.toast
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.flow.collectLatest
import kotlinx.coroutines.launch
@@ -97,7 +102,6 @@ class SubmitFoodActivity : BaseActivity() {
super.onCreate(savedInstanceState)
binding = ActivitySubmitFoodBinding.inflate(layoutInflater)
setContentView(binding.root)
initViewModel()
setHeaderBackground()
intent.extras?.apply {
food = getSerializable(FOOD_ITEM) as FoodRecord?
@@ -121,12 +125,34 @@ class SubmitFoodActivity : BaseActivity() {
})
setupRecyclerView()
addViewListener()
initObserver()
loadSlotsThenObserveScales()
onBackPressedDispatcher.addCallback(this) {
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 */
private fun setupRecyclerView() {
binding.rvSeasoning.itemAnimator = null
@@ -139,35 +165,32 @@ class SubmitFoodActivity : BaseActivity() {
* isCooking=true 时先从数据库查询 goodsList,筛选调料预填充 adapter,再开始观测
*/
private fun loadSlotsThenObserveScales() {
lifecycleScope.launch(Dispatchers.IO) {
val slots = BaseApp.instance!!.database.seasoningSlotDao().queryAll()
withContext(Dispatchers.Main) {
slots.forEach { slot ->
slotMap["${slot.deviceId}#${slot.address}"] = slot.goodsId to slot.goodsName
}
if (food?.isCooking == true) {
// 制作中:从数据库查询 goodsList,筛选调料数据预填充 adapter
val foodId = food?.foodId ?: return@withContext
val cookMode = food?.cookMode ?: return@withContext
appViewModel.getCookFoodGoodsList(foodId, cookMode) { list ->
goodsList = list
list.filter { it.materialType == 3 }.forEach { item ->
val name = item.goodsName ?: ""
val weight = item.useWeight ?: 0.0
baseSeasoningMap[name] = (item.goodsId) to weight
seasoningAdapter.updateItem(
SeasoningWeightAdapter.Item(
goodsId = item.goodsId,
goodsName = name,
useWeight = weight
)
)
}
observeScaleData()
}
} else {
observeScaleData()
lifecycleScope.launch {
val slots = appViewModel.loadSeasoningSlot()
slots.forEach { slot ->
slotMap["${slot.deviceId}#${slot.address}"] = slot.goodsId to slot.goodsName
}
if (food?.isCooking == true) {
// 制作中:从数据库查询 goodsList,筛选调料数据预填充 adapter
val foodId = food?.foodId ?: return@launch
val cookMode = food?.cookMode ?: return@launch
val list = appViewModel.getCookFoodGoodsList(foodId, cookMode)
goodsList = list
list.filter { it.materialType == 3 }.forEach { item ->
val name = item.goodsName ?: ""
val weight = item.useWeight ?: 0.0
baseSeasoningMap[name] = (item.goodsId) to weight
seasoningAdapter.updateItem(
SeasoningWeightAdapter.Item(
goodsId = item.goodsId,
goodsName = name,
useWeight = weight
)
)
}
observeScaleData()
} else {
observeScaleData()
}
}
}
@@ -240,14 +263,18 @@ class SubmitFoodActivity : BaseActivity() {
}
private fun addViewListener() {
binding.btnCook.clickWithDebounce { cook() }
binding.btnCook.clickWithDebounce {
val content = getRemindSpannable("开始制作")
showRemindDialog(content) { cook() }
}
binding.btnWeightClear.clickWithDebounce { WeightUtil.tareTwo(2) }
binding.btnSubmit.clickWithDebounce {
if (cookFoodEntity.foodWeight <= 0.toDouble()) {
toast("未识别到菜品熟重")
return@clickWithDebounce
}
submit()
val content = getRemindSpannable("制作完成")
showRemindDialog(content) { submit() }
}
// 监听主设备 2格秤的熟重
@@ -271,10 +298,10 @@ class SubmitFoodActivity : BaseActivity() {
return
}
val seasoningData = seasoningAdapter.items
if (seasoningData.isEmpty()) {
toast("未获取到调料信息")
return
}
// if (seasoningData.isEmpty()) {
// toast("未获取到调料信息")
// return
// }
showLoading()
// 将调料数据转为 CookFoodGoodsEntity 追加到 goodsList
@@ -305,16 +332,15 @@ class SubmitFoodActivity : BaseActivity() {
}
goodsList?.forEach { it.foodId = cookFoodEntity.foodId }
appViewModel.saveCookFoodAndGoods(
cookMode = food!!.cookMode,
entity = cookFoodEntity,
list = goodsList,
onFinish = {
runOnUiThread {
dismissLoading()
jumpPage(isSamplingData, true)
}
})
lifecycleScope.launch {
appViewModel.saveCookFoodAndGoods(
cookMode = food!!.cookMode,
entity = cookFoodEntity,
list = goodsList
)
dismissLoading()
jumpPage(isSamplingData, true)
}
}
private fun submit() {
@@ -354,13 +380,7 @@ class SubmitFoodActivity : BaseActivity() {
}
Log.d(TAG, "submit: json=${cookFoodEntity.toJsonString()}")
netViewModel.submitCookFood(
entity = cookFoodEntity,
onSuccess = { submitSuccess(isSamplingData) },
onFailure = { _, msg ->
toast(msg)
dismissLoading()
})
netViewModel.submitCookFood(entity = cookFoodEntity)
}
private fun submitSuccess(isSamplingData: Boolean) {
@@ -369,10 +389,11 @@ class SubmitFoodActivity : BaseActivity() {
jumpPage(isSamplingData, false)
return
}
appViewModel.deleteCookFoodAndGoods(
cookMode = food!!.cookMode,
foodId = food!!.foodId!!
) {
lifecycleScope.launch {
appViewModel.deleteCookFoodAndGoods(
cookMode = food!!.cookMode,
foodId = food!!.foodId!!
)
dismissLoading()
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() {
WeightUtil.removeWeightListener(TAG)
@@ -417,4 +431,22 @@ class SubmitFoodActivity : BaseActivity() {
.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
import android.annotation.SuppressLint
import android.content.Intent
import android.content.Context
import android.graphics.Bitmap
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.objbox.FoodCollectionBean
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.utils.BitmapSaver
import com.shuwei.dish.match.utils.CameraUtils
@@ -243,7 +245,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>(){
}
binding.btnCollectedFood.setOnClickListener {
// startActivity(Intent(requireActivity(), CollectedFoodActivity::class.java))
startActivity(Intent(requireActivity(), CollectedFoodActivity::class.java))
}
binding.btnTakePhoto.clickWithDebounce {
val count = foodCollectionList.count { it.bitmap != null }
@@ -39,7 +39,7 @@ class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
var modeDesc: String
)
// private lateinit var appViewModel: AppViewModel
// private lateinit var appViewModel: DbViewModel
// private val weightArray = SparseArray<Double>()
// private val addressArray = AddressUtil.getWeighAddressArray()
@@ -54,22 +54,12 @@ class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
}
override fun initialize() {
initViewModel()
initUI()
// addWeighListener()
// addGridItemListener()
// loadSeasoning()
}
/**
* 初始化ViewModel
*/
private fun initViewModel() {
// val db = BaseApp.instance!!.database
// val factory = AppFactory(AppRepository(db.appDao()))
// appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
/**
* 初始化UI
*/
@@ -5,6 +5,9 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
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.LinearLayoutManager
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.databinding.FragmentDishListBinding
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.net.UiState
import com.shuwei.dish.match.ui.PrepareCookActivity
import com.shuwei.dish.match.ui.SelectDishActivity
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.toast
import java.io.Serializable
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
class DishListFragment : BaseFragment<FragmentDishListBinding>() {
@@ -108,6 +115,7 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
itemTouchHelper.attachToRecyclerView(this)
}
addViewListener()
initObserver()
pageNo = 1
activity.showLoading()
@@ -115,6 +123,39 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
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() {
binding.refreshLayout.run {
setEnableRefresh(true)
@@ -147,72 +188,15 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
@SuppressLint("NotifyDataSetChanged")
@Suppress("unchecked_cast")
fun getDishList() {
activity.showLoading()
activity.getFoodList(
param = mutableMapOf(
"pageNum" to pageNo,
"pageSize" to pageSize,
"placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText()
),
onSuccess = { records ->
activity.delayDismissLoading()
finishRefresh()
loadDishList(records)
},
onFailure = { code, msg ->
if (isAdded.not()) {
return@getFoodList
}
toast(msg)
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
activity.delayDismissLoading()
}
val param = mutableMapOf<String, Any>(
"pageNum" to pageNo,
"pageSize" to pageSize,
"placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText()
)
// val map = mapOf(
//// "foodName" to input,
// "dinnerType" to getDinnerTypeText(),
// "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()
// }
// })
// 网络与本地并行触发,pageNo > 1 时不重复查询本地数据
activity.getFoodList(param = param)
if (pageNo == 1) activity.getCookFoodList()
}
private var emptyViewBinding: LayoutEmptyViewBinding? = null
@@ -248,64 +232,65 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
}
}
/**
* 将网络数据与本地烹饪中数据合并后渲染列表
* pageNo > 1 时忽略本地数据,直接追加网络数据,避免 combine 带入旧的本地状态
*/
@SuppressLint("NotifyDataSetChanged")
private fun refreshLocalData(list: MutableList<FoodRecord>) {
activity.getCookFoodList { cookFoodEntities ->
if (cookFoodEntities.isNullOrEmpty()) {
return@getCookFoodList
}
val tempList = mutableListOf<FoodRecord>()
cookFoodEntities.forEachIndexed { index, entity ->
val food = list.firstOrNull { it.foodId == entity.foodId }
if (food != null) {
food.isCooking = true
food.sort = index
food.dinnerType = entity.dinnerType ?: "0"
tempList.add(food)
} else {
//当前查询到的list不包括本地数据id,可能数据在很多页以后了,暂时只能直接构造数据
tempList.add(
FoodRecord(
foodId = entity.foodId,
foodName = entity.foodName,
sort = index,
dinnerType = entity.dinnerType ?: "0",
isCooking = true
)
)
}
}
list.removeAll(tempList)
list.addAll(0, tempList)
dishAdapter.notifyDataSetChanged()
}
}
@SuppressLint("NotifyDataSetChanged")
private fun loadDishList(records: MutableList<FoodRecord>?) {
private fun loadAndMergeDishList(
records: MutableList<FoodRecord>?,
localList: MutableList<CookFoodEntity>?
) {
try {
if (isAdded.not()) {
if (isAdded.not()) return
// pageNo > 1 时不参与本地合并,避免 combine 带入旧的本地状态
val effectiveLocalList = if (pageNo == 1) localList else null
if (records.isNullOrEmpty() && effectiveLocalList.isNullOrEmpty()) {
if (pageNo == 1) loadEmptyView()
return
}
if (records.isNullOrEmpty()) {
//toast("暂未搜索到相关菜品信息")
if (pageNo == 1) {
loadEmptyView()
if (pageNo == 1) list.clear()
val mergedList = records?.toMutableList() ?: mutableListOf()
// 仅 pageNo == 1 时才有本地数据参与合并
if (!effectiveLocalList.isNullOrEmpty()) {
val cookingItems = mutableListOf<FoodRecord>()
effectiveLocalList.forEachIndexed { index, entity ->
val food = mergedList.firstOrNull { it.foodId == entity.foodId }
if (food != null) {
mergedList.remove(food)
food.isCooking = true
food.sort = index
food.dinnerType = entity.dinnerType ?: "0"
cookingItems.add(food)
} else {
// 当前网络数据不包含该本地记录,直接构造
cookingItems.add(
FoodRecord(
foodId = entity.foodId,
foodName = entity.foodName,
sort = index,
dinnerType = entity.dinnerType ?: "0",
isCooking = true
)
)
}
}
return
mergedList.addAll(0, cookingItems)
}
if (pageNo == 1) {
list.clear()
if (pageNo >= 2 && !localList.isNullOrEmpty()) {
//第2页起,从mergedList中移除localList中的同foodId数据
val localIds = localList.map { it.foodId }.toHashSet()
mergedList.removeAll { it.foodId in localIds }
}
list.addAll(records)
list.addAll(mergedList)
dishAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
val isLoadMoreEnable = (records?.size ?: 0) >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
refreshLocalData(list)
if (isLoadMoreEnable) pageNo++
} catch (e: Exception) {
e.printStackTrace()
}
@@ -13,7 +13,6 @@ import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayoutManager
import com.shuwei.dish.match.adapter.Seasoning18GridAdapter
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.DeviceRole
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.WeightUtil
import com.shuwei.dish.match.utils.ext.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
@@ -51,7 +48,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
) = FragmentSeasoningConfigBinding.inflate(inflater, container, false)
private lateinit var currentActivity: SingleFragmentActivity
// private lateinit var appViewModel: AppViewModel
// private lateinit var appViewModel: DbViewModel
private val adapter22 by lazy { buildAdapter22() }
private val adapter18 by lazy {
@@ -149,11 +146,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
* 仅主设备执行广播,子设备不调用此方法
*/
private fun saveSlotAndBroadcast(slot: SeasoningSlotEntity) {
lifecycleScope.launch(Dispatchers.IO) {
val dao = BaseApp.instance!!.database.seasoningSlotDao()
dao.upsert(slot)
lifecycleScope.launch {
currentActivity.appViewModel.upsertSeasoningSlot(slot)
// 读取全量配置,通过 wsClient 推送给所有已连接子设备
val allSlots = dao.queryAll().map {
val allSlots = currentActivity.appViewModel.loadSeasoningSlot().map {
SlotConfig(it.deviceId, it.address, it.goodsId, it.goodsName)
}
ScaleServiceManager.sendSeasoningConfig(allSlots)
@@ -162,8 +158,8 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
// /** 初始化 ViewModel */
// private fun initViewModel() {
// val factory = AppFactory(AppRepository(BaseApp.instance!!.database.appDao()))
// appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
// val factory = AppFactory(DbRepository(BaseApp.instance!!.database.appDao()))
// appViewModel = ViewModelProvider(this, factory)[DbViewModel::class.java]
// }
@@ -205,25 +201,22 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
private fun loadData() {
adapter22.update(emptyList())
adapter18.update(emptyList())
lifecycleScope.launch(Dispatchers.IO) {
val dao = BaseApp.instance!!.database.seasoningSlotDao()
val slots22 = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_22)
val slots18 = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_18)
val slotsOil = dao.queryByDeviceId(ScaleDeviceConfig.DEVICE_ID_1)
withContext(Dispatchers.Main) {
slots22.forEach { slot ->
val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address)
if (pos >= 0) adapter22.updateItemName(pos, slot.goodsName)
}
slots18.forEach { slot ->
val pos = ScaleDeviceConfig.SCALE_ORDER_18.indexOf(slot.address)
if (pos >= 0) adapter18.updateItemName(pos, slot.goodsName)
}
// 加载油盆格子名称(address=1
val oilSlot = slotsOil.firstOrNull { it.address == 1 }
binding.tvOilSlot.text =
if (oilSlot != null && oilSlot.goodsName.isNotEmpty()) oilSlot.goodsName else "-"
lifecycleScope.launch {
val slots22 = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_22)
val slots18 = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_18)
val slotsOil = currentActivity.appViewModel.getSeasoningSlotsByDeviceId(ScaleDeviceConfig.DEVICE_ID_1)
slots22.forEach { slot ->
val pos = ScaleDeviceConfig.SCALE_ORDER_22.indexOf(slot.address)
if (pos >= 0) adapter22.updateItemName(pos, slot.goodsName)
}
slots18.forEach { slot ->
val pos = ScaleDeviceConfig.SCALE_ORDER_18.indexOf(slot.address)
if (pos >= 0) adapter18.updateItemName(pos, slot.goodsName)
}
// 加载油盆格子名称(address=1
val oilSlot = slotsOil.firstOrNull { it.address == 1 }
binding.tvOilSlot.text =
if (oilSlot != null && oilSlot.goodsName.isNotEmpty()) oilSlot.goodsName else "-"
}
}
@@ -7,7 +7,8 @@ import android.view.inputmethod.InputMethodManager
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
imm.hideSoftInputFromWindow(view.windowToken, 0)
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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFF3232"/>
<!-- <solid android:color="#FFFF3232"/>-->
<solid android:color="@color/dish_green"/>
<corners android:radius="12dp"/>
</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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFF3232"/>
<solid android:color="@color/dish_green"/>
<size android:width="10dp" android:height="40dp"/>
<corners android:radius="5dp"/>
</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_marginEnd="40dp"
android:gravity="center"
android:textColor="@color/black"
android:textColor="@color/black333"
android:textSize="36sp"
android:textStyle="bold" />
+25 -7
View File
@@ -20,7 +20,7 @@
android:layout_marginStart="32dp"
android:layout_marginTop="30dp"
android:drawablePadding="17dp"
android:text="餐品采集"
android:text="食材采集"
android:textColor="@color/black"
android:textSize="36sp"
android:textStyle="bold"
@@ -35,7 +35,7 @@
android:layout_marginEnd="32dp"
android:gravity="center_vertical"
android:includeFontPadding="false"
android:text="已采集餐品"
android:text="已采集食材"
android:textColor="#ff5e7585"
android:textSize="30sp"
android:textStyle="bold"
@@ -98,15 +98,33 @@
android:foreground="@drawable/ripple_effect"/>
<!-- android:background="?android:attr/selectableItemBackgroundBorderless"-->
<ImageView
<!-- 拍照按钮:绿色圆角背景 + 相机图标 + 文字 -->
<LinearLayout
android:id="@+id/btnTakePhoto"
android:layout_width="212dp"
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_constraintEnd_toEndOf="@id/btnCollectedFood"
tools:ignore="ContentDescription"
android:foreground="@drawable/ripple_effect"/>
app:layout_constraintEnd_toEndOf="@id/btnCollectedFood">
<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
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_gravity="end|top"
android:padding="6dp"
android:src="@drawable/ic_delete_red" />
android:src="@drawable/ic_delete_green" />
<ImageView
android:id="@+id/ivFinish"