Files
SmartPlateCabinet/app/src/main/java/com/sw/platecabinet/activity/BaseActivity.kt
T
mazengfeiandClaude Sonnet 4.6 2886e7d4a3 fix: 优化等待弹窗交互、人脸数据刷新及相机暂停逻辑
- BaseActivity: 等待对话框允许点击外部区域关闭
- LoginByFaceActivity: 恢复暂停相机时的预览帧处理
- PlcSettingActivity: 人脸数据更新成功后刷新人脸列表

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 10:41:25 +08:00

419 lines
14 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.sw.platecabinet.activity
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.view.WindowInsetsController
import android.view.WindowManager
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.App
import com.sw.plate.utils.Base64
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.R
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.ext.clickWithCoroutines
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.IntervalExecutor
import com.sw.platecabinet.utils.SpTool
import com.sw.platecabinet.view.CustomLoadingDialog
import com.sw.platecabinet.viewmodel.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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
protected val userViewModel by viewModels<UserViewModel>()
public val recognizeViewModel by viewModels<RecognizeViewModel>()
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()
}
)
timeJob = lifecycleScope.launch {
DateTimeUtils.realTimeChineseDateFlow()
.collect { date ->
it.tvRightTime.text = date
}
}
}
}
/**
* 左侧日期双击
*/
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)
mDialogWaiting!!.setCanceledOnTouchOutside(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")
}
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 = 1 * 1000L
private val dealyMillis = 10 * 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
userViewModel.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.isNotEmpty()) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
}
SpTool.lastFaceTimestamp = faceTimestamp
}
}
}
private fun updateFaceData(list: List<UserFaceModel>) {
lifecycleScope.launch(Dispatchers.IO) {
val faceList = mutableListOf<FaceEntity>()
val faceDao = FaceDatabase.getInstance(this@BaseActivity).faceDao()
try {
list.forEach { model ->
if (model.faceDeleted == true) {
//删除数据
faceDao.deleteFaceById(model.userId)
} else {
// 获取该用户所有已存特征,判断当前特征是否已存在
val existList = faceDao.queryAllByUserName(model.userId)
val featureMatches =
existList.map { Base64.encode(it.featureData) == model.faceFeatureStr }
Timber.d("userId=${model.userId}, 已存记录数=${existList.size}, 各条特征是否与服务器一致=$featureMatches")
val alreadyExists = featureMatches.any { it }
if (!alreadyExists) {
//当前特征不存在,保存
runCatching { getNewFaceEntity(model) }
.onSuccess { faceList.add(it) }
.onFailure { Timber.e(it, "人脸特征解码失败: userId=${model.userId}") }
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
if (faceList.isNotEmpty()) {
FaceDatabase.getInstance(this@BaseActivity).faceDao().insert(faceList)
}
recognizeViewModel.refreshFaceList()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun getNewFaceEntity(model: UserFaceModel): FaceEntity {
return FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.member) "1" else "2"
it.cardNo = model.cardNo
}
}
fun clearAllFace(block: () -> Unit) {
lifecycleScope.launch(Dispatchers.IO) {
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
faceDao.deleteAll()
faceDao.resetId()
recognizeViewModel.refreshFaceList()
withContext(Dispatchers.Main) { block() }
}
}
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var activityCallback: ((intent: Intent?) -> Unit)? = null
fun requestPermission(permission: String, callback: (isGranted: Boolean) -> Unit) {
this.permissionCallback = callback
requestPermissionLauncher.launch(permission)
}
// 权限请求回调
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
permissionCallback?.invoke(isGranted)
}
fun startActivity(intent: Intent, callback: (Intent?) -> Unit) {
this.activityCallback = callback
startActivityLauncher.launch(intent)
}
// activity页面返回的回调
private val startActivityLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
activityCallback?.invoke(it.data)
}
}