524 lines
18 KiB
Kotlin
524 lines
18 KiB
Kotlin
package com.sw.platecabinet.activity
|
||
|
||
import android.content.Context
|
||
import android.os.Build
|
||
import android.os.Bundle
|
||
import android.text.TextUtils
|
||
import android.util.Log
|
||
import android.view.KeyEvent
|
||
import android.view.View
|
||
import android.view.WindowInsetsController
|
||
import android.view.WindowManager
|
||
import android.widget.TextView
|
||
import androidx.activity.enableEdgeToEdge
|
||
import androidx.activity.viewModels
|
||
import androidx.appcompat.app.AppCompatActivity
|
||
import androidx.core.view.WindowCompat
|
||
import androidx.viewbinding.ViewBinding
|
||
import com.google.gson.Gson
|
||
import com.sw.inbound.utils.DateTimeUtils
|
||
import com.sw.plate.App
|
||
import com.sw.plate.utils.Base64
|
||
import com.sw.plate.utils.ScanGunKeyEventHelper
|
||
import com.sw.plate.utils.arcface.facedb.FaceDatabase
|
||
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
|
||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||
import com.sw.platecabinet.MyApp
|
||
import com.sw.platecabinet.R
|
||
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
|
||
import com.sw.platecabinet.ext.clickWithCoroutines
|
||
import com.sw.platecabinet.model.response.EquipmentUserInfo
|
||
import com.sw.platecabinet.model.response.UserFaceModel
|
||
import com.sw.platecabinet.utils.IntervalExecutor
|
||
import com.sw.platecabinet.utils.PermissionHelper
|
||
import com.sw.platecabinet.utils.PlateUtils
|
||
import com.sw.platecabinet.utils.SpTool
|
||
import com.sw.platecabinet.view.CustomLoadingDialog
|
||
import com.sw.platecabinet.viewmodel.SettingViewModel
|
||
import com.sw.platecabinet.viewmodel.UserViewModel
|
||
import kotlinx.coroutines.CoroutineScope
|
||
import kotlinx.coroutines.Dispatchers
|
||
import kotlinx.coroutines.Job
|
||
import kotlinx.coroutines.launch
|
||
import timber.log.Timber
|
||
|
||
/**
|
||
* activity 基类
|
||
*/
|
||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||
protected lateinit var binding: VB
|
||
private var headerBinding: ItemTitleTimeBinding? = null
|
||
protected lateinit var context: Context
|
||
private var timeJob: Job? = null
|
||
private var mDialogWaiting: CustomLoadingDialog? = null
|
||
private val permissionHelpers = mutableMapOf<Int, PermissionHelper>()
|
||
protected var keyEventHelper: ScanGunKeyEventHelper? = null
|
||
|
||
// 管理员对应的viewmodel
|
||
val settingViewModel by viewModels<SettingViewModel>()
|
||
|
||
// 用户对应的viewModel
|
||
protected val userViewModel by viewModels<UserViewModel>()
|
||
|
||
private var startTime: Long = 0
|
||
|
||
override fun onCreate(savedInstanceState: Bundle?) {
|
||
startTime = System.currentTimeMillis()
|
||
super.onCreate(savedInstanceState)
|
||
enableEdgeToEdge()
|
||
context = this
|
||
disableSystemUICompletely()
|
||
// 确保内容延伸到导航栏区域
|
||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||
|
||
//保持亮屏
|
||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||
|
||
binding = inflateViewBinding()
|
||
headerBinding = inflateTitleBinding()
|
||
setContentView(binding.root)
|
||
updateTime()
|
||
registerDataChange()
|
||
initialize()
|
||
registerKeyEvent()
|
||
val durationTime = System.currentTimeMillis() - startTime
|
||
Timber.d("启动时间:$durationTime")
|
||
}
|
||
|
||
/**
|
||
* 监听扫描枪扫描事件
|
||
*/
|
||
protected fun registerKeyEvent() {
|
||
keyEventHelper =
|
||
ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener {
|
||
override fun onScanSuccess(barcode: String?) {
|
||
Timber.d("onScanSuccess barcode = $barcode")
|
||
if (barcode == null) return
|
||
handleScanKeyInfo(barcode)
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 处理扫描枪数据
|
||
*/
|
||
protected open fun handleScanKeyInfo(scanInfo: String) {
|
||
// settingViewModel.findByPlateNumber(plateNumber = scanInfo)
|
||
var plateNumber = scanInfo
|
||
if (scanInfo.length > 6) {
|
||
plateNumber = scanInfo.take(6)
|
||
}
|
||
Log.d("TAG", "handleScanKeyInfo: " + Gson().toJson(MyApp.plateList))
|
||
if (MyApp.plateList == null) {
|
||
initPlateList()
|
||
}
|
||
//柜子未关联该餐盘,直接找空位置放入盘子
|
||
val item = MyApp.plateList?.firstOrNull { it.plateNumber.isNullOrBlank() }
|
||
if (item == null) {
|
||
//柜子已满
|
||
MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL)
|
||
return
|
||
}
|
||
//找到位置,放入餐盘,清除已有id和时间
|
||
item.plateNumber = plateNumber
|
||
item.faceId = null
|
||
item.updateTime = null
|
||
PlateUtils.open(this, item.equipmentBoxCode, true)
|
||
SpTool.savePlateData(MyApp.plateList!!)
|
||
}
|
||
|
||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||
if (keyEventHelper != null) {
|
||
if (keyEventHelper!!.isScanGunEvent(event)) {
|
||
keyEventHelper!!.analysisKeyEvent(event)
|
||
return true
|
||
}
|
||
}
|
||
return super.dispatchKeyEvent(event)
|
||
}
|
||
|
||
fun updateTime() {
|
||
headerBinding?.let {
|
||
it.tvLeftDate.clickWithCoroutines(
|
||
onDoubleClick = {
|
||
onLeftDoubleClick()
|
||
}
|
||
)
|
||
it.tvRightTime.clickWithCoroutines(
|
||
onDoubleClick = {
|
||
onRightDoubleClick()
|
||
}
|
||
)
|
||
val scope = CoroutineScope(Dispatchers.Main)
|
||
timeJob = scope.launch {
|
||
DateTimeUtils.realTimeChineseDateFlow()
|
||
.collect { (date, time) ->
|
||
it.tvLeftDate.text = date
|
||
it.tvRightTime.text = time
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 左侧日期双击
|
||
*/
|
||
open fun onLeftDoubleClick() {
|
||
|
||
}
|
||
|
||
/**
|
||
* 右侧时间双击
|
||
*/
|
||
open fun onRightDoubleClick() {
|
||
MainActivity.start(context, pageType = PageType.SETTING_LIST)
|
||
}
|
||
|
||
override fun onRequestPermissionsResult(
|
||
requestCode: Int,
|
||
permissions: Array<out String>,
|
||
grantResults: IntArray
|
||
) {
|
||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||
|
||
val helper = permissionHelpers[requestCode]
|
||
val allGranted = PermissionHelper.handlePermissionResult(
|
||
this,
|
||
requestCode,
|
||
permissions,
|
||
grantResults,
|
||
permissionHelper = helper
|
||
)
|
||
|
||
if (allGranted) {
|
||
permissionHelpers.remove(requestCode)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册PermissionHelper以便处理结果
|
||
*/
|
||
fun registerPermissionHelper(helper: PermissionHelper) {
|
||
permissionHelpers[helper.requestCode] = helper
|
||
}
|
||
|
||
override fun onResume() {
|
||
super.onResume()
|
||
// 关键点2:防止某些场景下系统栏恢复
|
||
enforceImmersiveMode()
|
||
}
|
||
|
||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||
super.onWindowFocusChanged(hasFocus)
|
||
if (hasFocus) {
|
||
disableSystemUICompletely()
|
||
}
|
||
}
|
||
|
||
private fun disableSystemUICompletely() {
|
||
Timber.d("disableSystemUICompletely")
|
||
// 禁用系统手势(Android 10+)
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||
window.insetsController?.systemBarsBehavior =
|
||
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||
}
|
||
|
||
|
||
// 全屏+隐藏导航栏(所有版本通用)
|
||
window.decorView.systemUiVisibility = (
|
||
View.SYSTEM_UI_FLAG_FULLSCREEN
|
||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||
)
|
||
|
||
// 禁止窗口扩展至系统栏区域(彻底锁定)
|
||
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||
}
|
||
|
||
|
||
private fun enforceImmersiveMode() {
|
||
Timber.d("enforceImmersiveMode")
|
||
// 持续强制隐藏系统栏(防止手势触发)
|
||
window.decorView.postDelayed({
|
||
window.decorView.systemUiVisibility = (
|
||
View.SYSTEM_UI_FLAG_FULLSCREEN
|
||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||
)
|
||
}, 100) // 延迟100ms确保覆盖手势触发
|
||
}
|
||
|
||
/**
|
||
* 显示等待提示框
|
||
*/
|
||
fun showWaitingDialog(tip: String?) {
|
||
runOnUiThread {
|
||
hideWaitingDialog()
|
||
val view = View.inflate(this, R.layout.dialog_waiting, null)
|
||
if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).setText(
|
||
tip
|
||
)
|
||
mDialogWaiting = CustomLoadingDialog(this, view, R.style.MyDialog)
|
||
mDialogWaiting!!.show()
|
||
mDialogWaiting!!.setCancelable(true)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 隐藏等待提示框
|
||
*/
|
||
fun hideWaitingDialog() {
|
||
runOnUiThread {
|
||
mDialogWaiting?.dismiss()
|
||
mDialogWaiting = null
|
||
}
|
||
}
|
||
|
||
protected abstract fun inflateViewBinding(): VB
|
||
|
||
protected abstract fun inflateTitleBinding(): ItemTitleTimeBinding?
|
||
|
||
protected abstract fun initialize()
|
||
|
||
/**
|
||
* 注册数据监听
|
||
*/
|
||
protected open fun registerDataChange() {
|
||
Timber.d("registerDataChange")
|
||
// lifecycleScope.launch {
|
||
// viewModel.showLoading.collect {
|
||
// Timber.d("registerDataChange 用户 showLoading = $it")
|
||
// if (it) {
|
||
// showWaitingDialog("")
|
||
// } else {
|
||
// hideWaitingDialog()
|
||
// }
|
||
// }
|
||
// }
|
||
// lifecycleScope.launch {
|
||
// viewModel.currentUserInfo.collect {
|
||
// if (it == null) return@collect
|
||
//// it.showBalanceNotEnoughDialog = true
|
||
// handleLoginSuccess(it, false)
|
||
// }
|
||
// }
|
||
// lifecycleScope.launch {
|
||
// settingViewModel.showLoading.collect {
|
||
// Timber.d("registerDataChange 管理员 showLoading = $it")
|
||
// if (it) {
|
||
// showWaitingDialog("")
|
||
// } else {
|
||
// hideWaitingDialog()
|
||
// }
|
||
// }
|
||
// }
|
||
// lifecycleScope.launch {
|
||
// settingViewModel.searchUserInfo.collect {
|
||
// Timber.d("registerDataChange 管理员 currentUserInfo = $it")
|
||
// if (it == null) return@collect
|
||
// handleLoginSuccess(it, true)
|
||
// }
|
||
// }
|
||
// lifecycleScope.launch {
|
||
// settingViewModel.equipmentList.collect {
|
||
// loadEquipmentList(it)
|
||
// }
|
||
// }
|
||
}
|
||
|
||
// private fun loadEquipmentList(items: List<EquipmentUserInfo>) {
|
||
// if (items.isEmpty()) return
|
||
// val unbindList = items.filter { !it.isBound() }
|
||
// if (unbindList.isEmpty()) {
|
||
// MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL)
|
||
// } else {
|
||
// val firstInfo = unbindList[0]
|
||
// val currentUserInfo = settingViewModel.searchUserInfo.value
|
||
// firstInfo.plateNumber = currentUserInfo?.plateNumber ?: ""
|
||
// MainActivity.start(
|
||
// context = context,
|
||
// pageType = PageType.BIND_PLATE,
|
||
// equipmentUserInfo = firstInfo
|
||
// )
|
||
// }
|
||
// }
|
||
|
||
/**
|
||
* 处理登录成功操作
|
||
* @param isAdmin true 管理员 执行绑盘此操作 false 用户,提示错误
|
||
*/
|
||
open fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
|
||
// Timber.d("handleLoginSuccess isAdmin = $isAdmin")
|
||
// equipmentUserInfo.isIntercept = false
|
||
// val cardBalance = equipmentUserInfo.cardBalance ?: 0.toDouble()
|
||
// val balanceIsNotEnough = cardBalance <= 0.toDouble()
|
||
// if (balanceIsNotEnough && equipmentUserInfo.showBalanceNotEnoughDialog) {
|
||
// //提示余额不足弹窗
|
||
// equipmentUserInfo.isIntercept = true
|
||
// BalanceNotEnoughDialog(this).show()
|
||
// return
|
||
// }
|
||
// viewModel.resetUserInfo()
|
||
//// if (equipmentUserInfo.isOtherEquipment()) {
|
||
//// MainActivity.start(
|
||
//// context,
|
||
//// pageType = PageType.PLATE_TIP,
|
||
//// equipmentUserInfo = equipmentUserInfo,
|
||
//// isAdmin
|
||
//// )
|
||
//// return
|
||
//// }
|
||
// if (equipmentUserInfo.equipmentBoxCode?.isNotEmpty() == true) {
|
||
// SerialApi.openPlate(
|
||
// equipmentUserInfo.equipmentBoxCode!!.toInt(),
|
||
// object : SerialPortManager.SendCallback {
|
||
// override fun onSuccess() {
|
||
// MainActivity.start(
|
||
// context,
|
||
// pageType = PageType.PLATE_TIP,
|
||
// isAdmin = isAdmin
|
||
// )
|
||
// }
|
||
//
|
||
// override fun onFail(e: Exception?) {
|
||
// ToastUtils.showToast("柜门打开失败")
|
||
// }
|
||
// })
|
||
// } else {
|
||
// if (isAdmin) {
|
||
// settingViewModel.getEquipmentList {
|
||
// loadEquipmentList(it)
|
||
// }
|
||
// } else {
|
||
// Timber.d("无餐盘信息")
|
||
// MainActivity.start(
|
||
// context,
|
||
// pageType = PageType.PLATE_TIP,
|
||
// equipmentUserInfo = equipmentUserInfo,
|
||
// isAdmin = isAdmin
|
||
// )
|
||
// }
|
||
// }
|
||
}
|
||
|
||
override fun onDestroy() {
|
||
timeJob?.cancel()
|
||
keyEventHelper?.onDestroy()
|
||
super.onDestroy()
|
||
}
|
||
|
||
|
||
private val intervalExecutor by lazy { IntervalExecutor() }
|
||
private var faceTaskJob: Job? = null
|
||
|
||
// private val initialDelay = 5 * 60 * 1000L
|
||
// private val dealyMillis = 10 * 60 * 1000L
|
||
private val initialDelay = 5 * 60 * 1000L
|
||
private val dealyMillis = 5 * 60 * 1000L
|
||
fun startFaceTask(block: (List<String?>) -> Unit) {
|
||
faceTaskJob =
|
||
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
|
||
getFaceIncrementList(action = block)
|
||
}
|
||
}
|
||
|
||
private var faceTimestamp = 0L
|
||
|
||
//private var taskPageNo = 1
|
||
private fun getFaceIncrementList(pageNo: Int = 1, action:(List<String?>)-> Unit) {
|
||
val timestamp = SpTool.lastFaceTimestamp
|
||
if (timestamp == 0L) {
|
||
return
|
||
}
|
||
settingViewModel.getFaceIncrementList(
|
||
pageNo = pageNo,
|
||
timestamp = timestamp
|
||
) { list ->
|
||
runOnUiThread {
|
||
if (pageNo == 1 && list.isEmpty()) {
|
||
//未查询到增量数据
|
||
return@runOnUiThread
|
||
}
|
||
updateFaceData(list)
|
||
val userIdList = list.map { it.userId }
|
||
action(userIdList)
|
||
//if (list.size >= settingViewModel.PAGE_SIZE) {
|
||
// faceTimestamp = list.last().faceUpdateTimestamp ?: 0
|
||
// taskPageNo++
|
||
// getFaceIncrementList(taskPageNo)
|
||
// return@runOnUiThread
|
||
//}
|
||
if (list.isNotEmpty()) {
|
||
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
|
||
}
|
||
SpTool.lastFaceTimestamp = faceTimestamp
|
||
}
|
||
}
|
||
}
|
||
|
||
public val recognizeViewModel by viewModels<RecognizeViewModel>()
|
||
private fun updateFaceData(list: List<UserFaceModel>) {
|
||
Thread {
|
||
val faceList = mutableListOf<FaceEntity>()
|
||
val faceDao = FaceDatabase.getInstance(this).faceDao()
|
||
try {
|
||
list.forEach { model ->
|
||
if (model.faceDeleted == true) {
|
||
//删除数据
|
||
faceDao.deleteFaceById(model.userId)
|
||
} else {
|
||
val faceData = faceDao.queryByUserName(model.userId)
|
||
if (faceData == null) {
|
||
//保存数据
|
||
val faceEntity = FaceEntity(
|
||
model.userId,
|
||
null,
|
||
Base64.decode(model.faceFeatureStr)
|
||
).also {
|
||
//1-会员、2-临时用户
|
||
it.userType = if (model.isMember) "1" else "2"
|
||
}
|
||
faceList.add(faceEntity)
|
||
}
|
||
}
|
||
}
|
||
} catch (e: Exception) {
|
||
e.printStackTrace()
|
||
}
|
||
try {
|
||
if (faceList.isNotEmpty()) {
|
||
FaceDatabase.getInstance(this).faceDao().insert(faceList)
|
||
recognizeViewModel.refreshFaceList()
|
||
}
|
||
} catch (e: Exception) {
|
||
e.printStackTrace()
|
||
}
|
||
}.start()
|
||
}
|
||
|
||
fun initPlateList() {
|
||
// SpTool.putString(SpTool.PLATE_BOX_DATA, "")
|
||
// var list = SpTool.getPlateData()
|
||
val list: MutableList<EquipmentUserInfo> = mutableListOf()
|
||
repeat(22) { index ->
|
||
list.add(
|
||
EquipmentUserInfo(equipmentBoxCode = "${index + 1}")
|
||
)
|
||
}
|
||
MyApp.plateList = list
|
||
}
|
||
|
||
fun clearAllFace(block: () -> Unit) {
|
||
Thread {
|
||
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
|
||
faceDao.deleteAll()
|
||
faceDao.resetId()
|
||
runOnUiThread { block() }
|
||
}.start()
|
||
}
|
||
|
||
} |