添加了界面及逻辑

This commit is contained in:
zxj
2025-07-22 15:48:12 +08:00
parent 9d14b5499b
commit 5ad82eb224
40 changed files with 2157 additions and 182 deletions
@@ -1,19 +1,23 @@
package com.sw.platecabinet.fragment
import android.app.Dialog
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.sw.platecabinet.R
import com.sw.platecabinet.view.CustomDialog
abstract class BaseFragment<VB : ViewBinding>(
private val bindingInflater: (inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) -> VB
) : Fragment() {
private var _binding: VB? = null
protected val binding get() = _binding!!
private var mDialogWaiting: CustomDialog? = null
override fun onCreateView(
inflater: LayoutInflater,
@@ -22,16 +26,37 @@ abstract class BaseFragment<VB : ViewBinding>(
): View? {
_binding = bindingInflater(inflater, container, false)
initialize()
registerDataChange()
return binding.root
}
/**
* 注册数据变化监听
*/
open fun registerDataChange() {}
abstract fun initialize()
class ViewModelFactory<T : ViewModel>(private val creator: () -> T) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return creator() as T
}
/**
* 显示等待提示框
*/
fun showWaitingDialog(tip: String?): Dialog? {
hideWaitingDialog()
val view = View.inflate(requireContext(), R.layout.dialog_waiting, null)
if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).text = tip
mDialogWaiting = CustomDialog(requireContext(), view, R.style.MyDialog)
mDialogWaiting!!.show()
mDialogWaiting!!.setCancelable(true)
return mDialogWaiting
}
/**
* 隐藏等待提示框
*/
fun hideWaitingDialog() {
mDialogWaiting?.dismiss()
mDialogWaiting = null
}
override fun onDestroyView() {
@@ -1,23 +1,42 @@
package com.sw.platecabinet.fragment
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.Editable
import android.text.TextWatcher
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.R
import com.sw.platecabinet.adapter.GenericChildAdapter
import com.sw.platecabinet.adapter.GenericItemAdapter
import com.sw.platecabinet.adapter.GridSpacingItemDecoration
import com.sw.platecabinet.adapter.dpToPx
import com.sw.platecabinet.databinding.FragmentBindPlateBinding
import com.sw.platecabinet.databinding.ItemSearchUserInfoBinding
import com.sw.platecabinet.ext.maskName
import com.sw.platecabinet.ext.maskPhone
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.viewmodel.SettingViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
/**
* 餐盘柜绑定
*/
class BindPlateFragment : BaseFragment<FragmentBindPlateBinding>(
FragmentBindPlateBinding::inflate
) {
private lateinit var adapter: GenericChildAdapter<EquipmentUserInfo, ItemSearchUserInfoBinding>
private lateinit var adapter: GenericItemAdapter<SearchResult.Member, ItemSearchUserInfoBinding>
private val viewModel by viewModels<SettingViewModel>()
internal val ARG_PARAM1 = "param1"
private var info: EquipmentUserInfo? = null
private var checkedItem: SearchResult.Member? = null
private var lastText = ""
companion object {
@JvmStatic
@@ -32,25 +51,147 @@ class BindPlateFragment : BaseFragment<FragmentBindPlateBinding>(
override fun initialize() {
arguments?.let {
info = it.getParcelable(ARG_PARAM1)
info?.let { binding.tvNum.text = "${it.equipmentName}-${it.equipmentBoxCode}" }
info?.let {
binding.tvNum.text = "${it.equipmentName}-${it.equipmentBoxCode}"
}
}
initView()
initListener()
}
private fun initView() {
adapter = createAdapter()
registerDateChange()
binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
// 添加间距装饰(12dp)
binding.recyclerview.addItemDecoration(
GridSpacingItemDecoration(
spanCount = 2,
spacing = requireContext().dpToPx(6),
includeEdge = false // 包含边缘间距
)
)
binding.recyclerview.adapter = adapter
binding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
val totalItems = layoutManager.itemCount
if (!viewModel.isLoading && viewModel.canLoadMore
&& lastVisibleItem >= totalItems - 3
) {
viewModel.getSearchMemberList(
param = lastText,
pageNum = viewModel.currentPage
)
}
}
})
binding.etUserInfo.addTextChangedListener(object : TextWatcher {
private val handler = Handler(Looper.getMainLooper())
private val debounceDelay = 500L // 延迟 500 毫秒
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) {
}
override fun afterTextChanged(s: Editable?) {
val currentText = s.toString()
if (currentText == lastText) return // 内容未变化时不处理
lastText = currentText
handler.removeCallbacksAndMessages(null) // 取消之前的延迟任务
handler.postDelayed({
// if (currentText.isNotEmpty()) {
viewModel.getSearchMemberList(param = currentText, pageNum = 1)
// }
}, debounceDelay)
}
})
}
private fun initListener() {
binding.llBind.setOnClickListener {
if (info == null || info!!.equipmentCode?.isEmpty() == true || info!!.equipmentBoxCode?.isEmpty() == true) {
ToastUtils.showToast("传入的设备信息异常")
return@setOnClickListener
}
val plateNumber = binding.etCode.text.toString()
if (plateNumber.isEmpty()) {
ToastUtils.showToast("请输入餐盘号")
return@setOnClickListener
}
if (checkedItem == null) {
ToastUtils.showToast("请选择要绑定的会员")
return@setOnClickListener
}
if (checkedItem!!.id == null) {
ToastUtils.showToast("选中的会员信息异常")
return@setOnClickListener
}
viewModel.bindPlate(
equipmentCode = info!!.equipmentCode!!,
equipmentBoxCode = info!!.equipmentBoxCode!!,
memberId = checkedItem!!.id!!,
plateNumber = plateNumber
)
}
binding.tvBack.setOnClickListener { activity?.finish() }
}
private fun registerDateChange() {
/**
* 监听数据变化
*/
override fun registerDataChange() {
lifecycleScope.launch {
viewModel.searchMemberList.collect {
adapter.updateData(it)
}
}
lifecycleScope.launch {
viewModel.bindStateChange.collect {
if (it.first == null) return@collect // 解绑状态过滤
val errorInfo = it.second
if (errorInfo.isSuccess()) {
ToastUtils.showToast("绑定成功")
activity?.finish()
} else {
ToastUtils.showToast(errorInfo.msg)
}
}
}
lifecycleScope.launch {
viewModel.showLoading.collect {
if (it) {
showWaitingDialog("")
} else {
hideWaitingDialog()
}
}
}
}
private fun createAdapter(): GenericChildAdapter<EquipmentUserInfo, ItemSearchUserInfoBinding> {
return GenericChildAdapter(
private fun createAdapter(): GenericItemAdapter<SearchResult.Member, ItemSearchUserInfoBinding> {
return GenericItemAdapter(
items = emptyList(),
bindingInflater = ItemSearchUserInfoBinding::inflate,
bindCallback = { item, position ->
this.tvName.text = item.name
this.tvPhone.text = item.phone
if (item.isBound()) {
this.tvName.text = item.name.maskName()
this.tvPhone.text = item.phone.maskPhone()
if (item.id != checkedItem?.id) {
this.llRoot.setBackgroundResource(R.drawable.grid_item_bind)
} else {
this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
@@ -58,6 +199,8 @@ class BindPlateFragment : BaseFragment<FragmentBindPlateBinding>(
this.llRoot.setOnClickListener {
Timber.d("itemClick ${item.name}, position = $position")
checkedItem = item
adapter.notifyDataSetChanged()
}
}
)
@@ -2,6 +2,7 @@ package com.sw.platecabinet.fragment
import android.os.CountDownTimer
import com.sw.platecabinet.databinding.FragmentPlateCabinetFullBinding
import timber.log.Timber
/**
* 餐盘柜已满
@@ -11,6 +12,7 @@ class PlateCabinetFullFragment :
private var totalTimeInMillis: Long = 5 * 1000
override fun initialize() {
Timber.d("initialize")
initCountTime()
}
@@ -5,14 +5,18 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.comn.SerialApi
import com.sw.plate.utils.comn.SerialPortManager
import com.sw.platecabinet.R
import com.sw.platecabinet.activity.MainActivity
import com.sw.platecabinet.activity.PageType
import com.sw.platecabinet.adapter.GenericItemAdapter
import com.sw.platecabinet.adapter.GenericPageAdapter
import com.sw.platecabinet.databinding.FragmentSettingListBinding
import com.sw.platecabinet.databinding.ItemBindViewBinding
import com.sw.platecabinet.ext.formatNumber
import com.sw.platecabinet.model.request.EquipmentParam
import com.sw.platecabinet.ext.maskName
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.viewmodel.SettingViewModel
import kotlinx.coroutines.launch
@@ -25,31 +29,41 @@ import kotlin.math.ceil
class SettingListFragment :
BaseFragment<FragmentSettingListBinding>(FragmentSettingListBinding::inflate) {
private val viewModel: SettingViewModel by viewModels<SettingViewModel>()
private lateinit var pageAdapter: GenericPageAdapter<EquipmentUserInfo, ItemBindViewBinding>
private lateinit var itemAdapter: GenericItemAdapter<EquipmentUserInfo, ItemBindViewBinding>
private val itemsPerPage = 22 // 每行2个,每列11个,共22个
private fun registerDateChange() {
override fun registerDataChange() {
lifecycleScope.launch {
// 使用 repeatOnLifecycle 确保只在特定生命周期状态收集
// repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.equipmentList.collect { it ->
Timber.d("registerDateChange updateAdapter it = $it")
updateAdapter(it)
}
// }
}
lifecycleScope.launch {
viewModel.showLoading.collect {
if (it) {
showWaitingDialog("")
} else {
hideWaitingDialog()
}
}
}
}
override fun initialize() {
binding.mainRecyclerView.apply {
layoutManager = LinearLayoutManager(context)
pageAdapter = createAdapter()
adapter = pageAdapter
}
registerDateChange()
viewModel.getEquipmentList(EquipmentParam(equipmentCode = "202501171634"))
}
override fun onResume() {
super.onResume()
viewModel.getEquipmentList()
}
private fun createAdapter(): GenericPageAdapter<EquipmentUserInfo, ItemBindViewBinding> {
@@ -76,7 +90,7 @@ class SettingListFragment :
this.llBindInfo.visibility = View.VISIBLE
this.tvUnbind.visibility = View.GONE
this.tvName.text = item.name
this.tvName.text = item.name.maskName()
this.tvLastTime.text = DateTimeUtils.getTimeAgo(date)
} else {
this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
@@ -103,15 +117,29 @@ class SettingListFragment :
}
}
this.llOpen.setOnClickListener {
Timber.d("itemClick llOpen ${item.equipmentCode}")
MainActivity.start(requireContext(), pageType = PageType.PLATE_OPEN)
Timber.d("itemClick llOpen equipmentBoxCode = ${item.equipmentBoxCode}")
val equipmentBoxCode: Int = item.equipmentBoxCode?.toInt() ?: -1
if (equipmentBoxCode < 0) {
ToastUtils.showToast("餐盘柜编号错误")
return@setOnClickListener
}
SerialApi.openPlate(equipmentBoxCode, object : SerialPortManager.SendCallback {
override fun onSuccess() {
MainActivity.start(requireContext(), pageType = PageType.PLATE_OPEN)
}
override fun onFail(e: Exception?) {
ToastUtils.showToast("柜门打开失败")
}
})
}
}
)
}
fun updateAdapter(items: List<EquipmentUserInfo>) {
val pages = if (items.size > itemsPerPage) {
// if (GlobalData.arrayCross > 2) {
var pages = if (items.size > itemsPerPage) {
items.chunked(itemsPerPage) {
convertToColumnFirst(it, 11)
}
@@ -123,6 +151,9 @@ class SettingListFragment :
)
}
pageAdapter.updatePages(pages)
// }else{
//
// }
}
/**
@@ -1,9 +1,16 @@
package com.sw.platecabinet.fragment
import android.os.Bundle
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.databinding.FragmentUnbindPlateBinding
import com.sw.platecabinet.ext.maskName
import com.sw.platecabinet.ext.maskPhone
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.viewmodel.SettingViewModel
import kotlinx.coroutines.launch
/**
* 餐盘柜绑定
@@ -12,8 +19,8 @@ class UnBindPlateFragment private constructor() : BaseFragment<FragmentUnbindPla
FragmentUnbindPlateBinding::inflate
) {
internal val ARG_PARAM1 = "param1"
private var param1: EquipmentUserInfo? = null
private var info: EquipmentUserInfo? = null
private val viewModel by viewModels<SettingViewModel>()
companion object {
@JvmStatic
@@ -31,15 +38,46 @@ class UnBindPlateFragment private constructor() : BaseFragment<FragmentUnbindPla
info?.let {
binding.tvNum.text = "${it.equipmentName}-${it.equipmentBoxCode}"
binding.tvPlateNumberValue.text = it.plateNumber
binding.tvNameValue.text = it.name
binding.tvPhoneValue.text = it.phone
binding.tvNameValue.text = it.name.maskName()
binding.tvPhoneValue.text = it.phone.maskPhone()
val timeInfo = DateTimeUtils.parseDateTime(it.updateTime)
val timeAgo = DateTimeUtils.getTimeAgo(timeInfo)
binding.tvUpdateTimeValue.text = timeAgo
}
}
binding.llUnbind.setOnClickListener {
if (info == null) return@setOnClickListener
viewModel.unbindPlate(
equipmentBoxCode = info!!.equipmentBoxCode!!,
equipmentCode = info!!.equipmentCode!!
)
}
binding.tvBack.setOnClickListener {
activity?.finish()
}
}
override fun registerDataChange() {
lifecycleScope.launch {
viewModel.bindStateChange.collect {
if (it.first == null) return@collect // 绑定状态过滤
val errorInfo = it.second
if (errorInfo.isSuccess()) {
ToastUtils.showToast("解绑成功")
activity?.finish()
} else {
ToastUtils.showToast(errorInfo.msg)
}
}
}
lifecycleScope.launch {
viewModel.showLoading.collect {
if (it) {
showWaitingDialog("")
} else {
hideWaitingDialog()
}
}
}
}
}