refactor(setting): 重构设置页面界面和功能结构

- 将底部导航菜单替换为三个独立的功能卡片布局
- 添加餐品模式、调料区设置和食材采样功能入口
- 移除原有的Fragment容器和标签页切换逻辑
- 更新CollectFragment以使用新的SingleFragmentActivity架构
- 修改相机权限检查逻辑并添加权限拒绝提示对话框
- 简化DeviceConfigFragment和SeasoningConfigFragment的依赖导入
- 在设置页面添加当前模式显示和跳转功能
- 重构调料配置页面的日志记录和高亮逻辑
This commit is contained in:
2026-04-22 14:55:09 +08:00
parent 04e4efe1fc
commit 9ba6443f4f
7 changed files with 561 additions and 344 deletions
@@ -2,33 +2,19 @@ package com.shuwei.dish.match.ui
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatButton
import android.provider.Settings
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import androidx.fragment.app.Fragment
import androidx.camera.view.PreviewView
import androidx.core.view.updateLayoutParams
import com.shuwei.dish.match.R
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.DeviceRole
import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.databinding.ActivitySettingBinding
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
import com.shuwei.dish.match.ui.fragment.CollectFragment
import com.shuwei.dish.match.ui.fragment.DeviceConfigFragment
import com.shuwei.dish.match.ui.fragment.SeasoningConfigFragment
import com.shuwei.dish.match.utils.CameraUtils
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.clickWithDebounce
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
/**
@@ -42,10 +28,10 @@ class SettingActivity : BaseActivity() {
}
private lateinit var binding: ActivitySettingBinding
private val cameraUtils: CameraUtils by lazy {
CameraUtils(this)
}
private lateinit var previewView: PreviewView
// private val cameraUtils: CameraUtils by lazy {
// CameraUtils(this)
// }
// private lateinit var previewView: PreviewView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -55,104 +41,51 @@ class SettingActivity : BaseActivity() {
setTitleBar(titleBarAction = {
it.visible()
}, titleAction = {
it.text = "设置"
it.text = "备设"
}, rightIconActon = {
it.gone()
})
// 默认显示设备配置Fragment
if (savedInstanceState == null) {
showDeviceConfigFragment()
}
initView()
initUI()
// // 默认显示设备配置Fragment
// if (savedInstanceState == null) {
// showDeviceConfigFragment()
// }
//
// initUI()
}
/**
* 初始化UI和事件监听
*/
private fun initUI() {
// 设备配置按钮点击事件
binding.btnDeviceConfig.setOnClickListener {
showDeviceConfigFragment()
loadTabStyle(button = binding.btnDeviceConfig, isSelected = true)
loadTabStyle(button = binding.btnSeasoningConfig, isSelected = false)
loadTabStyle(button = binding.btnFoodCollect, isSelected = false)
private fun initView() {
binding.tvCurrentMode.text = when (SpTool.cookMode) {
0 -> "当前:制作模式"
1 -> "当前:采样模式"
else -> "当前:未知模式"
}
// 调料配置按钮点击事件
binding.btnSeasoningConfig.setOnClickListener {
showSeasoningConfigFragment()
loadTabStyle(button = binding.btnDeviceConfig, isSelected = false)
loadTabStyle(button = binding.btnSeasoningConfig, isSelected = true)
loadTabStyle(button = binding.btnFoodCollect, isSelected = false)
binding.llCookMode.clickWithDebounce {
SingleFragmentActivity.start(this, SingleFragmentActivity.PageType.COOK_MODE)
}
// 菜品采集按钮点击事件
binding.btnFoodCollect.setOnClickListener {
checkCameraPermissionAndShowFragment()
loadTabStyle(button = binding.btnDeviceConfig, isSelected = false)
loadTabStyle(button = binding.btnSeasoningConfig, isSelected = false)
loadTabStyle(button = binding.btnFoodCollect, isSelected = true)
binding.llSeasoningConfig.clickWithDebounce {
SingleFragmentActivity.start(this, SingleFragmentActivity.PageType.SEASONING_CONFIG)
}
// 主设备显示「秤数据监控」入口
if (GlobalData.deviceRole == DeviceRole.MASTER) {
binding.btnScaleMonitor.visible()
binding.btnScaleMonitor.setOnClickListener {
startActivity<MasterScaleActivity>()
}
binding.llFoodCollect.clickWithDebounce {
checkCameraPermission()
}
// 初始化按钮颜色(默认设备配置为选中状态)
loadTabStyle(button = binding.btnDeviceConfig, isSelected = true)
loadTabStyle(button = binding.btnSeasoningConfig, isSelected = false)
loadTabStyle(button = binding.btnFoodCollect, isSelected = false)
}
// /** 刷新角色按钮文字 */
// private fun updateRoleButtonText() {
// val roleLabel = if (GlobalData.deviceRole == DeviceRole.MASTER) "主设备" else "子设备"
// binding.btnDeviceRole.text = "设备角色:$roleLabel"
// }
/**
* 加载顶部Tab按钮样式
*
* @param button 按钮
* @param isSelected 是否选中
*/
private fun loadTabStyle(button: AppCompatButton, isSelected: Boolean) {
button.apply {
if (isSelected){
// 选中
setTextColor(Color.WHITE)
textSize = 28f
setTypeface(null, android.graphics.Typeface.BOLD)
background = ContextCompat.getDrawable(this@SettingActivity, R.drawable.bg_btn_underline)
} else {
// 未选中
setTextColor("#5E7585".toColorInt())
textSize = 26f
setTypeface(null, android.graphics.Typeface.NORMAL)
background = null
}
}
private fun openCollectPage() {
SingleFragmentActivity.start(this, SingleFragmentActivity.PageType.FOOD_COLLECT)
}
/**
* 检查相机权限并显示 CollectFragment
*/
@SuppressLint("ObsoleteSdkInt")
private fun checkCameraPermissionAndShowFragment() {
val collectFragment = fragmentList[2]
private fun checkCameraPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Android 6.0 以下,直接显示 Fragment
showFragment(collectFragment)
// Android 6.0 以下,直接打开
openCollectPage()
return
}
if (ContextCompat.checkSelfPermission(
@@ -160,109 +93,228 @@ class SettingActivity : BaseActivity() {
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
// 权限已授予,直接显示 Fragment
showFragment(collectFragment)
// 权限已授予,直接打开
openCollectPage()
} else {
// 权限未授予,申请权限
requestPermission(Manifest.permission.CAMERA) { isGranted ->
if (isGranted) {
// 权限已授予,显示 CollectFragment
showFragment(collectFragment)
// 权限已授予,直接打开
openCollectPage()
} else {
// 权限被拒绝
toast("暂无相机权限,无法使用菜品采集功能")
showNotGrantedCameraPermissionDialog()
}
}
}
}
/**
* 初始化相机
* @param container 相机预览容器
*/
fun initCamera(container: ViewGroup) {
cameraUtils.initCamera()
val previewBinding =
LayoutCameraPreviewBinding.inflate(layoutInflater, container)
previewView = previewBinding.previewView.also {
it.updateLayoutParams {
width = 456.dp
height = 342.dp
}
}
cameraUtils.setPreviewController(previewView)
}
/**
* Activity 恢复时绑定相机
*/
override fun onResume() {
super.onResume()
cameraUtils.bind()
}
/**
* Activity 暂停时解绑相机
*/
override fun onPause() {
super.onPause()
cameraUtils.unbind()
}
/**
* 提供给 Fragment 调用的拍照方法
*/
fun takePhoto(succCallback: (Uri) -> Unit, failCallback: (msg: String) -> Unit = {}) {
cameraUtils.takePhoto(succCallback = succCallback, failCallback = failCallback)
}
/**
* 显示设备切换Fragment
*/
private fun showDeviceConfigFragment() {
showFragment(fragmentList[0])
}
/**
* 显示设调料配置Fragment
*/
private fun showSeasoningConfigFragment() {
showFragment(fragmentList[1])
}
private val fragmentList = mutableListOf<Fragment>().apply {
add(DeviceConfigFragment())
add(SeasoningConfigFragment())
add(CollectFragment())
}
/**
* 显示指定的 Fragment
* 使用 add/show/hide 方式管理 Fragment,避免重复创建
* 已添加的 Fragment 直接显示,未添加的 Fragment 先添加再显示
*/
private fun showFragment(fragment: Fragment) {
runCatching {
supportFragmentManager.beginTransaction().apply {
// 如果 Fragment 还未添加到容器中,则添加
if (!fragment.isAdded) {
add(binding.fragmentContainer.id, fragment)
} else {
// 已添加过,直接显示
show(fragment)
private fun showNotGrantedCameraPermissionDialog() {
CommonDialog(this)
.setTitle("温馨提示")
.setContent("暂无相机权限,无法使用菜品采集功能,请确认是否同意开始权限?")
.setNegativeButton("不同意")
.setPositiveButton("同意") {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
// 隐藏其他 Fragment
fragmentList.forEach { otherFragment ->
if (otherFragment != fragment && otherFragment.isAdded) {
hide(otherFragment)
}
startActivity(intent) {
checkCameraPermission()
}
commit()
}
}.onFailure {
it.printStackTrace()
}
.show()
}
//
// /**
// * 初始化UI和事件监听
// */
// private fun initUI() {
// // 设备配置按钮点击事件
// binding.btnDeviceConfig.setOnClickListener {
// showDeviceConfigFragment()
//
// loadTabStyle(button = binding.btnDeviceConfig, isSelected = true)
// loadTabStyle(button = binding.btnSeasoningConfig, isSelected = false)
// loadTabStyle(button = binding.btnFoodCollect, isSelected = false)
// }
//
// // 调料配置按钮点击事件
// binding.btnSeasoningConfig.setOnClickListener {
// showSeasoningConfigFragment()
//
// loadTabStyle(button = binding.btnDeviceConfig, isSelected = false)
// loadTabStyle(button = binding.btnSeasoningConfig, isSelected = true)
// loadTabStyle(button = binding.btnFoodCollect, isSelected = false)
// }
//
// // 菜品采集按钮点击事件
// binding.btnFoodCollect.setOnClickListener {
// checkCameraPermissionAndShowFragment()
//
// loadTabStyle(button = binding.btnDeviceConfig, isSelected = false)
// loadTabStyle(button = binding.btnSeasoningConfig, isSelected = false)
// loadTabStyle(button = binding.btnFoodCollect, isSelected = true)
// }
//
// // 主设备显示「秤数据监控」入口
// if (GlobalData.deviceRole == DeviceRole.MASTER) {
// binding.btnScaleMonitor.visible()
// binding.btnScaleMonitor.setOnClickListener {
// startActivity<MasterScaleActivity>()
// }
// }
//
// // 初始化按钮颜色(默认设备配置为选中状态)
//
// loadTabStyle(button = binding.btnDeviceConfig, isSelected = true)
// loadTabStyle(button = binding.btnSeasoningConfig, isSelected = false)
// loadTabStyle(button = binding.btnFoodCollect, isSelected = false)
// }
//
// /**
// * 加载顶部Tab按钮样式
// *
// * @param button 按钮
// * @param isSelected 是否选中
// */
// private fun loadTabStyle(button: AppCompatButton, isSelected: Boolean) {
// button.apply {
// if (isSelected){
// // 选中
// setTextColor(Color.WHITE)
// textSize = 28f
// setTypeface(null, android.graphics.Typeface.BOLD)
// background = ContextCompat.getDrawable(this@SettingActivity, R.drawable.bg_btn_underline)
// } else {
// // 未选中
// setTextColor("#5E7585".toColorInt())
// textSize = 26f
// setTypeface(null, android.graphics.Typeface.NORMAL)
// background = null
// }
// }
// }
//
// /**
// * 检查相机权限并显示 CollectFragment
// */
// @SuppressLint("ObsoleteSdkInt")
// private fun checkCameraPermissionAndShowFragment() {
// val collectFragment = fragmentList[2]
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// // Android 6.0 以下,直接显示 Fragment
// showFragment(collectFragment)
// return
// }
// if (ContextCompat.checkSelfPermission(
// this,
// Manifest.permission.CAMERA
// ) == PackageManager.PERMISSION_GRANTED
// ) {
// // 权限已授予,直接显示 Fragment
// showFragment(collectFragment)
// } else {
// // 权限未授予,申请权限
// requestPermission(Manifest.permission.CAMERA) { isGranted ->
// if (isGranted) {
// // 权限已授予,显示 CollectFragment
// showFragment(collectFragment)
// } else {
// // 权限被拒绝
// toast("暂无相机权限,无法使用菜品采集功能")
// }
// }
// }
// }
//
// /**
// * 初始化相机
// * @param container 相机预览容器
// */
// fun initCamera(container: ViewGroup) {
// cameraUtils.initCamera()
// val previewBinding =
// LayoutCameraPreviewBinding.inflate(layoutInflater, container)
// previewView = previewBinding.previewView.also {
// it.updateLayoutParams {
// width = 456.dp
// height = 342.dp
// }
// }
// cameraUtils.setPreviewController(previewView)
// }
//
// /**
// * Activity 恢复时绑定相机
// */
// override fun onResume() {
// super.onResume()
// cameraUtils.bind()
// }
//
// /**
// * Activity 暂停时解绑相机
// */
// override fun onPause() {
// super.onPause()
// cameraUtils.unbind()
// }
//
// /**
// * 提供给 Fragment 调用的拍照方法
// */
// fun takePhoto(succCallback: (Uri) -> Unit, failCallback: (msg: String) -> Unit = {}) {
// cameraUtils.takePhoto(succCallback = succCallback, failCallback = failCallback)
// }
//
// /**
// * 显示设备切换Fragment
// */
// private fun showDeviceConfigFragment() {
// showFragment(fragmentList[0])
// }
//
// /**
// * 显示设调料配置Fragment
// */
// private fun showSeasoningConfigFragment() {
// showFragment(fragmentList[1])
// }
//
// private val fragmentList = mutableListOf<Fragment>().apply {
// add(DeviceConfigFragment())
// add(SeasoningConfigFragment())
// add(CollectFragment())
// }
//
// /**
// * 显示指定的 Fragment
// * 使用 add/show/hide 方式管理 Fragment,避免重复创建
// * 已添加的 Fragment 直接显示,未添加的 Fragment 先添加再显示
// */
// private fun showFragment(fragment: Fragment) {
// runCatching {
// supportFragmentManager.beginTransaction().apply {
// // 如果 Fragment 还未添加到容器中,则添加
// if (!fragment.isAdded) {
// add(binding.fragmentContainer.id, fragment)
// } else {
// // 已添加过,直接显示
// show(fragment)
// }
//
// // 隐藏其他 Fragment
// fragmentList.forEach { otherFragment ->
// if (otherFragment != fragment && otherFragment.isAdded) {
// hide(otherFragment)
// }
// }
//
// commit()
// }
// }.onFailure {
// it.printStackTrace()
// }
// }
}
@@ -17,15 +17,22 @@ import com.shuwei.dish.match.utils.ext.visible
*/
class SingleFragmentActivity : BaseActivity() {
interface LifecycleCallback {
fun onActivityResume()
fun onActivityPause()
}
/**
* 支持的页面类型枚举
* 新增页面时在此追加枚举值,并在 [createFragment] 中补充对应分支
*/
enum class PageType {
/** 菜品采集页 */
COLLECT,
FOOD_COLLECT,
/** 设备配置页 */
DEVICE_CONFIG,
COOK_MODE,
/** 调料配置页 */
SEASONING_CONFIG,
}
@@ -46,6 +53,11 @@ class SingleFragmentActivity : BaseActivity() {
}
}
private var lifecycleCallback: LifecycleCallback? = null
fun setLifecycleCallback(callback: LifecycleCallback) {
lifecycleCallback = callback
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHeaderBackground()
@@ -80,8 +92,8 @@ class SingleFragmentActivity : BaseActivity() {
* 根据页面类型返回对应标题文本
*/
private fun getTitleForPage(pageType: PageType): String = when (pageType) {
PageType.COLLECT -> "菜品采集"
PageType.DEVICE_CONFIG -> "设备配置"
PageType.FOOD_COLLECT -> "菜品采集"
PageType.COOK_MODE -> "设备配置"
PageType.SEASONING_CONFIG -> "调料配置"
}
@@ -89,8 +101,19 @@ class SingleFragmentActivity : BaseActivity() {
* 根据页面类型创建对应 Fragment 实例
*/
private fun createFragment(pageType: PageType): Fragment = when (pageType) {
PageType.COLLECT -> CollectFragment()
PageType.DEVICE_CONFIG -> DeviceConfigFragment()
PageType.FOOD_COLLECT -> CollectFragment()
PageType.COOK_MODE -> DeviceConfigFragment()
PageType.SEASONING_CONFIG -> SeasoningConfigFragment()
}
override fun onResume() {
super.onResume()
lifecycleCallback?.onActivityResume()
}
override fun onPause() {
super.onPause()
lifecycleCallback?.onActivityPause()
}
}
@@ -10,24 +10,29 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.camera.view.PreviewView
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.GridLayoutManager
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.FoodCollectionAdapter
import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentCollectBinding
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.SettingActivity
import com.shuwei.dish.match.ui.SingleFragmentActivity
import com.shuwei.dish.match.utils.BitmapSaver
import com.shuwei.dish.match.utils.CameraUtils
import com.shuwei.dish.match.utils.Debouncer
import com.shuwei.dish.match.utils.ImageUtil
import com.shuwei.dish.match.utils.LogSaveUtil
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.toast
@SuppressLint("NotifyDataSetChanged")
class CollectFragment : BaseFragment<FragmentCollectBinding>() {
class CollectFragment : BaseFragment<FragmentCollectBinding>(){
companion object {
private const val TAG = "CollectFragment"
@@ -58,7 +63,8 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
// }
// }
private val debouncer = Debouncer(2000)
private lateinit var settingActivity: SettingActivity
// private lateinit var settingActivity: SettingActivity
private lateinit var currentActivity: SingleFragmentActivity
private val collectionAdapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(foodCollectionList).apply {
@@ -168,7 +174,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
return
}
showWaitingDialog("采集中……")
settingActivity.takePhoto(succCallback = cameraCallback, failCallback = { errMsg ->
cameraUtils.takePhoto(succCallback = cameraCallback, failCallback = { errMsg ->
hideWaitingDialog()
// toast("拍照异常,请重新操作")
// if (cameraErrorCount >= 10) {
@@ -183,9 +189,18 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
}
override fun initialize() {
settingActivity = activity as SettingActivity
currentActivity = activity as SingleFragmentActivity
// 首次显示 CollectFragment 时初始化相机
settingActivity.initCamera(binding.flCameraPreview)
initCamera(binding.flCameraPreview)
currentActivity.setLifecycleCallback(object : SingleFragmentActivity.LifecycleCallback {
override fun onActivityResume() {
cameraUtils.bind()
}
override fun onActivityPause() {
cameraUtils.unbind()
}
})
binding.rvFoodList.let {
it.layoutManager =
@@ -361,7 +376,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
private fun searchByFoodName(name: String, block: (String) -> Unit) {}
private fun hideWaitingDialog() {
settingActivity.dismissLoading()
currentActivity.dismissLoading()
}
private fun showWaitingDialog(msg: String) {
@@ -369,6 +384,30 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
Loading.updateContent(msg)
return
}
settingActivity.showLoading(msg)
currentActivity.showLoading(msg)
}
private lateinit var previewView: PreviewView
private val cameraUtils: CameraUtils by lazy {
CameraUtils(requireActivity())
}
/**
* 初始化相机
* @param container 相机预览容器
*/
fun initCamera(container: ViewGroup) {
cameraUtils.initCamera()
val previewBinding =
LayoutCameraPreviewBinding.inflate(layoutInflater, container)
previewView = previewBinding.previewView.also {
it.updateLayoutParams {
width = 456.dp
height = 342.dp
}
}
cameraUtils.setPreviewController(previewView)
}
}
@@ -1,46 +1,22 @@
package com.shuwei.dish.match.ui.fragment
import android.graphics.Typeface
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.text.style.LineHeightSpan
import android.text.style.StyleSpan
import android.util.Log
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.graphics.toColorInt
import androidx.core.view.forEach
import androidx.lifecycle.ViewModelProvider
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentDeviceConfigBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.SeasoningSearchDialog
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.ResetReasoningRecord
import com.shuwei.dish.match.adapter.TextCellAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.JsonAssetsLoader
import com.shuwei.dish.match.utils.MultiClickDetector
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.dp
import com.shuwei.dish.match.utils.ext.clickWithDebounce
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 com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import com.shuwei.dish.match.ui.SamplingListActivity
import com.shuwei.dish.match.ui.SelectDishActivity
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.startActivity
/**
@@ -65,10 +41,10 @@ class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
// private lateinit var appViewModel: AppViewModel
private val weightArray = SparseArray<Double>()
private val addressArray = AddressUtil.getWeighAddressArray()
private var seasoningItems = mutableListOf<SeasoningEntity>()
private val detector = MultiClickDetector(targetCount = 10, intervalMs = 800)
// private val weightArray = SparseArray<Double>()
// private val addressArray = AddressUtil.getWeighAddressArray()
// private var seasoningItems = mutableListOf<SeasoningEntity>()
// private val detector = MultiClickDetector(targetCount = 10, intervalMs = 800)
override fun inflateBinding(
inflater: LayoutInflater,
@@ -77,7 +53,7 @@ class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
return FragmentDeviceConfigBinding.inflate(inflater, container, false)
}
override fun initialize(){
override fun initialize() {
initViewModel()
initUI()
// addWeighListener()
@@ -140,7 +116,8 @@ class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
loadCookSpan()
loadSamplingSpan(isClicked = true)
activity?.startActivity<SamplingListActivity> {
putExtra(SamplingListActivity.IS_CONFIG_PAGE, true) }
putExtra(SamplingListActivity.IS_CONFIG_PAGE, true)
}
activity?.finish()
}
}
@@ -19,12 +19,13 @@ import com.shuwei.dish.match.base.DeviceRole
import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.databinding.FragmentSeasoningConfigBinding
import com.shuwei.dish.match.dialog.SeasoningSelectDialog
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.SeasoningSlotEntity
import com.shuwei.dish.match.scale.ScaleDeviceConfig
import com.shuwei.dish.match.scale.ScaleEvent
import com.shuwei.dish.match.scale.ScaleServiceManager
import com.shuwei.dish.match.scale.ScaleEvent.SlotConfig
import com.shuwei.dish.match.ui.SettingActivity
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
@@ -36,8 +37,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
companion object {
private const val TAG = "SeasoningConfigFragment"
/** 重量增加阈值(克),超过此值才触发高亮 */
private const val WEIGHT_DELTA_THRESHOLD = 10.0
/** 高亮自动恢复时间(毫秒) */
private const val HIGHLIGHT_DURATION_MS = 15_000L
}
@@ -47,21 +50,28 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
container: ViewGroup?
) = FragmentSeasoningConfigBinding.inflate(inflater, container, false)
private lateinit var settingActivity: SettingActivity
private lateinit var currentActivity: SingleFragmentActivity
// private lateinit var appViewModel: AppViewModel
private val adapter22 by lazy { buildAdapter22() }
private val adapter18 by lazy { Seasoning18GridAdapter(showNameOnly = true, leftRightSpace = 20.dp) }
private val adapter18 by lazy {
Seasoning18GridAdapter(
showNameOnly = true,
leftRightSpace = 20.dp
)
}
/** 各秤的上一次重量快照,key 格式 "{deviceId}#{address}",用于计算 delta */
private val weightSnapshot = mutableMapOf<String, Double>()
/** 主线程 Handler,用于延迟清除高亮 */
private val mainHandler = Handler(Looper.getMainLooper())
/** 当前高亮清除任务,新高亮触发时取消旧任务 */
private var clearHighlightRunnable: Runnable? = null
override fun initialize() {
settingActivity = activity as SettingActivity
currentActivity = activity as SingleFragmentActivity
// initViewModel()
setupRecyclerViews()
bindAdapterClicks()
@@ -73,62 +83,67 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
/** 绑定油盆格子点击,弹出调料选择弹窗并保存到 DEVICE_ID_1 */
private fun bindOilSlotClick() {
binding.tvOilSlot.setOnClickListener {
SeasoningSelectDialog(
activity = settingActivity,
clickIndex = 0,
onItemSelected = { item ->
binding.tvOilSlot.text = item.goodsName ?: "-"
saveSlotAndBroadcast(
SeasoningSlotEntity(
deviceId = ScaleDeviceConfig.DEVICE_ID_1,
address = 1,
goodsId = item.goodsId,
goodsName = item.goodsName ?: ""
)
openSeasoningSelectDialog(0) { item ->
binding.tvOilSlot.text = item.goodsName ?: "-"
saveSlotAndBroadcast(
SeasoningSlotEntity(
deviceId = ScaleDeviceConfig.DEVICE_ID_1,
address = 1,
goodsId = item.goodsId,
goodsName = item.goodsName ?: ""
)
}
).show()
)
}
}
}
/** 绑定两个 Adapter 的格子点击,弹出调料选择弹窗 */
private fun bindAdapterClicks() {
adapter22.onItemClick = { scaleData, position ->
SeasoningSelectDialog(
activity = settingActivity,
clickIndex = position,
onItemSelected = { item ->
adapter22.updateItemName(position, item.goodsName ?: "")
saveSlotAndBroadcast(
SeasoningSlotEntity(
deviceId = ScaleDeviceConfig.DEVICE_ID_22,
address = scaleData.address,
goodsId = item.goodsId,
goodsName = item.goodsName ?: ""
)
openSeasoningSelectDialog(position) { item ->
adapter22.updateItemName(position, item.goodsName ?: "")
saveSlotAndBroadcast(
SeasoningSlotEntity(
deviceId = ScaleDeviceConfig.DEVICE_ID_22,
address = scaleData.address,
goodsId = item.goodsId,
goodsName = item.goodsName ?: ""
)
}
).show()
)
}
}
adapter18.onItemClick = { scaleData, position ->
SeasoningSelectDialog(
activity = settingActivity,
clickIndex = position,
onItemSelected = { item ->
adapter18.updateItemName(position, item.goodsName ?: "")
saveSlotAndBroadcast(
SeasoningSlotEntity(
deviceId = ScaleDeviceConfig.DEVICE_ID_18,
address = scaleData.address,
goodsId = item.goodsId,
goodsName = item.goodsName ?: ""
)
openSeasoningSelectDialog(position) { item ->
adapter18.updateItemName(position, item.goodsName ?: "")
saveSlotAndBroadcast(
SeasoningSlotEntity(
deviceId = ScaleDeviceConfig.DEVICE_ID_18,
address = scaleData.address,
goodsId = item.goodsId,
goodsName = item.goodsName ?: ""
)
}
).show()
)
}
}
}
/**
* 打开调料选择弹窗
*
* @param position 格子位置
* @param callback 调料选择回调
*/
private fun openSeasoningSelectDialog(
position: Int,
callback: (item: SeasoningEntity) -> Unit
) {
SeasoningSelectDialog(
activity = currentActivity,
clickIndex = position,
onItemSelected = callback
).show()
}
/**
* 将单个槽位写入 Room,再读取全量配置广播给子设备
* 仅主设备执行广播,子设备不调用此方法
@@ -153,6 +168,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
private val size by lazy { SizeTool.getFlexLayoutSize(20.dp) }
/** 构建上层(22格)Adapter,需要先计算格子尺寸 */
private fun buildAdapter22(): Seasoning22GridAdapter {
return Seasoning22GridAdapter(
@@ -205,7 +221,8 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
}
// 加载油盆格子名称(address=1
val oilSlot = slotsOil.firstOrNull { it.address == 1 }
binding.tvOilSlot.text = if (oilSlot != null && oilSlot.goodsName.isNotEmpty()) oilSlot.goodsName else "-"
binding.tvOilSlot.text =
if (oilSlot != null && oilSlot.goodsName.isNotEmpty()) oilSlot.goodsName else "-"
}
}
}
@@ -223,7 +240,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
* - STATE_STABLE:无条件更新基准(覆盖取走调料/归零等场景)
*/
private fun addWeightListener() {
Log.d(TAG, "addWeightListener: 注册监听, deviceId=${GlobalData.deviceId}, role=${GlobalData.deviceRole}")
Log.d(
TAG,
"addWeightListener: 注册监听, deviceId=${GlobalData.deviceId}, role=${GlobalData.deviceRole}"
)
if (GlobalData.deviceRole == DeviceRole.MASTER) {
// 主设备:通过 allScales StateFlow 观测所有秤(含远端子设备秤)
@@ -243,14 +263,21 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
return@forEach
}
val delta = data.weight - baseline
Log.d(TAG, "allScales: key=$key address=${data.address} state=${data.state} weight=${data.weight} baseline=$baseline delta=$delta")
Log.d(
TAG,
"allScales: key=$key address=${data.address} state=${data.state} weight=${data.weight} baseline=$baseline delta=$delta"
)
when {
// 仅在秤稳定后判断增量,避免取走物品时手部冲力造成误触发
data.state == WeightUtil.STATE_STABLE && delta > WEIGHT_DELTA_THRESHOLD -> {
weightSnapshot[snapshotKey] = data.weight
Log.d(TAG, "allScales: 触发高亮 key=$key address=${data.address} delta=$delta")
Log.d(
TAG,
"allScales: 触发高亮 key=$key address=${data.address} delta=$delta"
)
triggerHighlight(data.deviceId, data.address, delta)
}
data.state == WeightUtil.STATE_STABLE -> {
weightSnapshot[snapshotKey] = data.weight
}
@@ -263,7 +290,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
WeightUtil.addWeightListener(TAG) { address, state, weight ->
val snapshotKey = "${GlobalData.deviceId}#$address"
val baseline = weightSnapshot[snapshotKey]
Log.d(TAG, "onWeight: address=$address state=$state weight=$weight baseline=$baseline")
Log.d(
TAG,
"onWeight: address=$address state=$state weight=$weight baseline=$baseline"
)
if (baseline == null) {
weightSnapshot[snapshotKey] = weight
@@ -271,7 +301,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
}
val delta = weight - baseline
Log.d(TAG, "onWeight: address=$address state=$state weight=$weight baseline=$baseline delta=$delta")
Log.d(
TAG,
"onWeight: address=$address state=$state weight=$weight baseline=$baseline delta=$delta"
)
when {
// 仅在秤稳定后判断增量,避免取走物品时手部冲力造成误触发
@@ -280,6 +313,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
Log.d(TAG, "onWeight: 触发高亮 address=$address delta=$delta")
triggerHighlight(GlobalData.deviceId, address, delta)
}
state == WeightUtil.STATE_STABLE -> {
weightSnapshot[snapshotKey] = weight
}
@@ -302,7 +336,10 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
val pos18 = if (sourceDeviceId == ScaleDeviceConfig.DEVICE_ID_18)
ScaleDeviceConfig.SCALE_ORDER_18.indexOf(address) else -1
Log.d(TAG, "triggerHighlight: sourceDeviceId=$sourceDeviceId address=$address pos22=$pos22 pos18=$pos18")
Log.d(
TAG,
"triggerHighlight: sourceDeviceId=$sourceDeviceId address=$address pos22=$pos22 pos18=$pos18"
)
// 两个列表都不包含该地址,忽略
if (pos22 < 0 && pos18 < 0) {
@@ -318,6 +355,7 @@ class SeasoningConfigFragment : BaseFragment<FragmentSeasoningConfigBinding>() {
adapter22.highlightPosition(pos22)
adapter18.clearHighlight()
}
else -> {
adapter18.highlightPosition(pos18)
adapter22.clearHighlight()
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

+143 -55
View File
@@ -1,77 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="@color/bg_color"
android:orientation="vertical">
<!-- 底部导航菜单 -->
<LinearLayout
android:id="@+id/bottomMenu"
android:id="@+id/ll_cook_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="120dp"
android:orientation="horizontal"
android:background="@android:color/transparent"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:elevation="8dp">
android:gravity="center_vertical"
android:layout_marginHorizontal="32dp"
android:background="@drawable/shape_scale_cell">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnDeviceConfig"
<TextView
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="设备切换"
android:textSize="28sp"
android:text="餐品模式"
android:textSize="32sp"
android:textStyle="bold"
android:background="@android:color/transparent" />
android:layout_marginStart="32dp"
android:textColor="@color/black333" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSeasoningConfig"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="1"
android:text="调料配置"
<TextView
android:id="@+id/tvCurrentMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="当前:制作模式"
android:textSize="26sp"
android:background="@android:color/transparent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnFoodCollect"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="1"
android:text="采集食材"
android:textSize="26sp"
android:background="@android:color/transparent" />
<!-- <androidx.appcompat.widget.AppCompatButton-->
<!-- android:id="@+id/btnDeviceRole"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="70dp"-->
<!-- android:layout_weight="1"-->
<!-- android:text="设备角色:子设备"-->
<!-- android:textSize="22sp"-->
<!-- android:background="@android:color/transparent" />-->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnScaleMonitor"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="1"
android:text="秤数据监控"
android:textSize="26sp"
android:visibility="visible"
android:background="@android:color/transparent" />
android:layout_marginStart="32dp"
android:textColor="@color/black999" />
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="20dp"
android:adjustViewBounds="true"
android:src="@drawable/ic_arrow_right2"
tools:ignore="ContentDescription" />
</LinearLayout>
<!-- Fragment 容器 -->
<FrameLayout
android:id="@+id/fragmentContainer"
<LinearLayout
android:id="@+id/llSeasoningConfig"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_height="120dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="32dp"
android:background="@drawable/shape_scale_cell">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="调料区设置"
android:textSize="32sp"
android:textStyle="bold"
android:layout_marginStart="32dp"
android:textColor="@color/black333" />
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="20dp"
android:adjustViewBounds="true"
android:src="@drawable/ic_arrow_right2"
tools:ignore="ContentDescription" />
</LinearLayout>
<LinearLayout
android:id="@+id/llFoodCollect"
android:layout_width="match_parent"
android:layout_height="120dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="32dp"
android:background="@drawable/shape_scale_cell">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="食材采样"
android:textSize="32sp"
android:textStyle="bold"
android:layout_marginStart="32dp"
android:textColor="@color/black333" />
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="20dp"
android:adjustViewBounds="true"
android:src="@drawable/ic_arrow_right2"
tools:ignore="ContentDescription" />
</LinearLayout>
<!-- &lt;!&ndash; 底部导航菜单 &ndash;&gt;-->
<!-- <LinearLayout-->
<!-- android:id="@+id/bottomMenu"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:orientation="horizontal"-->
<!-- android:background="@android:color/transparent"-->
<!-- android:layout_marginTop="10dp"-->
<!-- android:layout_marginBottom="10dp"-->
<!-- android:layout_marginStart="15dp"-->
<!-- android:layout_marginEnd="15dp"-->
<!-- android:elevation="8dp">-->
<!-- <androidx.appcompat.widget.AppCompatButton-->
<!-- android:id="@+id/btnDeviceConfig"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="70dp"-->
<!-- android:layout_weight="1"-->
<!-- android:text="设备切换"-->
<!-- android:textSize="28sp"-->
<!-- android:textStyle="bold"-->
<!-- android:background="@android:color/transparent" />-->
<!-- <androidx.appcompat.widget.AppCompatButton-->
<!-- android:id="@+id/btnSeasoningConfig"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="70dp"-->
<!-- android:layout_weight="1"-->
<!-- android:text="调料配置"-->
<!-- android:textSize="26sp"-->
<!-- android:background="@android:color/transparent" />-->
<!-- <androidx.appcompat.widget.AppCompatButton-->
<!-- android:id="@+id/btnFoodCollect"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="70dp"-->
<!-- android:layout_weight="1"-->
<!-- android:text="采集食材"-->
<!-- android:textSize="26sp"-->
<!-- android:background="@android:color/transparent" />-->
<!-- <androidx.appcompat.widget.AppCompatButton-->
<!-- android:id="@+id/btnScaleMonitor"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="70dp"-->
<!-- android:layout_weight="1"-->
<!-- android:text="秤数据监控"-->
<!-- android:textSize="26sp"-->
<!-- android:visibility="visible"-->
<!-- android:background="@android:color/transparent" />-->
<!-- </LinearLayout>-->
<!-- &lt;!&ndash; Fragment 容器 &ndash;&gt;-->
<!-- <FrameLayout-->
<!-- android:id="@+id/fragmentContainer"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="0dp"-->
<!-- android:layout_weight="1" />-->
</LinearLayout>