123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- package xn.xxp.home.sign
- import android.content.Context
- import android.os.Bundle
- import android.os.Message
- import android.view.LayoutInflater
- import android.view.View
- import android.view.ViewGroup
- import androidx.appcompat.app.AlertDialog
- import androidx.lifecycle.ViewModelProvider
- import core.ui.fragment.RcBaseFragment
- import core.util.MediaUtils
- import core.util.OnWeakListener
- import core.util.WeakHandler
- import core.util.ifNullOrEmpty
- import http.client.ApiRepository
- import http.exception.AICheckException
- import http.exception.NetException
- import http.vo.request.CheckInAllReq
- import http.vo.request.PatrolSignInReq
- import http.vo.response.LaboratoryVo
- import io.fotoapparat.Fotoapparat
- import io.fotoapparat.facedetector.processor.FaceDetectorProcessor
- import io.fotoapparat.log.fileLogger
- import io.fotoapparat.log.logcat
- import io.fotoapparat.log.loggers
- import io.fotoapparat.selector.front
- import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
- import org.greenrobot.eventbus.EventBus
- import xn.xxp.R
- import xn.xxp.app.LabApp
- import xn.xxp.databinding.FragmentSafetyCheckBinding
- import xn.xxp.home.auth.SignType
- import xn.xxp.home.auth.fragment.CardAuthFragment
- import xn.xxp.mqtt.event.OnlineUserEvent
- import xn.xxp.room.RoomTool
- import xn.xxp.room.bean.DeviceConfig
- import xn.xxp.room.bean.LabConfig
- import xn.xxp.utils.AudioPlayer
- import xn.xxp.utils.Tool
- import java.io.File
- import java.text.SimpleDateFormat
- import java.util.*
- import java.util.concurrent.TimeUnit
- /**
- * 安全准入检测
- *
- * @author ReiChin_
- */
- class SafetyCheckFragment :
- RcBaseFragment<FragmentSafetyCheckBinding>(),
- OnWeakListener {
- companion object {
- // 5s后,开始拍照/录像
- private const val MAX_TIME = 5
- private const val WHAT_REPEAT = 1
- private const val WHAT_TIME_OUT = 2
- private const val WHAT_RECORDING = 3
- private const val WHAT_COUNT_DOWN = 4
- private const val REPEAT_TIME = 10 * 1000L
- private const val TIME_OUT = 30 * 1000L
- private const val RECORDING_HEART = 300L
- fun newInstance(id: String): SafetyCheckFragment {
- val args = Bundle()
- args.putString("id", id)
- val fragment = SafetyCheckFragment()
- fragment.arguments = args
- return fragment
- }
- }
- private var mCounter = 0
- private var mCurrentCheckItem: CheckItem? = null
- private var mSignTyp = SignType.ACCESS.code
- private var mJumpSafetyCheck = false
- private var isMatching = false
- private var mSignId: String? = null
- private lateinit var laboratoryVo: LaboratoryVo
- private lateinit var labConfig: LabConfig
- private lateinit var deviceConfig: DeviceConfig
- private val viewModel by lazy {
- ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory()).get(
- SignViewModel::class.java
- )
- }
- private var mFragmentCallback: SignInFragmentCallback? = null
- private var mCountDown: ICountDown? = null
- private lateinit var mFotoapparat: Fotoapparat
- override fun onAttach(context: Context) {
- super.onAttach(context)
- mFragmentCallback = context as? SignInFragmentCallback
- mCountDown = context as? ICountDown
- }
- override fun onDetach() {
- super.onDetach()
- mFragmentCallback = null
- mCountDown = null
- }
- private fun takePicture() {
- isMatching = true
- val format = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault())
- val fileName = "${format.format(Calendar.getInstance().time)}.jpg"
- val photoFile = File(requireActivity().getExternalFilesDir("photos"), fileName)
- mFotoapparat.takePicture()
- .saveToFile(photoFile)
- .whenAvailable {
- handlerTakePictureResult(photoFile)
- }
- }
- override fun onResume() {
- super.onResume()
- try {
- mFotoapparat.stop()
- } catch (e: Exception) {
- }
- viewBinding.cameraView.postDelayed({
- mFotoapparat.start()
- isMatching = false
- }, 500)
- }
- override fun onPause() {
- super.onPause()
- mFotoapparat.stop()
- }
- override fun createViewBinding(
- inflater: LayoutInflater,
- container: ViewGroup?
- ) = FragmentSafetyCheckBinding.inflate(inflater, container, false)
- override fun initViews(savedInstanceState: Bundle?) {
- laboratoryVo = LabApp.laboratory
- labConfig = RoomTool.getInstance().labConfigDao().labConfig
- deviceConfig = RoomTool.getInstance().deviceConfigDao().deviceConfig
- mSignId = arguments?.getString("id")
- viewModel.checkItemData.observe(viewLifecycleOwner) { data ->
- mCurrentCheckItem = data
- data?.let {
- preStartCheck()
- }
- }
- viewModel.signTypeData.observe(viewLifecycleOwner) { data ->
- mSignTyp = data ?: SignType.ACCESS.code
- }
- viewModel.signFaceData.observe(viewLifecycleOwner) { data ->
- data?.let {
- mSignId = data.id
- mCurrentCheckItem = CheckItem(data.code)
- preStartCheck()
- }
- }
- mFotoapparat = Fotoapparat.with(requireActivity())
- .into(viewBinding.cameraView)
- .lensPosition(
- front()
- )//front()前置 back 后置
- .frameProcessor(
- FaceDetectorProcessor.with(requireActivity())
- .listener { faces ->
- viewBinding.rectanglesView.setRectangles(faces)
- }.build()
- )
- .cameraErrorCallback { onError(it.message) }
- .logger(loggers(logcat(), fileLogger(requireActivity())))
- .build()
- // 开始检测
- viewBinding.startCheck.setOnClickListener {
- // 移除语音播报提示检测Message
- mCountDown?.onStopCountDown()
- mWeakHandler.removeMessages(WHAT_REPEAT)
- mWeakHandler.removeMessages(WHAT_TIME_OUT)
- // 按钮不可用
- viewBinding.startCheck.isEnabled = false
- // 倒计时5s,结束后,拍照开始
- mCounter = 0
- viewBinding.countDown.visibility = View.VISIBLE
- mWeakHandler.sendEmptyMessage(WHAT_COUNT_DOWN)
- }
- // 电子牌 + 巡查签到
- if (1 == labConfig.isStart && SignType.PATROL.code == mSignTyp) {
- preStartCheck()
- }
- // takePictureInterval()
- }
- private fun preStartCheck() {
- mCountDown?.onStartCountDown()
- dispatchCheckTip()
- mWeakHandler.sendEmptyMessageDelayed(WHAT_TIME_OUT, TIME_OUT)
- }
- private fun takePictureInterval() {
- val disposable = io.reactivex.rxjava3.core.Observable.interval(3, 3, TimeUnit.SECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- if (isVisible && !isMatching) takePicture()
- }
- addDisposable(disposable)
- }
- private val mWeakHandler by lazy { WeakHandler(this) }
- override fun onWeakHandleMessage(msg: Message) {
- if (!isVisible) return
- when (msg.what) {
- WHAT_COUNT_DOWN -> handleCountDown(msg)
- WHAT_RECORDING -> handleRecording(msg)
- WHAT_REPEAT -> dispatchCheckTip()
- WHAT_TIME_OUT -> dispatchTimeOut()
- }
- }
- private fun handleCountDown(msg: Message) {
- val countDown = MAX_TIME - mCounter
- if (countDown <= 0) {
- viewBinding.countDown.visibility = View.GONE
- // 倒计时结束,开始拍照
- takePicture()
- } else {
- viewBinding.countDown.text = "$countDown"
- msg.target.sendEmptyMessageDelayed(WHAT_COUNT_DOWN, 1000)
- }
- mCounter++
- }
- private fun handleRecording(msg: Message) {
- val visibility = msg.obj as? Boolean ?: false
- viewBinding.recording.visibility =
- if (visibility) View.VISIBLE else View.INVISIBLE
- val message = msg.target.obtainMessage(WHAT_RECORDING, !visibility)
- msg.target.sendMessageDelayed(message, RECORDING_HEART)
- }
- private fun dispatchCheckTip() {
- // if (!isResumed) return
- val message = "穿戴安全准入检测,请确认处于检测区域后,点击开始检测。"
- AudioPlayer.getInstance().play(R.raw.audio_check_ready)
- mWeakHandler.sendEmptyMessageDelayed(WHAT_REPEAT, REPEAT_TIME)
- }
- private fun dispatchTimeOut() {
- mCountDown?.onStopCountDown()
- AudioPlayer.getInstance().play(R.raw.audio_failed_sign)
- SafetyCheckResultDialog(requireActivity(), ResultEnum.FAIL, "签到失败", 3000).apply {
- setCancelable(false)
- setOnDismissListener { requireActivity().finish() }
- show()
- }
- }
- private fun handlerTakePictureResult(photoFile: File) {
- if (!isVisible) return
- viewBinding.startCheck.isEnabled = true
- // 1) 显示loading
- val message = "检测中,请勿操作屏幕。"
- AudioPlayer.getInstance().play(R.raw.audio_checking)
- if (null == mCheckLoading) {
- mCheckLoading =
- SafetyCheckResultDialog(requireActivity(), ResultEnum.LOADING, message).apply {
- setCancelable(false)
- }
- }
- mCheckLoading?.show()
- // 2) 调用后台接口:穿戴安全准入检测
- callCheckApi(photoFile)
- }
- /*
- * 调用检测接口
- */
- private fun callCheckApi(photoFile: File) {
- if (null == labConfig || null == laboratoryVo) {
- showToast("暂无实验室信息,即将重新获取")
- Tool.INSTANCE.reStartApp("暂无实验室信息,即将重新获取")
- return
- }
- val pram = CheckInAllReq().apply {
- subId = labConfig.labId.toString()
- subName = laboratoryVo.subName
- id = mSignId
- file = photoFile
- }
- val disposable = ApiRepository.checkInAll(SignType.PATROL.code == mSignTyp, pram)
- .subscribe({
- // 删除源文件
- MediaUtils.deleteFile(photoFile)
- // 检测通过:跳转到下一级检测
- dismissCheckLoadingDialog()
- dispatchCheckSuccess()
- }, { throwable ->
- // 删除源文件
- MediaUtils.deleteFile(photoFile)
- // 检测失败:弹出提示,重新检测
- dismissCheckLoadingDialog()
- if (!pretreatmentError(throwable, "穿戴检测")) {
- throwable.printStackTrace()
- showNetError(throwable)
- // 出现其它错误时,重新开始计时。
- preStartCheck()
- }
- })
- addDisposable(disposable)
- }
- private fun dismissCheckLoadingDialog() {
- mCheckLoading?.let {
- if (it.isShowing) it.dismiss()
- mCheckLoading = null
- }
- }
- private fun dispatchCheckSuccess() {
- val message = "穿戴安全准入检测完成。"
- val dialog = SafetyCheckResultDialog(
- requireActivity(),
- ResultEnum.SUCCESS,
- message
- ).apply {
- setCancelable(false)
- show()
- }
- AudioPlayer.getInstance().play(R.raw.audio_wear_check_completed) {
- dialog.dismiss()
- callSignInSubmitApi()
- }
- }
- private fun callSignInSubmitApi() {
- showLoading("签到中...")
- val disposable = if (SignType.PATROL.code == mSignTyp) {
- val param = PatrolSignInReq().apply {
- num = deviceConfig.devId
- userId = LabApp.userVo.userId
- subjectId = labConfig.labId.toString()
- }
- ApiRepository.signInWithPatrol(param)
- } else {
- val code = mCurrentCheckItem?.code ?: ""
- ApiRepository.signIn(mJumpSafetyCheck, code, mSignId!!)
- }
- .subscribe({ success ->
- dismissLoading()
- if (success) {
- EventBus.getDefault().post(OnlineUserEvent())
- val message = "签到成功"
- AudioPlayer.getInstance().play(R.raw.audio_success_sign)
- SafetyCheckResultDialog(
- requireActivity(),
- ResultEnum.SUCCESS,
- message,
- 3000
- ).apply {
- setCancelable(false)
- setOnDismissListener {
- Tool.INSTANCE.openDoor()
- requireActivity().finish()
- }
- show()
- }
- }
- }, { throwable ->
- dismissLoading()
- if (!pretreatmentSignInError(throwable)) {
- throwable.printStackTrace()
- showNetError(throwable)
- }
- })
- addDisposable(disposable)
- }
- private fun pretreatmentSignInError(throwable: Throwable): Boolean {
- if (throwable is NetException) {
- // 600- 签到&签出已超时,请重新刷卡重试!
- if ("600" == throwable.code) {
- dispatchError_600(throwable.message)
- return true
- } else {
- // 后台返回的其它errorCode
- val message = throwable.message.ifNullOrEmpty("签到失败,请联系实验室管理员。")
- AudioPlayer.getInstance().play(R.raw.audio_failed_sign_admin)
- SafetyCheckResultDialog(requireActivity(), ResultEnum.FAIL, message, 3000).apply {
- setCancelable(false)
- setOnDismissListener { requireActivity().finish() }
- show()
- }
- return true
- }
- }
- return false
- }
- private fun dispatchError_600(message: String?) {
- AlertDialog.Builder(requireActivity())
- .setTitle("提示")
- .setMessage(message)
- .setPositiveButton("确定") { _, _ ->
- val fragment =
- requireFragmentManager().findFragmentByTag(CardAuthFragment::class.java.simpleName)
- as? CardAuthFragment ?: CardAuthFragment()
- mFragmentCallback?.switchFragment(fragment)
- }.show()
- }
- private fun pretreatmentError(throwable: Throwable, text: String): Boolean {
- if (throwable is AICheckException) {
- // 600- 签到&签出已超时,请重新刷卡重试!
- if ("600" == throwable.code) {
- dispatchError_600(throwable.message)
- return true
- } else if ("700" == throwable.code) {
- // 700- 检测失败,但是可以跳过此检测项
- mJumpSafetyCheck = true
- dispatchCheckSuccess()
- return true
- } else {
- // 后台返回了其它code
- val front = if ("300" == throwable.code) decodeCheckCode(throwable.data) else null
- val message = "${front ?: ""}穿戴检测未通过,请再次点击开始检测。"
- AudioPlayer.getInstance().play(speakCheckFailed(throwable.data))
- SafetyCheckResultDialog(requireActivity(), ResultEnum.WARNING, message).show()
- return true
- }
- } else if (throwable is NetException) {
- val message = throwable.message.ifNullOrEmpty("${text}失败,请联系实验室管理员。")
- //AudioPlayerManager.speakVoice(R.raw.audio_failed_sign_admin)
- SafetyCheckResultDialog(requireActivity(), ResultEnum.FAIL, message, 3000).apply {
- setCancelable(false)
- show()
- }
- return true
- }
- return false
- }
- private fun speakCheckFailed(code: String?): Int {
- return when (code) {
- "1" -> R.raw.audio_wear_check_hmj_failed
- "2" -> R.raw.audio_wear_check_syf_failed
- "3" -> R.raw.audio_wear_check_st_failed
- else -> R.raw.audio_wear_check_failed
- }
- }
- private fun decodeCheckCode(code: String?): String? {
- val laboratoryVo = LabApp.laboratory
- if (null == laboratoryVo || laboratoryVo.inCheck.isNullOrEmpty()) return null
- return laboratoryVo.inCheck.find { it.code == code }?.name
- }
- private var mCheckLoading: SafetyCheckResultDialog? = null
- private fun onError(message: String?) {
- dismissLoading()
- message?.let { showToast(it) }
- viewBinding.startCheck.isEnabled = true
- // 拍照错误时,重新开始计时。
- mCountDown?.onStartCountDown()
- dispatchCheckTip()
- mWeakHandler.sendEmptyMessageDelayed(WHAT_TIME_OUT, TIME_OUT)
- }
- override fun onDestroyView() {
- mCountDown?.onStopCountDown()
- mWeakHandler.removeCallbacksAndMessages(null)
- super.onDestroyView()
- }
- }
|