Kaynağa Gözat

refactor(更新)
- 增加头像采集
- 增加人脸校验
- 增加头像显示

周文健 4 ay önce
ebeveyn
işleme
c79ef82825
68 değiştirilmiş dosya ile 1697 ekleme ve 211 silme
  1. 2 2
      app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitViewModel.kt
  2. 5 2
      app/src/main/java/com/grkj/iscs/features/login/dialog/LoginDialog.kt
  3. 2 2
      app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt
  4. 10 1
      app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt
  5. 187 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/CheckFaceDialog.kt
  6. 18 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/common/SelectMemberFragment.kt
  7. 23 7
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateJobFragment.kt
  8. 21 6
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateSopFragment.kt
  9. 21 6
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateSopJobFragment.kt
  10. 13 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditJobFragment.kt
  11. 13 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopFragment.kt
  12. 13 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopJobFragment.kt
  13. 29 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt
  14. 92 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/UserInfoFragment.kt
  15. 6 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/common/SelectMemberViewModel.kt
  16. 2 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/common/WorkflowSettingViewModel.kt
  17. 2 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/data_manage/UserManageViewModel.kt
  18. 2 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/CardManageViewModel.kt
  19. 57 6
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt
  20. 19 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobViewModel.kt
  21. 19 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopJobViewModel.kt
  22. 19 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopViewModel.kt
  23. 16 3
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/user_info/UserInfoViewModel.kt
  24. 5 0
      app/src/main/res/drawable/icon_avatar.xml
  25. 9 0
      app/src/main/res/drawable/oval_shape.xml
  26. 21 13
      app/src/main/res/layout-land/activity_main.xml
  27. 53 0
      app/src/main/res/layout-land/dialog_check_face.xml
  28. 1 0
      app/src/main/res/layout-land/fragment_create_job.xml
  29. 1 0
      app/src/main/res/layout-land/fragment_create_sop.xml
  30. 1 0
      app/src/main/res/layout-land/fragment_create_sop_job.xml
  31. 1 0
      app/src/main/res/layout-land/fragment_edit_job.xml
  32. 1 0
      app/src/main/res/layout-land/fragment_edit_sop.xml
  33. 1 0
      app/src/main/res/layout-land/fragment_edit_sop_job.xml
  34. 377 0
      app/src/main/res/layout-land/fragment_user_info.xml
  35. 22 13
      app/src/main/res/layout/activity_main.xml
  36. 53 0
      app/src/main/res/layout/dialog_check_face.xml
  37. 1 0
      app/src/main/res/layout/fragment_create_job.xml
  38. 1 0
      app/src/main/res/layout/fragment_create_sop.xml
  39. 1 0
      app/src/main/res/layout/fragment_create_sop_job.xml
  40. 1 0
      app/src/main/res/layout/fragment_edit_job.xml
  41. 1 0
      app/src/main/res/layout/fragment_edit_sop.xml
  42. 1 0
      app/src/main/res/layout/fragment_edit_sop_job.xml
  43. 0 1
      app/src/main/res/layout/fragment_set_face.xml
  44. 298 128
      app/src/main/res/layout/fragment_user_info.xml
  45. 1 0
      app/src/main/res/values-en/strings.xml
  46. 2 0
      app/src/main/res/values-land/dimens.xml
  47. 1 0
      app/src/main/res/values-zh/strings.xml
  48. 2 0
      app/src/main/res/values/dimens.xml
  49. 1 0
      app/src/main/res/values/strings.xml
  50. 1 1
      data/src/main/java/com/grkj/data/dao/HardwareDao.kt
  51. 1 0
      data/src/main/java/com/grkj/data/dao/IsSopDao.kt
  52. 1 0
      data/src/main/java/com/grkj/data/dao/JobTicketDao.kt
  53. 14 0
      data/src/main/java/com/grkj/data/dao/UserDao.kt
  54. 5 0
      data/src/main/java/com/grkj/data/data/CommonConstants.kt
  55. 9 1
      data/src/main/java/com/grkj/data/data/MainDomainData.kt
  56. 10 0
      data/src/main/java/com/grkj/data/model/dos/WorkflowStep.kt
  57. 1 0
      data/src/main/java/com/grkj/data/model/vo/UserManageVo.kt
  58. 20 0
      data/src/main/java/com/grkj/data/repository/IUserRepository.kt
  59. 12 0
      data/src/main/java/com/grkj/data/repository/impl/network/NetworkUserRepository.kt
  60. 80 0
      data/src/main/java/com/grkj/data/repository/impl/standard/UserRepository.kt
  61. 1 0
      shared/build.gradle.kts
  62. 8 1
      shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt
  63. 6 2
      ui-base/src/main/java/com/grkj/ui_base/base/BaseFragment.kt
  64. 56 2
      ui-base/src/main/java/com/grkj/ui_base/base/BaseViewModel.kt
  65. 21 0
      ui-base/src/main/java/com/grkj/ui_base/utils/event/UiEvent.kt
  66. 1 0
      ui-base/src/main/res/values-en/strings.xml
  67. 1 0
      ui-base/src/main/res/values-zh/strings.xml
  68. 1 0
      ui-base/src/main/res/values/strings.xml

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitViewModel.kt

@@ -18,9 +18,9 @@ import javax.inject.Inject
  */
 @HiltViewModel
 class InitViewModel @Inject constructor(
-    val userRepository: IUserRepository,
+    override val userRepository: IUserRepository,
     val hardwareRepository: IHardwareRepository
-) : BaseViewModel() {
+) : BaseViewModel(userRepository) {
     /**
      * 移除超级管理员用户
      */

+ 5 - 2
app/src/main/java/com/grkj/iscs/features/login/dialog/LoginDialog.kt

@@ -22,6 +22,9 @@ import com.sik.sikcore.thread.ThreadUtils
 import com.sik.sikimage.ImageConvertUtils
 import com.sik.sikimage.ImageUtils
 
+/**
+ * 登录弹窗
+ */
 class LoginDialog(
     private var lifecycleOwner: LifecycleOwner,
     private var viewModel: LoginViewModel,
@@ -170,7 +173,7 @@ class LoginDialog(
                 mBinding.preview!!
             ) { bitmap, faceSize, alive ->
                 bitmap?.let { itBitmap ->
-                    if (faceSize==0){
+                    if (faceSize == 0) {
                         return@let
                     }
                     if (inFaceChecking) {
@@ -184,7 +187,7 @@ class LoginDialog(
                             ThreadUtils.runOnMainDelayed(1000) {
                                 inFaceChecking = false
                             }
-                        }else{
+                        } else {
                             ArcSoftUtil.stop()
                         }
                         callBack?.invoke(it)

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt

@@ -14,8 +14,8 @@ import javax.inject.Inject
  */
 @HiltViewModel
 class LoginViewModel @Inject constructor(
-    val userRepository: IUserRepository,
-) : BaseViewModel() {
+    override val userRepository: IUserRepository,
+) : BaseViewModel(userRepository) {
 
 
     /**

+ 10 - 1
app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt

@@ -9,6 +9,7 @@ import androidx.activity.viewModels
 import androidx.core.view.get
 import androidx.core.view.isNotEmpty
 import androidx.core.view.isVisible
+import coil.load
 import com.grkj.data.data.EventConstants
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.enums.RoleFunctionalPermissionsEnum
@@ -23,6 +24,8 @@ import com.grkj.ui_base.utils.event.BottomNavVisibilityEvent
 import com.grkj.shared.utils.extension.toByteArrays
 import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.ui_base.utils.event.RFIDCardReadEvent
+import com.sik.sikcore.extension.file
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 
 /**
@@ -77,6 +80,12 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
 
     override fun initView() {
         binding.nickname.text = MainDomainData.userInfo?.nickName ?: ""
+        (MainDomainData.userInfo?.avatar
+            ?: MainDomainData.userBiometricDataVo.find { it.type == "2" }?.content)?.let {
+            val faceData = it.file().readText()
+            val avatar = ImageConvertUtils.base64ToBitmap(faceData)
+            binding.avatar.load(avatar)
+        }
         // 1. 拆出两个可选控件
         // 2. 清空原有菜单
         binding.navBar.let {
@@ -100,7 +109,7 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
                 binding.navBar.selectedItemId = firstId
             }
         }
-        binding.nickname.setOnClickListener {
+        binding.userInfoLayout.setOnClickListener {
             if (MainDomainData.permissions.contains(RoleFunctionalPermissionsEnum.USER_INFO_HOME.functionalPermission)) {
                 binding.navBar.isVisible = true
                 replaceNavGraph(R.navigation.nav_user_info)

+ 187 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/CheckFaceDialog.kt

@@ -0,0 +1,187 @@
+package com.grkj.iscs.features.main.dialog
+
+import android.graphics.Bitmap
+import android.view.View
+import androidx.appcompat.widget.PopupMenu
+import androidx.lifecycle.LifecycleOwner
+import com.grkj.data.model.res.UserInfoRes
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogCheckFaceBinding
+import com.grkj.iscs.databinding.DialogLoginBinding
+import com.grkj.iscs.features.login.viewmodel.LoginViewModel
+import com.grkj.shared.utils.ArcSoftUtil
+import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.event.LoadingEvent
+import com.grkj.ui_base.utils.fingerprint.FingerprintUtil
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.DialogLifecycleCallback
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.SIKCore
+import com.sik.sikcore.activity.ActivityTracker
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.thread.ThreadUtils
+import com.sik.sikimage.ImageConvertUtils
+import com.sik.sikimage.ImageUtils
+
+/**
+ * 检查人脸弹窗
+ */
+class CheckFaceDialog(
+    private var lifecycleOwner: LifecycleOwner,
+    private var viewModel: BaseViewModel,
+    private var callBack: ((Boolean) -> Unit)? = null
+) : OnBindView<CustomDialog>(R.layout.dialog_check_face) {
+    private var mLoginType = 0 // 0:人脸 1:指纹 2:工卡 3:账号
+    private var inFaceChecking: Boolean = false
+    private val mPairList = mutableListOf(
+        Pair(
+            SIKCore.getApplication().getString(com.grkj.ui_base.R.string.please_scan_face),
+            R.drawable.icon_login_menu_face
+        ), Pair(
+            SIKCore.getApplication().getString(com.grkj.ui_base.R.string.please_scan_fingerprint),
+            R.mipmap.icon_login_menu_fingerprint
+        ), Pair(
+            SIKCore.getApplication().getString(com.grkj.ui_base.R.string.please_swipe_card),
+            R.drawable.icon_login_menu_card
+        )
+    )
+
+    private lateinit var mBinding: DialogCheckFaceBinding
+
+    override fun onBind(customDialog: CustomDialog, contentView: View) {
+        mBinding = DialogCheckFaceBinding.bind(contentView)
+        customDialog.setDialogLifecycleCallback(object : DialogLifecycleCallback<CustomDialog>() {
+            override fun onDismiss(dialog: CustomDialog?) {
+                ArcSoftUtil.stop()
+                super.onDismiss(dialog)
+            }
+        })
+        mBinding.closeIv.setDebouncedClickListener {
+            when (mLoginType) {
+                0 -> {
+                    ArcSoftUtil.stop()
+                }
+
+                1 -> {
+                    FingerprintUtil.stop()
+                    FingerprintUtil.unInit()
+                }
+            }
+            customDialog.dismiss()
+        }
+        customDialog.setDialogLifecycleCallback(object : DialogLifecycleCallback<CustomDialog>() {
+            override fun onDismiss(dialog: CustomDialog?) {
+                when (mLoginType) {
+                    0 -> {
+                        ArcSoftUtil.stop()
+                    }
+
+                    1 -> {
+                        FingerprintUtil.stop()
+                        FingerprintUtil.unInit()
+                    }
+                }
+                super.onDismiss(dialog)
+            }
+        })
+        mBinding.llEasyContainer.visibility = View.VISIBLE
+        mBinding.ivIcon.setImageResource(mPairList[mLoginType].second)
+        mBinding.tvTip.text = mPairList[mLoginType].first
+        when (mLoginType) {
+            0 -> {
+                if (!ArcSoftUtil.isActivated) {
+                    PopTip.tip(
+                        CommonUtils.getStr(com.grkj.ui_base.R.string.face_can_not_process)
+                            .toString()
+                    )
+                }
+                startFace()
+            }
+
+            1 -> {
+                FingerprintUtil.init(SIKCore.getApplication())
+                FingerprintUtil.start()
+                FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {
+                    override fun onScan(bitmap: Bitmap) {
+                        LoadingEvent.sendLoadingEvent(
+                            CommonUtils.getStr(com.grkj.ui_base.R.string.doing_checking), true
+                        )
+                        viewModel.checkFinger(
+                            ImageConvertUtils.bitmapToBase64(bitmap).toString()
+                        ).observe(lifecycleOwner) {
+                            if (it) {
+                                callBack?.invoke(it)
+                            } else {
+                                PopTip.tip(R.string.verify_failed)
+                            }
+                        }
+                    }
+                })
+            }
+        }
+    }
+
+    fun showByType(loginType: Int) {
+        this.mLoginType = loginType
+    }
+
+    companion object {
+        /**
+         * 根据类型显示弹框
+         *
+         * @param loginType 0:人脸 1:指纹 2:工卡
+         */
+        @JvmStatic
+        fun show(
+            lifecycleOwner: LifecycleOwner,
+            viewModel: BaseViewModel,
+            loginType: Int,
+            callBack: ((Boolean) -> Unit)?
+        ) {
+            CustomDialog.show(CheckFaceDialog(lifecycleOwner, viewModel, callBack).apply {
+                showByType(loginType)
+            })
+        }
+    }
+
+
+    private fun startFace() {
+        ActivityTracker.getCurrentActivity()?.let { context ->
+            ArcSoftUtil.initEngine(context)
+            ArcSoftUtil.initCamera(
+                context, context.windowManager, mBinding.preview!!
+            ) { bitmap, faceSize, alive ->
+                bitmap?.let { itBitmap ->
+                    if (faceSize == 0) {
+                        return@let
+                    }
+                    if (inFaceChecking) {
+                        return@let
+                    }
+                    inFaceChecking = true
+                    viewModel.checkFace(
+                        ImageConvertUtils.bitmapToBase64(itBitmap).toString()
+                    ).observe(lifecycleOwner) {
+                        if (it == false) {
+                            ThreadUtils.runOnMainDelayed(1000) {
+                                inFaceChecking = false
+                            }
+                        } else {
+                            ArcSoftUtil.stop()
+                        }
+                        if (it) {
+                            callBack?.invoke(it)
+                        } else {
+                            PopTip.tip(R.string.verify_failed)
+                        }
+                    }
+                    LoadingEvent.sendLoadingEvent(
+                        context.getString(com.grkj.ui_base.R.string.doing_checking), true
+                    )
+                }
+            }
+        }
+    }
+}

+ 18 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/common/SelectMemberFragment.kt

@@ -3,6 +3,7 @@ package com.grkj.iscs.features.main.fragment.common
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.grid
 import com.drake.brv.utils.linear
@@ -21,7 +22,9 @@ import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 import kotlin.getValue
 
@@ -98,6 +101,11 @@ class SelectMemberFragment : BaseFragment<FragmentSelectMemeberBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = item.isSelected
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
         itemBinding.root.setOnClickListener {
             if (item.isSelected) {
                 if (!isLockerSelect && (selectedColockerData.size == 1 || (viewModel.ticketUsers.isNotEmpty() && viewModel.ticketUsers.find { it.userId == item.userId }?.jobStatus == "1"))) {
@@ -138,6 +146,11 @@ class SelectMemberFragment : BaseFragment<FragmentSelectMemeberBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
         itemBinding.root.setOnClickListener {
             if (selectedLockerData.size == 1 && viewModel.jobTicketData != null && viewModel.jobTicketData?.ticketStatus != JobTicketStatusEnum.SELECT_MEMBER.status) {
                 PopTip.tip(com.grkj.ui_base.R.string.can_not_remove_current_locker)
@@ -155,6 +168,11 @@ class SelectMemberFragment : BaseFragment<FragmentSelectMemeberBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
         itemBinding.root.setOnClickListener {
             if (selectedColockerData.size == 1 || (viewModel.ticketUsers.isNotEmpty() && viewModel.ticketUsers.find { it.userId == item.userId }?.jobStatus == "1")) {
                 PopTip.tip(com.grkj.ui_base.R.string.can_not_remove_current_colocker)

+ 23 - 7
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateJobFragment.kt

@@ -3,6 +3,7 @@ package com.grkj.iscs.features.main.fragment.job_manage
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.grid
 import com.drake.brv.utils.linear
@@ -24,8 +25,10 @@ import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
 import com.sik.sikcore.thread.ThreadUtils
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
@@ -460,6 +463,11 @@ class CreateJobFragment : BaseFormFragment<FragmentCreateJobBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
     }
 
     private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
@@ -496,13 +504,21 @@ class CreateJobFragment : BaseFormFragment<FragmentCreateJobBinding>() {
         binding.noSelectedMemberLayout.isVisible =
             selectedColockerData.isEmpty() && selectedLockerData.isEmpty()
         binding.noSelectedPointLayout.isVisible = selectedPointData.isEmpty()
-        if (selectedColockerData.isNotEmpty()) {
-            binding.colockerRv.models = selectedColockerData
-        }
-        if (selectedLockerData.isNotEmpty()) {
-            val userData = selectedLockerData.first()
-            binding.lockerName.text = userData.nickName
-        }
+        viewModel.getUserBiometricDataByUserIds((selectedLockerData + selectedColockerData).map { it.userId })
+            .observe(this) {
+                if (selectedColockerData.isNotEmpty()) {
+                    binding.colockerRv.models = selectedColockerData
+                }
+                if (selectedLockerData.isNotEmpty()) {
+                    val userData = selectedLockerData.first()
+                    binding.lockerName.text = userData.nickName
+                    (userData.avatar
+                        ?: viewModel.userBiometricDataVo.find { it.userId == userData.userId }?.content)?.let {
+                        val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+                        binding.lockerIv.load(avatar)
+                    } ?: binding.lockerIv.setImageResource(R.drawable.icon_select_member)
+                }
+            }
         if (selectedPointData.isNotEmpty()) {
             binding.pointRv.models = selectedPointData
         }

+ 21 - 6
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateSopFragment.kt

@@ -3,6 +3,7 @@ package com.grkj.iscs.features.main.fragment.job_manage
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.grid
 import com.drake.brv.utils.linear
@@ -24,7 +25,9 @@ import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 
 /**
@@ -363,6 +366,11 @@ class CreateSopFragment : BaseFormFragment<FragmentCreateSopBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
     }
 
     private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
@@ -399,12 +407,19 @@ class CreateSopFragment : BaseFormFragment<FragmentCreateSopBinding>() {
         binding.noSelectedMemberLayout.isVisible =
             selectedColockerData.isEmpty() && selectedLockerData.isEmpty()
         binding.noSelectedPointLayout.isVisible = selectedPointData.isEmpty()
-        if (selectedColockerData.isNotEmpty()) {
-            binding.colockerRv.models = selectedColockerData
-        }
-        if (selectedLockerData.isNotEmpty()) {
-            val userData = selectedLockerData.first()
-            binding.lockerName.text = userData.nickName
+        viewModel.getUserBiometricDataByUserIds((selectedLockerData+selectedColockerData).map { it.userId }).observe(this) {
+            if (selectedColockerData.isNotEmpty()) {
+                binding.colockerRv.models = selectedColockerData
+            }
+            if (selectedLockerData.isNotEmpty()) {
+                val userData = selectedLockerData.first()
+                binding.lockerName.text = userData.nickName
+                (userData.avatar
+                    ?: viewModel.userBiometricDataVo.find { it.userId == userData.userId }?.content)?.let {
+                    val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+                    binding.lockerIv.load(avatar)
+                } ?: binding.lockerIv.setImageResource(R.drawable.icon_select_member)
+            }
         }
         if (selectedPointData.isNotEmpty()) {
             binding.pointRv.models = selectedPointData

+ 21 - 6
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateSopJobFragment.kt

@@ -4,6 +4,7 @@ import android.view.View
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.grid
 import com.drake.brv.utils.linear
@@ -27,7 +28,9 @@ import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 import kotlin.getValue
 
@@ -313,6 +316,11 @@ class CreateSopJobFragment : BaseFormFragment<FragmentCreateSopJobBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
     }
 
     private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
@@ -341,12 +349,19 @@ class CreateSopJobFragment : BaseFormFragment<FragmentCreateSopJobBinding>() {
         binding.noSelectedMemberLayout.isVisible =
             selectedColockerData.isEmpty() && selectedLockerData.isEmpty()
         binding.noSelectedPointLayout.isVisible = selectedPointData.isEmpty()
-        if (selectedColockerData.isNotEmpty()) {
-            binding.colockerRv.models = selectedColockerData
-        }
-        if (selectedLockerData.isNotEmpty()) {
-            val userData = selectedLockerData.first()
-            binding.lockerName.text = userData.nickName
+        viewModel.getUserBiometricDataByUserIds((selectedLockerData+selectedColockerData).map { it.userId }).observe(this) {
+            if (selectedColockerData.isNotEmpty()) {
+                binding.colockerRv.models = selectedColockerData
+            }
+            if (selectedLockerData.isNotEmpty()) {
+                val userData = selectedLockerData.first()
+                binding.lockerName.text = userData.nickName
+                (userData.avatar
+                    ?: viewModel.userBiometricDataVo.find { it.userId == userData.userId }?.content)?.let {
+                    val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+                    binding.lockerIv.load(avatar)
+                } ?: binding.lockerIv.setImageResource(R.drawable.icon_select_member)
+            }
         }
         if (selectedPointData.isNotEmpty()) {
             binding.pointRv.models = selectedPointData

+ 13 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditJobFragment.kt

@@ -3,6 +3,7 @@ package com.grkj.iscs.features.main.fragment.job_manage
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.grid
 import com.drake.brv.utils.linear
@@ -24,7 +25,9 @@ import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 
 /**
@@ -318,6 +321,11 @@ class EditJobFragment : BaseFormFragment<FragmentEditJobBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
     }
 
     private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
@@ -389,6 +397,11 @@ class EditJobFragment : BaseFormFragment<FragmentEditJobBinding>() {
         if (selectedLockerData.isNotEmpty()) {
             val userData = selectedLockerData.first()
             binding.lockerName.text = userData.nickName
+            (userData.avatar
+                ?: viewModel.userBiometricDataVo.find { it.userId == userData.userId }?.content)?.let {
+                val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+                binding.lockerIv.load(avatar)
+            } ?: binding.lockerIv.setImageResource(R.drawable.icon_select_member)
         }
         if (selectedPointData.isNotEmpty()) {
             binding.pointRv.models = selectedPointData

+ 13 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopFragment.kt

@@ -3,6 +3,7 @@ package com.grkj.iscs.features.main.fragment.job_manage
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.grid
 import com.drake.brv.utils.linear
@@ -25,7 +26,9 @@ import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 
 /**
@@ -284,6 +287,11 @@ class EditSopFragment : BaseFormFragment<FragmentEditSopBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
     }
 
     private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
@@ -359,6 +367,11 @@ class EditSopFragment : BaseFormFragment<FragmentEditSopBinding>() {
         if (selectedLockerData.isNotEmpty()) {
             val userData = selectedLockerData.first()
             binding.lockerName.text = userData.nickName
+            (userData.avatar
+                ?: viewModel.userBiometricDataVo.find { it.userId == userData.userId }?.content)?.let {
+                val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+                binding.lockerIv.load(avatar)
+            } ?: binding.lockerIv.setImageResource(R.drawable.icon_select_member)
         }
         if (selectedPointData.isNotEmpty()) {
             binding.pointRv.models = selectedPointData

+ 13 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopJobFragment.kt

@@ -4,6 +4,7 @@ import android.view.View
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.grid
 import com.drake.brv.utils.linear
@@ -27,7 +28,9 @@ import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 import kotlin.getValue
 
@@ -305,6 +308,11 @@ class EditSopJobFragment : BaseFormFragment<FragmentEditSopJobBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.lockerName.text = item.nickName
         itemBinding.lockerIcon.isSelected = true
+        (item.avatar
+            ?: viewModel.userBiometricDataVo.find { it.userId == item.userId }?.content)?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            itemBinding.lockerIcon.load(avatar)
+        } ?: itemBinding.lockerIcon.setImageResource(R.drawable.icon_select_member)
     }
 
     private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
@@ -367,6 +375,11 @@ class EditSopJobFragment : BaseFormFragment<FragmentEditSopJobBinding>() {
         if (selectedLockerData.isNotEmpty()) {
             val userData = selectedLockerData.first()
             binding.lockerName.text = userData.nickName
+            (userData.avatar
+                ?: viewModel.userBiometricDataVo.find { it.userId == userData.userId }?.content)?.let {
+                val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+                binding.lockerIv.load(avatar)
+            } ?: binding.lockerIv.setImageResource(R.drawable.icon_select_member)
         }
         if (selectedPointData.isNotEmpty()) {
             binding.pointRv.models = selectedPointData

+ 29 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt

@@ -5,7 +5,10 @@ import android.view.ViewGroup
 import android.widget.LinearLayout
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import com.drake.brv.BindingAdapter
 import com.drake.brv.annotaion.DividerOrientation
 import com.drake.brv.utils.dividerSpace
@@ -30,14 +33,17 @@ import com.grkj.ui_base.base.BaseFragment
 import com.grkj.data.data.EventConstants
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.model.dos.WorkflowStep
+import com.grkj.iscs.features.main.dialog.CheckFaceDialog
 import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.RFIDCardReadEvent
+import com.grkj.ui_base.utils.event.UiEvent
 import com.grkj.ui_base.utils.extension.toggleExpandView
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.count
 import kotlin.getValue
 
 /**
@@ -129,6 +135,29 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
         }
     }
 
+    override suspend fun initObservers() {
+        super.initObservers()
+        repeatOnLifecycle(Lifecycle.State.STARTED) {
+            viewModel.uiEvents.collect { ev ->
+                when (ev) {
+                    is UiEvent.FaceCheck -> {
+                        CheckFaceDialog.show(viewLifecycleOwner, viewModel, 0) {
+                            viewModel.onParamProvided(it)
+                        }
+                    }
+
+                    is UiEvent.FingerprintCheck -> {
+                        CheckFaceDialog.show(viewLifecycleOwner, viewModel, 1) {
+                            viewModel.onParamProvided(it)
+                        }
+                    }
+
+                    else -> {}
+                }
+            }
+        }
+    }
+
     /**
      * 检查界面
      */

+ 92 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/UserInfoFragment.kt

@@ -1,16 +1,26 @@
 package com.grkj.iscs.features.main.fragment.user_info
 
+import android.graphics.Bitmap
+import android.view.View
+import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import coil.load
+import com.grkj.data.data.CommonConstants
 import com.grkj.data.data.MainDomainData
+import com.grkj.data.utils.FileStorageUtils
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentUserInfoBinding
 import com.grkj.iscs.features.main.viewmodel.user_info.UserInfoViewModel
+import com.grkj.shared.utils.ArcSoftUtil
 import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.LogoutEvent
 import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.date.TimeUtils
+import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 
 /**
@@ -19,6 +29,8 @@ import dagger.hilt.android.AndroidEntryPoint
 @AndroidEntryPoint
 class UserInfoFragment : BaseFragment<FragmentUserInfoBinding>() {
     private val viewModel: UserInfoViewModel by viewModels()
+    private var mCapturedBitmap: Bitmap? = null
+    private var isFaceChecking: Boolean = false
 
     override fun getLayoutId(): Int {
         return R.layout.fragment_user_info
@@ -26,11 +38,48 @@ class UserInfoFragment : BaseFragment<FragmentUserInfoBinding>() {
 
     override fun initView() {
         binding.back.setDebouncedClickListener {
+            releaseFace()
             navController.popBackStack()
         }
         binding.cancel.setDebouncedClickListener {
             navController.popBackStack()
         }
+        binding.avatar.setDebouncedClickListener {
+            binding.showUserInfoLayout.isVisible = false
+            binding.faceSetLayout.isVisible = true
+            isFaceChecking = false
+            startFace()
+        }
+        binding.setAvatarCancel.setDebouncedClickListener {
+            releaseFace()
+            isFaceChecking = false
+            binding.showUserInfoLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+        }
+        binding.setAvatarConfirm.setDebouncedClickListener {
+            releaseFace()
+            isFaceChecking = false
+            binding.avatar.load(mCapturedBitmap)
+            binding.showUserInfoLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            val saveFileName =
+                "${MainDomainData.userInfo?.userId}_avatar_${TimeUtils.nowString("yyyyMMddHHmmss")}"
+            FileStorageUtils.writeText(
+                CommonConstants.AVATAR_FOLDER,
+                saveFileName,
+                ImageConvertUtils.bitmapToBase64(mCapturedBitmap).toString()
+            )
+            val savePath = FileStorageUtils.getFilePath(
+                CommonConstants.AVATAR_FOLDER,
+                saveFileName
+            )
+            viewModel.saveUserAvatar(savePath).observe(this) {}
+        }
+        binding.recapture.setDebouncedClickListener {
+            isFaceChecking = false
+            binding.image.isVisible = false
+            binding.preview.isVisible = true
+        }
         binding.confirm.setDebouncedClickListener {
             if (checkData()) {
                 viewModel.saveUserInfo(
@@ -57,6 +106,10 @@ class UserInfoFragment : BaseFragment<FragmentUserInfoBinding>() {
 
     override fun initData() {
         super.initData()
+        MainDomainData.userInfo?.avatar?.let {
+            val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+            binding.avatar.load(avatar)
+        }
         binding.username.text = MainDomainData.userInfo?.userName
         binding.nicknameEt.setText(MainDomainData.userInfo?.nickName)
         binding.phoneEt.setText(MainDomainData.userInfo?.phoneNumber)
@@ -73,4 +126,43 @@ class UserInfoFragment : BaseFragment<FragmentUserInfoBinding>() {
         }
         return true
     }
+
+    private fun startFace() {
+        binding.preview.isVisible = true
+        binding.image.isVisible = false
+        ArcSoftUtil.initEngine(requireContext())
+        ArcSoftUtil.initCamera(
+            requireContext(),
+            requireActivity().windowManager,
+            binding.preview
+        ) { bitmap, faceSize, alive ->
+            if (isFaceChecking) {
+                return@initCamera
+            }
+            isFaceChecking = true
+            binding.tipTv.isVisible = faceSize > 1 || alive == false
+            logger.info("人脸检测结果: ${bitmap == null},$faceSize,$alive")
+            if (faceSize > 1) {
+                binding.tipTv.text = getString(R.string.only_one_person_allowed)
+                isFaceChecking = false
+                return@initCamera
+            }
+            if (alive == false) {
+                binding.tipTv.text =
+                    getString(R.string.real_person_verification_required)
+                isFaceChecking = false
+                return@initCamera
+            }
+            binding.preview.visibility = View.INVISIBLE
+            binding.image.visibility = View.VISIBLE
+            mCapturedBitmap = bitmap
+            binding.image.setImageBitmap(bitmap)
+            binding.recapture.visibility = View.VISIBLE
+            binding.confirm.visibility = View.VISIBLE
+        }
+    }
+
+    private fun releaseFace() {
+        ArcSoftUtil.stop()
+    }
 }

+ 6 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/common/SelectMemberViewModel.kt

@@ -3,8 +3,10 @@ package com.grkj.iscs.features.main.viewmodel.common
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
 import com.grkj.data.model.dos.IsJobTicketUser
+import com.grkj.data.model.dos.SysUserCharacteristicDo
 import com.grkj.data.model.vo.IsJobTicketDataVo
 import com.grkj.data.model.vo.IsJobTicketUserDataVo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.UserManageVo
 import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.IUserRepository
@@ -18,13 +20,14 @@ import javax.inject.Inject
  */
 @HiltViewModel
 class SelectMemberViewModel @Inject constructor(
-    val userRepository: IUserRepository,
+    override val userRepository: IUserRepository,
     val jobTicketRepository: IJobTicketRepository
 ) :
-    BaseViewModel() {
+    BaseViewModel(userRepository) {
     var workstationId: Long = 0
     var ticketUsers: List<IsJobTicketUserDataVo> = listOf()
     var jobTicketData: IsJobTicketDataVo? = null
+    var userBiometricDataVo: List<SysBiometricDataVo> = mutableListOf()
 
     /**
      * 用户数据
@@ -37,6 +40,7 @@ class SelectMemberViewModel @Inject constructor(
     fun getUserData(): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
             userData = userRepository.getAllUserDataWithWorkstation(workstationId)
+            userBiometricDataVo = userRepository.getUserBiometricDataByUserIds(userData.map { it.userId })
             emit(true)
         }
     }

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/common/WorkflowSettingViewModel.kt

@@ -23,9 +23,9 @@ import javax.inject.Inject
 @HiltViewModel
 class WorkflowSettingViewModel @Inject constructor(
     val workflowRepository: IWorkflowRepository,
-    val userRepository: IUserRepository,
+    override val userRepository: IUserRepository,
     val roleRepository: IRoleRepository
-) : BaseViewModel() {
+) : BaseViewModel(userRepository) {
     var modeId: Long = 0
     var workflowSteps: List<WorkflowStep> = mutableListOf()
     var currentStep: WorkflowStep? = null

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/data_manage/UserManageViewModel.kt

@@ -22,11 +22,11 @@ import javax.inject.Inject
  */
 @HiltViewModel
 class UserManageViewModel @Inject constructor(
-    val userRepository: IUserRepository,
+    override val userRepository: IUserRepository,
     val roleRepository: IRoleRepository,
     val workstationRepository: IWorkstationRepository,
     val hardwareRepository: IHardwareRepository
-) : BaseViewModel() {
+) : BaseViewModel(userRepository) {
     private var current: Int = 0
     private var size: Int = 50
     var userManageDataList: MutableList<UserManageVo> = mutableListOf()

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/CardManageViewModel.kt

@@ -22,8 +22,8 @@ import javax.inject.Inject
 @HiltViewModel
 class CardManageViewModel @Inject constructor(
     private val hardwareRepository: IHardwareRepository,
-    private val userRepository: IUserRepository
-) : BaseViewModel() {
+    override val userRepository: IUserRepository
+) : BaseViewModel(userRepository) {
     private var current: Int = 0
     private val size: Int = 50
     var cardManageDataList: MutableList<IsJobCard> = mutableListOf()

+ 57 - 6
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt

@@ -1,6 +1,7 @@
 package com.grkj.iscs.features.main.viewmodel.job_manage
 
 import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.liveData
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.di.RepositoryManager
@@ -15,16 +16,20 @@ import com.grkj.data.model.vo.IsJobTicketLockDataVo
 import com.grkj.data.model.vo.IsJobTicketPointsDataVo
 import com.grkj.data.model.vo.IsJobTicketStepDataVo
 import com.grkj.data.model.vo.IsJobTicketUserDataVo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.UserManageVo
 import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.IWorkflowRepository
+import com.grkj.data.repository.impl.standard.UserRepository
 import com.grkj.iscs.R
+import com.grkj.iscs.features.main.dialog.CheckFaceDialog
 import com.grkj.ui_base.base.BaseViewModel
 import com.grkj.ui_base.business.BleBusinessManager
 import com.grkj.ui_base.business.ModbusBusinessManager
 import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.LoadingEvent
+import com.grkj.ui_base.utils.event.UiEvent
 import com.grkj.ui_base.utils.modbus.DeviceConst
 import com.grkj.ui_base.utils.modbus.ModBusController
 import com.kongzue.dialogx.dialogs.PopTip
@@ -40,8 +45,9 @@ import java.util.concurrent.atomic.AtomicInteger
  */
 @HiltViewModel
 class JobExecuteViewModel @Inject constructor(
-    val jobTicketRepository: IJobTicketRepository, val workflowRepository: IWorkflowRepository
-) : BaseViewModel() {
+    val jobTicketRepository: IJobTicketRepository, val workflowRepository: IWorkflowRepository,
+    userRepository: UserRepository
+) : BaseViewModel(userRepository) {
     var ticketId: Long = 0
     var ticketData: IsJobTicketDataVo? = null
     lateinit var ticketKey: List<IsJobTicketKeyDataVo>
@@ -56,6 +62,7 @@ class JobExecuteViewModel @Inject constructor(
     var workflowModes: List<WorkflowMode> = mutableListOf()
     var workflowSteps: List<WorkflowStep> = mutableListOf()
     var isUnlockFirst: Boolean = false
+    var userBiometricDataVo: List<SysBiometricDataVo> = mutableListOf()
 
     /**
      * 获取当前流程步骤数据
@@ -125,8 +132,19 @@ class JobExecuteViewModel @Inject constructor(
                 }
                 return@liveData
             }
-            jobTicketRepository.cancelJob(ticketId)
-            emit(true)
+            if (workflowStep?.needCheckFace() == true) {
+                _uiEvents.send(UiEvent.FaceCheck)
+                val checkResult = _paramResponse.receive() as Boolean
+                if (checkResult) {
+                    jobTicketRepository.cancelJob(ticketId)
+                    emit(true)
+                } else {
+                    emit(false)
+                }
+            } else {
+                jobTicketRepository.cancelJob(ticketId)
+                emit(true)
+            }
         }
     }
 
@@ -147,8 +165,19 @@ class JobExecuteViewModel @Inject constructor(
                 }
                 return@liveData
             }
-            jobTicketRepository.finishJob(ticketId)
-            emit(true)
+            if (workflowStep?.needCheckFace() == true) {
+                _uiEvents.send(UiEvent.FaceCheck)
+                val checkResult = _paramResponse.receive() as Boolean
+                if (checkResult) {
+                    jobTicketRepository.finishJob(ticketId)
+                    emit(true)
+                } else {
+                    emit(false)
+                }
+            } else {
+                jobTicketRepository.finishJob(ticketId)
+                emit(true)
+            }
         }
     }
 
@@ -169,6 +198,17 @@ class JobExecuteViewModel @Inject constructor(
                 }
                 return@liveData
             }
+            var verifySuccess = false
+            if (workflowStep?.needCheckFace() == true) {
+                _uiEvents.send(UiEvent.FaceCheck)
+                val checkResult = _paramResponse.receive() as Boolean
+                verifySuccess = checkResult
+            } else {
+                verifySuccess = true
+            }
+            if (!verifySuccess) {
+                return@liveData
+            }
             ModbusBusinessManager.checkEquipCount(ticketId, ticketPoints.count {
                 it.pointStatus == "0" || (it.pointStatus == "2" && workflowRepository.isUnlockBeforeLock(
                     ticketData?.modeId!!
@@ -256,6 +296,17 @@ class JobExecuteViewModel @Inject constructor(
                 }
                 return@liveData
             }
+            var verifySuccess = false
+            if (workflowStep?.needCheckFace() == true) {
+                _uiEvents.send(UiEvent.FaceCheck)
+                val checkResult = _paramResponse.receive() as Boolean
+                verifySuccess = checkResult
+            } else {
+                verifySuccess = true
+            }
+            if (!verifySuccess) {
+                return@liveData
+            }
             if (checkBeforeToUnlock()) {
                 ModbusBusinessManager.checkEquipCount(ticketId, 0, true) { keyMap, _ ->
                     LoadingEvent.sendLoadingEvent()

+ 19 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobViewModel.kt

@@ -8,11 +8,13 @@ import com.grkj.data.model.dos.WorkflowMode
 import com.grkj.data.model.dos.WorkflowStep
 import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.model.vo.PointManageVo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.UserManageVo
 import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.ISopRepository
 import com.grkj.data.repository.IWorkflowRepository
 import com.grkj.data.repository.IWorkstationRepository
+import com.grkj.data.repository.impl.standard.UserRepository
 import com.grkj.ui_base.base.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
@@ -26,8 +28,9 @@ class JobViewModel @Inject constructor(
     val workstationRepository: IWorkstationRepository,
     val sopRepository: ISopRepository,
     val jobTicketRepository: IJobTicketRepository,
-    val workflowRepository: IWorkflowRepository
-) : BaseViewModel() {
+    val workflowRepository: IWorkflowRepository,
+    override val userRepository: UserRepository
+) : BaseViewModel(userRepository) {
     var workstationData: List<IsWorkstation> = listOf()
     var jobTicketData: JobTicketManageVo? = null
     var jobPointsData: List<PointManageVo> = mutableListOf()
@@ -35,6 +38,7 @@ class JobViewModel @Inject constructor(
     var jobColockerData: List<UserManageVo> = mutableListOf()
     var workflowModes: List<WorkflowMode> = mutableListOf()
     var workflowSteps: List<WorkflowStep> = mutableListOf()
+    var userBiometricDataVo: List<SysBiometricDataVo> = mutableListOf()
 
     /**
      * 初始化岗位数据
@@ -140,6 +144,8 @@ class JobViewModel @Inject constructor(
             jobPointsData =
                 jobTicketRepository.getTicketPointsByTicketId(ticketId)
             val tempJobTicketUserId = jobTicketRepository.getTicketUsersByTicketId(ticketId)
+            userBiometricDataVo =
+                userRepository.getUserBiometricDataByUserIds(userIds)
             jobLockerData =
                 tempJobTicketUserId.filter { it.roleKeys.contains(RoleEnum.JTLOCKER.roleKey) }
             jobColockerData =
@@ -148,6 +154,17 @@ class JobViewModel @Inject constructor(
         }
     }
 
+    /**
+     * 根据用户id获取生物信息
+     */
+    fun getUserBiometricDataByUserIds(userIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userBiometricDataVo =
+                userRepository.getUserBiometricDataByUserIds(userIds)
+            emit(true)
+        }
+    }
+
     /**
      * 获取流程模式列表
      */

+ 19 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopJobViewModel.kt

@@ -9,9 +9,11 @@ import com.grkj.data.model.dos.WorkflowStep
 import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.model.vo.PointManageVo
 import com.grkj.data.model.vo.SopManageVo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.UserManageVo
 import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.ISopRepository
+import com.grkj.data.repository.IUserRepository
 import com.grkj.data.repository.IWorkflowRepository
 import com.grkj.data.repository.IWorkstationRepository
 import com.grkj.ui_base.base.BaseViewModel
@@ -27,8 +29,9 @@ class SopJobViewModel @Inject constructor(
     val workstationRepository: IWorkstationRepository,
     val sopRepository: ISopRepository,
     val jobTicketRepository: IJobTicketRepository,
-    val workflowRepository: IWorkflowRepository
-) : BaseViewModel() {
+    val workflowRepository: IWorkflowRepository,
+    override val userRepository: IUserRepository
+) : BaseViewModel(userRepository) {
     var workstationData: List<IsWorkstation> = listOf()
     var sopData: List<SopManageVo> = listOf()
     var sopPoints: List<PointManageVo> = listOf()
@@ -37,6 +40,7 @@ class SopJobViewModel @Inject constructor(
     var jobTicketData: JobTicketManageVo? = null
     var workflowModes: List<WorkflowMode> = mutableListOf()
     var workflowSteps: List<WorkflowStep> = mutableListOf()
+    var userBiometricDataVo: List<SysBiometricDataVo> = mutableListOf()
 
 
     /**
@@ -146,6 +150,8 @@ class SopJobViewModel @Inject constructor(
             workstationData = workstationRepository.getWorkStationData()
             sopData = sopRepository.getSopDataByWorkstationId(jobTicketData?.workstationId ?: 0)
             val sopJobUsers = jobTicketRepository.getTicketUsersByTicketId(ticketId)
+            userBiometricDataVo =
+                userRepository.getUserBiometricDataByUserIds(sopJobUsers.map { it.userId })
             sopLockerData = sopJobUsers.filter { it.roleKeys.contains(RoleEnum.JTLOCKER.roleKey) }
             sopColockerData =
                 sopJobUsers.filter { it.roleKeys.contains(RoleEnum.JTCOLOCKER.roleKey) }
@@ -154,6 +160,17 @@ class SopJobViewModel @Inject constructor(
         }
     }
 
+    /**
+     * 根据用户id获取生物信息
+     */
+    fun getUserBiometricDataByUserIds(userIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userBiometricDataVo =
+                userRepository.getUserBiometricDataByUserIds(userIds)
+            emit(true)
+        }
+    }
+
     /**
      * 获取流程模式列表
      */

+ 19 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopViewModel.kt

@@ -7,10 +7,12 @@ import com.grkj.data.model.dos.WorkflowMode
 import com.grkj.data.model.dos.WorkflowStep
 import com.grkj.data.model.vo.PointManageVo
 import com.grkj.data.model.vo.SopManageVo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.UserManageVo
 import com.grkj.data.repository.ISopRepository
 import com.grkj.data.repository.IWorkflowRepository
 import com.grkj.data.repository.IWorkstationRepository
+import com.grkj.data.repository.impl.standard.UserRepository
 import com.grkj.ui_base.base.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
 import javax.inject.Inject
@@ -23,14 +25,16 @@ import kotlinx.coroutines.Dispatchers
 class SopViewModel @Inject constructor(
     val workstationRepository: IWorkstationRepository,
     val sopRepository: ISopRepository,
-    val workflowRepository: IWorkflowRepository
-) : BaseViewModel() {
+    val workflowRepository: IWorkflowRepository,
+    override val userRepository: UserRepository
+) : BaseViewModel(userRepository) {
     var workstationData: List<IsWorkstation> = listOf()
     var selectedSopData: SopManageVo? = null
     var selectedSopPointData: List<PointManageVo> = mutableListOf()
     var selectedSopUserData: List<UserManageVo> = mutableListOf()
     var workflowModes: List<WorkflowMode> = mutableListOf()
     var workflowSteps: List<WorkflowStep> = mutableListOf()
+    var userBiometricDataVo: List<SysBiometricDataVo> = mutableListOf()
 
     /**
      * 初始化岗位数据
@@ -72,6 +76,19 @@ class SopViewModel @Inject constructor(
             selectedSopPointData = sopRepository.getSopPointsBySopId(sopId)
             selectedSopUserData = sopRepository.getSopUsersBySopId(sopId)
             selectedSopData = sopRepository.getSopDataBySopId(sopId)
+            userBiometricDataVo =
+                userRepository.getUserBiometricDataByUserIds(selectedSopUserData.map { it.userId })
+            emit(true)
+        }
+    }
+
+    /**
+     * 根据用户id获取生物信息
+     */
+    fun getUserBiometricDataByUserIds(userIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userBiometricDataVo =
+                userRepository.getUserBiometricDataByUserIds(userIds)
             emit(true)
         }
     }

+ 16 - 3
app/src/main/java/com/grkj/iscs/features/main/viewmodel/user_info/UserInfoViewModel.kt

@@ -16,9 +16,9 @@ import javax.inject.Inject
 
 @HiltViewModel
 class UserInfoViewModel @Inject constructor(
-    val userRepository: IUserRepository,
+    override val userRepository: IUserRepository,
     val hardwareRepository: IHardwareRepository
-) : BaseViewModel() {
+) : BaseViewModel(userRepository) {
     /**
      * 生物数据
      */
@@ -133,9 +133,22 @@ class UserInfoViewModel @Inject constructor(
      * 保存用户工卡
      */
     fun saveUserJobCard(rfidNo: String): LiveData<Boolean> {
-        return liveData(Dispatchers.IO){
+        return liveData(Dispatchers.IO) {
             hardwareRepository.updateUserJobCard(rfidNo, MainDomainData.userInfo?.userId!!)
             emit(true)
         }
     }
+
+    /**
+     * 保存用户头像
+     */
+    fun saveUserAvatar(avatarSavePath: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            MainDomainData.userInfo?.let {
+                it.avatar = avatarSavePath
+                userRepository.updateUser(it)
+            }
+            emit(true)
+        }
+    }
 }

+ 5 - 0
app/src/main/res/drawable/icon_avatar.xml

@@ -0,0 +1,5 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#878787" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+      
+    <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
+    
+</vector>

+ 9 - 0
app/src/main/res/drawable/oval_shape.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="#FFF" />
+    <!-- 背景色可随意 -->
+    <stroke
+        android:width="2dp"
+        android:color="@color/black" />
+</shape>

+ 21 - 13
app/src/main/res/layout-land/activity_main.xml

@@ -38,21 +38,29 @@
                     android:textColor="@color/white"
                     android:textSize="@dimen/header_time_text_size" />
 
-                <ImageView
-                    android:layout_width="@dimen/home_user_icon_size"
-                    android:layout_height="@dimen/home_user_icon_size"
-                    android:layout_gravity="center_vertical"
-                    android:layout_marginLeft="@dimen/home_user_icon_margin"
-                    android:src="@mipmap/icon_avatar" />
-
-                <TextView
-                    android:id="@+id/nickname"
+                <LinearLayout
+                    android:id="@+id/user_info_layout"
                     android:layout_width="wrap_content"
                     android:layout_height="match_parent"
-                    android:layout_marginLeft="@dimen/home_nickname_margin"
-                    android:textColor="@color/white"
-                    android:gravity="center"
-                    android:textSize="@dimen/home_nickname_text_size" />
+                    android:orientation="horizontal">
+
+                    <ImageView
+                        android:id="@+id/avatar"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:src="@mipmap/icon_avatar" />
+
+                    <TextView
+                        android:id="@+id/nickname"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_marginLeft="@dimen/home_nickname_margin"
+                        android:gravity="center"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/home_nickname_text_size" />
+                </LinearLayout>
             </LinearLayout>
         </FrameLayout>
 

+ 53 - 0
app/src/main/res/layout-land/dialog_check_face.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <com.google.android.material.card.MaterialCardView
+        android:layout_width="@dimen/login_dialog_width"
+        android:layout_height="@dimen/login_dialog_height"
+        android:gravity="center"
+        app:cardBackgroundColor="@color/dialog_card_login_bg"
+        app:strokeColor="@color/common_transparent">
+
+        <LinearLayout
+            android:id="@+id/ll_easy_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:visibility="visible">
+
+            <ImageView
+                android:id="@+id/iv_icon"
+                android:layout_width="@dimen/dialog_common_root_height_normal"
+                android:layout_height="@dimen/dialog_common_root_height_normal"
+                android:layout_marginBottom="@dimen/common_spacing_small" />
+
+            <TextView
+                android:id="@+id/tv_tip"
+                style="@style/CommonTextView"
+                android:textSize="@dimen/common_text_size_big" />
+        </LinearLayout>
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <TextureView
+                android:id="@+id/preview"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="invisible" />
+        </FrameLayout>
+
+        <ImageView
+            android:id="@+id/close_iv"
+            android:layout_width="@dimen/dialog_check_face_close_size"
+            android:layout_height="@dimen/dialog_check_face_close_size"
+            android:layout_gravity="right"
+            android:padding="@dimen/common_spacing"
+            android:src="@drawable/icon_close"
+            android:tint="@color/white" />
+    </com.google.android.material.card.MaterialCardView>
+</layout>

+ 1 - 0
app/src/main/res/layout-land/fragment_create_job.xml

@@ -423,6 +423,7 @@
                                     android:paddingBottom="@dimen/common_spacing">
 
                                     <ImageView
+                                        android:id="@+id/locker_iv"
                                         android:layout_width="@dimen/icon_member_size"
                                         android:layout_height="@dimen/icon_member_size"
                                         android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout-land/fragment_create_sop.xml

@@ -423,6 +423,7 @@
                                     android:paddingBottom="@dimen/common_spacing">
 
                                     <ImageView
+                                        android:id="@+id/locker_iv"
                                         android:layout_width="@dimen/icon_member_size"
                                         android:layout_height="@dimen/icon_member_size"
                                         android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout-land/fragment_create_sop_job.xml

@@ -408,6 +408,7 @@
                                     android:paddingBottom="@dimen/common_spacing">
 
                                     <ImageView
+                                        android:id="@+id/locker_iv"
                                         android:layout_width="@dimen/icon_member_size"
                                         android:layout_height="@dimen/icon_member_size"
                                         android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout-land/fragment_edit_job.xml

@@ -379,6 +379,7 @@
                                 android:paddingBottom="@dimen/common_spacing">
 
                                 <ImageView
+                                    android:id="@+id/locker_iv"
                                     android:layout_width="@dimen/icon_member_size"
                                     android:layout_height="@dimen/icon_member_size"
                                     android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout-land/fragment_edit_sop.xml

@@ -373,6 +373,7 @@
                                 android:paddingBottom="@dimen/common_spacing">
 
                                 <ImageView
+                                    android:id="@+id/locker_iv"
                                     android:layout_width="@dimen/icon_member_size"
                                     android:layout_height="@dimen/icon_member_size"
                                     android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout-land/fragment_edit_sop_job.xml

@@ -363,6 +363,7 @@
                                 android:paddingBottom="@dimen/common_spacing">
 
                                 <ImageView
+                                    android:id="@+id/locker_iv"
                                     android:layout_width="@dimen/icon_member_size"
                                     android:layout_height="@dimen/icon_member_size"
                                     android:layout_marginTop="@dimen/common_spacing"

+ 377 - 0
app/src/main/res/layout-land/fragment_user_info.xml

@@ -0,0 +1,377 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout 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">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/common_spacing_2x"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing">
+
+            <ImageView
+                android:layout_width="@dimen/title_icon_size"
+                android:layout_height="@dimen/title_icon_size"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/user_info_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/normal_text_size_25"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="5dp"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:drawableLeft="@mipmap/icon_back"
+                android:drawablePadding="@dimen/common_spacing"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <LinearLayout
+                android:id="@+id/show_user_info_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
+
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    android:gravity="center_vertical"
+                    android:orientation="horizontal"
+                    android:paddingHorizontal="100dp">
+
+                    <ImageView
+                        android:id="@+id/avatar"
+                        android:layout_width="@dimen/avatar_size"
+                        android:layout_height="@dimen/avatar_size"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:background="@drawable/oval_shape"
+                        android:clipToOutline="true"
+                        android:scaleType="centerCrop"
+                        android:src="@drawable/icon_avatar"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent" />
+
+                    <TextView
+                        android:id="@+id/username_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:text="@string/user_name"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toBottomOf="@+id/avatar" />
+
+                    <TextView
+                        android:id="@+id/username"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/bg_common_input"
+                        android:enabled="false"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/common_spacing"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/username_tv"
+                        app:layout_constraintTop_toTopOf="@+id/username_tv" />
+
+                    <TextView
+                        android:id="@+id/nickname_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:text="@string/nickname"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="@+id/username_tv"
+                        app:layout_constraintTop_toBottomOf="@+id/username_tv" />
+
+                    <EditText
+                        android:id="@+id/nickname_et"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/bg_common_input"
+                        android:hint="@string/please_input_nickname"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/common_spacing"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/nickname_tv"
+                        app:layout_constraintTop_toTopOf="@+id/nickname_tv" />
+
+
+                    <TextView
+                        android:id="@+id/phone_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:text="@string/phone"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="@+id/nickname_tv"
+                        app:layout_constraintTop_toBottomOf="@+id/nickname_tv" />
+
+                    <EditText
+                        android:id="@+id/phone_et"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/bg_common_input"
+                        android:hint="@string/please_input_phone"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/common_spacing"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/phone_tv"
+                        app:layout_constraintTop_toTopOf="@+id/phone_tv" />
+
+                </androidx.constraintlayout.widget.ConstraintLayout>
+
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:layout_weight="1" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                    android:padding="@dimen/common_spacing">
+
+                    <View
+                        android:layout_width="0dp"
+                        android:layout_height="@dimen/divider_line_space"
+                        android:layout_weight="1" />
+
+                    <TextView
+                        android:id="@+id/confirm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/common_btn_confirm"
+                        android:drawableLeft="@mipmap/icon_confirm"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:text="@string/confirm"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size" />
+
+                    <TextView
+                        android:id="@+id/cancel"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/common_btn_cancel"
+                        android:drawableLeft="@mipmap/icon_cancel"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:text="@string/cancel"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size" />
+                </LinearLayout>
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/face_set_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:visibility="gone">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:orientation="horizontal">
+
+                    <FrameLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_marginHorizontal="@dimen/common_margin_spacing_big"
+                        android:layout_marginTop="@dimen/common_margin_spacing_big"
+                        android:layout_weight="1"
+                        android:background="@drawable/common_card_bg">
+
+                        <TextureView
+                            android:id="@+id/preview"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:visibility="invisible" />
+
+                        <ImageView
+                            android:id="@+id/image"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:scaleType="centerCrop" />
+
+                    </FrameLayout>
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_marginHorizontal="@dimen/common_margin_spacing_big"
+                        android:layout_marginTop="@dimen/common_spacing_2x"
+                        android:layout_weight="1"
+                        android:background="@drawable/common_card_bg"
+                        android:orientation="vertical"
+                        android:padding="@dimen/common_spacing_small">
+
+                        <LinearLayout
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:gravity="center_vertical"
+                            android:orientation="horizontal">
+
+                            <ImageView
+                                android:layout_width="@dimen/common_icon_size"
+                                android:layout_height="@dimen/common_icon_size"
+                                android:background="@mipmap/tip" />
+
+                            <TextView
+                                style="@style/CommonTextView"
+                                android:layout_marginLeft="@dimen/common_spacing_small"
+                                android:text="@string/capture_tip_title"
+                                android:textColor="@color/black"
+                                android:textSize="@dimen/common_text_size_small" />
+                        </LinearLayout>
+
+                        <TextView
+                            style="@style/CommonTextView"
+                            android:layout_marginTop="@dimen/common_spacing"
+                            android:gravity="left"
+                            android:text="@string/capture_tip_content"
+                            android:textColor="@color/black"
+                            android:textSize="@dimen/common_text_size_small" />
+
+                        <TextView
+                            android:id="@+id/tip_tv"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="10dp"
+                            android:background="@color/common_status_red"
+                            android:gravity="center"
+                            android:text="@string/only_one_person_allowed"
+                            android:textColor="@color/white"
+                            android:textSize="@dimen/common_text_size_big"
+                            android:visibility="gone"
+                            tools:text="请保证画面中只有自己" />
+                    </LinearLayout>
+                </LinearLayout>
+
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingVertical="@dimen/common_spacing_2x">
+
+                    <TextView
+                        android:id="@+id/set_avatar_confirm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_confirm"
+                        android:drawableLeft="@mipmap/icon_confirm"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:drawableTint="@color/white"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:paddingVertical="@dimen/common_spacing"
+                        android:text="@string/confirm"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/recapture"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent" />
+
+                    <TextView
+                        android:id="@+id/recapture"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:backgroundTint="@color/dialogxColorBlue"
+                        android:drawableLeft="@drawable/icon_camera"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:drawableTint="@color/white"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:paddingVertical="@dimen/common_spacing"
+                        android:text="@string/recapture"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/set_avatar_cancel"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/set_avatar_confirm"
+                        app:layout_constraintTop_toTopOf="@id/set_avatar_confirm" />
+
+                    <TextView
+                        android:id="@+id/set_avatar_cancel"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:drawableLeft="@drawable/icon_close"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:drawableTint="@color/white"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:paddingVertical="@dimen/common_spacing"
+                        android:text="@string/cancel"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/recapture"
+                        app:layout_constraintTop_toTopOf="@id/recapture" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
+
+            </LinearLayout>
+        </FrameLayout>
+    </LinearLayout>
+</layout>

+ 22 - 13
app/src/main/res/layout/activity_main.xml

@@ -38,21 +38,30 @@
                     android:textColor="@color/white"
                     android:textSize="@dimen/header_time_text_size" />
 
-                <ImageView
-                    android:layout_width="@dimen/home_user_icon_size"
-                    android:layout_height="@dimen/home_user_icon_size"
-                    android:layout_gravity="center_vertical"
-                    android:layout_marginLeft="@dimen/home_user_icon_margin"
-                    android:src="@mipmap/icon_avatar" />
-
-                <TextView
-                    android:id="@+id/nickname"
+                <LinearLayout
+                    android:id="@+id/user_info_layout"
                     android:layout_width="wrap_content"
                     android:layout_height="match_parent"
-                    android:gravity="center"
-                    android:layout_marginLeft="@dimen/home_nickname_margin"
-                    android:textColor="@color/white"
-                    android:textSize="@dimen/home_nickname_text_size" />
+                    android:orientation="horizontal">
+
+                    <ImageView
+                        android:id="@+id/avatar"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:src="@mipmap/icon_avatar" />
+
+                    <TextView
+                        android:id="@+id/nickname"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_marginLeft="@dimen/home_nickname_margin"
+                        android:gravity="center"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/home_nickname_text_size" />
+                </LinearLayout>
+
             </LinearLayout>
 
 

+ 53 - 0
app/src/main/res/layout/dialog_check_face.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <com.google.android.material.card.MaterialCardView
+        android:layout_width="@dimen/login_dialog_width"
+        android:layout_height="@dimen/login_dialog_height"
+        android:gravity="center"
+        app:cardBackgroundColor="@color/dialog_card_login_bg"
+        app:strokeColor="@color/common_transparent">
+
+        <LinearLayout
+            android:id="@+id/ll_easy_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:visibility="visible">
+
+            <ImageView
+                android:id="@+id/iv_icon"
+                android:layout_width="@dimen/dialog_common_root_height_normal"
+                android:layout_height="@dimen/dialog_common_root_height_normal"
+                android:layout_marginBottom="@dimen/common_spacing_small" />
+
+            <TextView
+                android:id="@+id/tv_tip"
+                style="@style/CommonTextView"
+                android:textSize="@dimen/common_text_size_big" />
+        </LinearLayout>
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <TextureView
+                android:id="@+id/preview"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="invisible" />
+        </FrameLayout>
+
+        <ImageView
+            android:id="@+id/close_iv"
+            android:layout_width="@dimen/dialog_check_face_close_size"
+            android:layout_height="@dimen/dialog_check_face_close_size"
+            android:layout_gravity="right"
+            android:padding="@dimen/common_spacing"
+            android:src="@drawable/icon_close"
+            android:tint="@color/white" />
+    </com.google.android.material.card.MaterialCardView>
+</layout>

+ 1 - 0
app/src/main/res/layout/fragment_create_job.xml

@@ -409,6 +409,7 @@
                                 android:paddingBottom="@dimen/common_spacing">
 
                                 <ImageView
+                                    android:id="@+id/locker_iv"
                                     android:layout_width="@dimen/icon_member_size"
                                     android:layout_height="@dimen/icon_member_size"
                                     android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout/fragment_create_sop.xml

@@ -417,6 +417,7 @@
                                 android:paddingBottom="@dimen/common_spacing">
 
                                 <ImageView
+                                    android:id="@+id/locker_iv"
                                     android:layout_width="@dimen/icon_member_size"
                                     android:layout_height="@dimen/icon_member_size"
                                     android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout/fragment_create_sop_job.xml

@@ -393,6 +393,7 @@
                                 android:paddingBottom="@dimen/common_spacing">
 
                                 <ImageView
+                                    android:id="@+id/locker_iv"
                                     android:layout_width="@dimen/icon_member_size"
                                     android:layout_height="@dimen/icon_member_size"
                                     android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout/fragment_edit_job.xml

@@ -372,6 +372,7 @@
                                 android:paddingBottom="@dimen/common_spacing">
 
                                 <ImageView
+                                    android:id="@+id/locker_iv"
                                     android:layout_width="@dimen/icon_member_size"
                                     android:layout_height="@dimen/icon_member_size"
                                     android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout/fragment_edit_sop.xml

@@ -366,6 +366,7 @@
                             android:paddingBottom="@dimen/common_spacing">
 
                             <ImageView
+                                android:id="@+id/locker_iv"
                                 android:layout_width="@dimen/icon_member_size"
                                 android:layout_height="@dimen/icon_member_size"
                                 android:layout_marginTop="@dimen/common_spacing"

+ 1 - 0
app/src/main/res/layout/fragment_edit_sop_job.xml

@@ -355,6 +355,7 @@
                             android:paddingBottom="@dimen/common_spacing">
 
                             <ImageView
+                                android:id="@+id/locker_iv"
                                 android:layout_width="@dimen/icon_member_size"
                                 android:layout_height="@dimen/icon_member_size"
                                 android:layout_marginTop="@dimen/common_spacing"

+ 0 - 1
app/src/main/res/layout/fragment_set_face.xml

@@ -245,7 +245,6 @@
                         app:layout_constraintStart_toEndOf="@+id/recapture"
                         app:layout_constraintTop_toTopOf="@id/recapture" />
                 </androidx.constraintlayout.widget.ConstraintLayout>
-
             </LinearLayout>
         </FrameLayout>
     </LinearLayout>

+ 298 - 128
app/src/main/res/layout/fragment_user_info.xml

@@ -30,8 +30,8 @@
                 android:layout_weight="1"
                 android:text="@string/user_info_title"
                 android:textColor="@color/black"
-                android:textStyle="bold"
-                android:textSize="@dimen/normal_text_size_25" />
+                android:textSize="@dimen/normal_text_size_25"
+                android:textStyle="bold" />
 
             <TextView
                 android:id="@+id/back"
@@ -55,145 +55,315 @@
             android:layout_height="@dimen/divider_line_space"
             android:background="@color/black" />
 
-        <androidx.constraintlayout.widget.ConstraintLayout
+        <FrameLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/common_spacing_2x"
-            android:gravity="center_vertical"
-            android:orientation="horizontal"
-            android:paddingHorizontal="100dp">
+            android:layout_height="match_parent">
 
-            <TextView
-                android:id="@+id/username_tv"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/user_name"
-                android:textColor="@color/black"
-                android:textSize="@dimen/common_text_size"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
+            <LinearLayout
+                android:id="@+id/show_user_info_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
 
-            <TextView
-                android:id="@+id/username"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="@dimen/common_spacing"
-                android:background="@drawable/bg_common_input"
-                android:enabled="false"
-                android:maxLines="1"
-                android:paddingHorizontal="@dimen/common_spacing"
-                android:paddingVertical="2dp"
-                android:singleLine="true"
-                android:textColor="@color/black"
-                android:textSize="@dimen/common_text_size"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@+id/username_tv"
-                app:layout_constraintTop_toTopOf="@+id/username_tv" />
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    android:gravity="center_vertical"
+                    android:orientation="horizontal"
+                    android:paddingHorizontal="100dp">
 
-            <TextView
-                android:id="@+id/nickname_tv"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/common_spacing"
-                android:text="@string/nickname"
-                android:textColor="@color/black"
-                android:textSize="@dimen/common_text_size"
-                app:layout_constraintEnd_toEndOf="@+id/username_tv"
-                app:layout_constraintTop_toBottomOf="@+id/username_tv" />
+                    <ImageView
+                        android:id="@+id/avatar"
+                        android:layout_width="@dimen/avatar_size"
+                        android:layout_height="@dimen/avatar_size"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:background="@drawable/oval_shape"
+                        android:clipToOutline="true"
+                        android:scaleType="centerCrop"
+                        android:src="@drawable/icon_avatar"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent" />
 
-            <EditText
-                android:id="@+id/nickname_et"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="@dimen/common_spacing"
-                android:background="@drawable/bg_common_input"
-                android:hint="@string/please_input_nickname"
-                android:maxLines="1"
-                android:paddingHorizontal="@dimen/common_spacing"
-                android:paddingVertical="2dp"
-                android:singleLine="true"
-                android:textColor="@color/black"
-                android:textSize="@dimen/common_text_size"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@+id/nickname_tv"
-                app:layout_constraintTop_toTopOf="@+id/nickname_tv" />
+                    <TextView
+                        android:id="@+id/username_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:text="@string/user_name"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toBottomOf="@+id/avatar" />
 
+                    <TextView
+                        android:id="@+id/username"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/bg_common_input"
+                        android:enabled="false"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/common_spacing"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/username_tv"
+                        app:layout_constraintTop_toTopOf="@+id/username_tv" />
 
-            <TextView
-                android:id="@+id/phone_tv"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/common_spacing"
-                android:text="@string/phone"
-                android:textColor="@color/black"
-                android:textSize="@dimen/common_text_size"
-                app:layout_constraintEnd_toEndOf="@+id/nickname_tv"
-                app:layout_constraintTop_toBottomOf="@+id/nickname_tv" />
+                    <TextView
+                        android:id="@+id/nickname_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:text="@string/nickname"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="@+id/username_tv"
+                        app:layout_constraintTop_toBottomOf="@+id/username_tv" />
 
-            <EditText
-                android:id="@+id/phone_et"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="@dimen/common_spacing"
-                android:background="@drawable/bg_common_input"
-                android:hint="@string/please_input_phone"
-                android:maxLines="1"
-                android:paddingHorizontal="@dimen/common_spacing"
-                android:paddingVertical="2dp"
-                android:singleLine="true"
-                android:textColor="@color/black"
-                android:textSize="@dimen/common_text_size"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@+id/phone_tv"
-                app:layout_constraintTop_toTopOf="@+id/phone_tv" />
+                    <EditText
+                        android:id="@+id/nickname_et"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/bg_common_input"
+                        android:hint="@string/please_input_nickname"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/common_spacing"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/nickname_tv"
+                        app:layout_constraintTop_toTopOf="@+id/nickname_tv" />
 
-        </androidx.constraintlayout.widget.ConstraintLayout>
 
-        <View
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_weight="1" />
+                    <TextView
+                        android:id="@+id/phone_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:text="@string/phone"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="@+id/nickname_tv"
+                        app:layout_constraintTop_toBottomOf="@+id/nickname_tv" />
 
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:padding="@dimen/common_spacing">
+                    <EditText
+                        android:id="@+id/phone_et"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/bg_common_input"
+                        android:hint="@string/please_input_phone"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/common_spacing"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/phone_tv"
+                        app:layout_constraintTop_toTopOf="@+id/phone_tv" />
 
-            <View
-                android:layout_width="0dp"
-                android:layout_height="@dimen/divider_line_space"
-                android:layout_weight="1" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
 
-            <TextView
-                android:id="@+id/confirm"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="@dimen/common_spacing"
-                android:background="@drawable/common_btn_confirm"
-                android:drawableLeft="@mipmap/icon_confirm"
-                android:drawablePadding="@dimen/common_spacing"
-                android:gravity="center"
-                android:minHeight="@dimen/common_btn_height"
-                android:paddingHorizontal="@dimen/common_spacing_2x"
-                android:text="@string/confirm"
-                android:textColor="@color/white"
-                android:textSize="@dimen/common_btn_text_size" />
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:layout_weight="1" />
 
-            <TextView
-                android:id="@+id/cancel"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="@dimen/common_spacing"
-                android:background="@drawable/common_btn_cancel"
-                android:drawableLeft="@mipmap/icon_cancel"
-                android:drawablePadding="@dimen/common_spacing"
-                android:gravity="center"
-                android:minHeight="@dimen/common_btn_height"
-                android:paddingHorizontal="@dimen/common_spacing_2x"
-                android:text="@string/cancel"
-                android:textColor="@color/white"
-                android:textSize="@dimen/common_btn_text_size" />
-        </LinearLayout>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                    android:padding="@dimen/common_spacing">
+
+                    <View
+                        android:layout_width="0dp"
+                        android:layout_height="@dimen/divider_line_space"
+                        android:layout_weight="1" />
+
+                    <TextView
+                        android:id="@+id/confirm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/common_btn_confirm"
+                        android:drawableLeft="@mipmap/icon_confirm"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:text="@string/confirm"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size" />
+
+                    <TextView
+                        android:id="@+id/cancel"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/common_spacing"
+                        android:background="@drawable/common_btn_cancel"
+                        android:drawableLeft="@mipmap/icon_cancel"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:text="@string/cancel"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size" />
+                </LinearLayout>
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/face_set_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:visibility="gone">
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    android:layout_marginHorizontal="@dimen/common_margin_spacing_big"
+                    android:layout_marginTop="@dimen/common_margin_spacing_big"
+                    android:layout_weight="1"
+                    android:background="@drawable/common_card_bg">
+
+                    <TextureView
+                        android:id="@+id/preview"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:visibility="invisible" />
+
+                    <ImageView
+                        android:id="@+id/image"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:scaleType="centerCrop" />
+
+                </FrameLayout>
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp"
+                    android:layout_marginHorizontal="@dimen/common_margin_spacing_big"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    android:layout_weight="1"
+                    android:background="@drawable/common_card_bg"
+                    android:orientation="vertical"
+                    android:padding="@dimen/common_spacing_small">
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_vertical"
+                        android:orientation="horizontal">
+
+                        <ImageView
+                            android:layout_width="@dimen/common_icon_size"
+                            android:layout_height="@dimen/common_icon_size"
+                            android:background="@mipmap/tip" />
+
+                        <TextView
+                            style="@style/CommonTextView"
+                            android:layout_marginLeft="@dimen/common_spacing_small"
+                            android:text="@string/capture_tip_title"
+                            android:textColor="@color/black"
+                            android:textSize="@dimen/common_text_size_small" />
+                    </LinearLayout>
+
+                    <TextView
+                        style="@style/CommonTextView"
+                        android:layout_marginTop="@dimen/common_spacing"
+                        android:gravity="left"
+                        android:text="@string/capture_tip_content"
+                        android:textColor="@color/black"
+                        android:textSize="@dimen/common_text_size_small" />
+
+                    <TextView
+                        android:id="@+id/tip_tv"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="10dp"
+                        android:background="@color/common_status_red"
+                        android:gravity="center"
+                        android:text="@string/only_one_person_allowed"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_text_size_big"
+                        android:visibility="gone"
+                        tools:text="请保证画面中只有自己" />
+                </LinearLayout>
+
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingVertical="@dimen/common_spacing_2x">
+
+                    <TextView
+                        android:id="@+id/set_avatar_confirm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_confirm"
+                        android:drawableLeft="@mipmap/icon_confirm"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:drawableTint="@color/white"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:paddingVertical="@dimen/common_spacing"
+                        android:text="@string/confirm"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/recapture"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent" />
+
+                    <TextView
+                        android:id="@+id/recapture"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:backgroundTint="@color/dialogxColorBlue"
+                        android:drawableLeft="@drawable/icon_camera"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:drawableTint="@color/white"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:paddingVertical="@dimen/common_spacing"
+                        android:text="@string/recapture"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/set_avatar_cancel"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/set_avatar_confirm"
+                        app:layout_constraintTop_toTopOf="@id/set_avatar_confirm" />
+
+                    <TextView
+                        android:id="@+id/set_avatar_cancel"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:drawableLeft="@drawable/icon_close"
+                        android:drawablePadding="@dimen/common_spacing"
+                        android:drawableTint="@color/white"
+                        android:paddingHorizontal="@dimen/common_spacing_2x"
+                        android:paddingVertical="@dimen/common_spacing"
+                        android:text="@string/cancel"
+                        android:textColor="@color/white"
+                        android:textSize="@dimen/common_btn_text_size"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/recapture"
+                        app:layout_constraintTop_toTopOf="@id/recapture" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
+            </LinearLayout>
+        </FrameLayout>
     </LinearLayout>
 </layout>

+ 1 - 0
app/src/main/res/values-en/strings.xml

@@ -442,5 +442,6 @@
     <string name="please_select_exception_description">please select exception description</string>
     <string name="process_application_tv">Process application</string>
     <string name="please_select_process_application">please select process application</string>
+    <string name="verify_failed">Verify failed</string>
 
 </resources>

+ 2 - 0
app/src/main/res/values-land/dimens.xml

@@ -99,4 +99,6 @@
     <dimen name="job_execute_tab_icon_size">51dp</dimen>
     <dimen name="slots_exception_report_width">850dp</dimen>
     <dimen name="slots_exception_report_dialog_content_height">340dp</dimen>
+    <dimen name="avatar_size">150dp</dimen>
+    <dimen name="dialog_check_face_close_size">80dp</dimen>
 </resources>

+ 1 - 0
app/src/main/res/values-zh/strings.xml

@@ -442,5 +442,6 @@
     <string name="please_select_exception_description">请选择异常描述</string>
     <string name="process_application_tv">处理申请</string>
     <string name="please_select_process_application">请选择处理申请</string>
+    <string name="verify_failed">验证失败</string>
 
 </resources>

+ 2 - 0
app/src/main/res/values/dimens.xml

@@ -99,4 +99,6 @@
     <dimen name="job_execute_tab_icon_size">30dp</dimen>
     <dimen name="slots_exception_report_width">500dp</dimen>
     <dimen name="slots_exception_report_dialog_content_height">200dp</dimen>
+    <dimen name="avatar_size">80dp</dimen>
+    <dimen name="dialog_check_face_close_size">60dp</dimen>
 </resources>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -445,5 +445,6 @@
     <string name="please_select_exception_description">请选择异常描述</string>
     <string name="process_application_tv">处理申请</string>
     <string name="please_select_process_application">请选择处理申请</string>
+    <string name="verify_failed">验证失败</string>
 
 </resources>

+ 1 - 1
data/src/main/java/com/grkj/data/dao/HardwareDao.kt

@@ -379,7 +379,7 @@ interface HardwareDao {
      * 根据用户id获取工卡数据
      */
     @Query("select * from is_job_card where user_id = :userId")
-    fun getIsJobCardByUserId(userId: Long): List<IsJobCard>
+    fun getIsJobCardByUserId(userId: Long): MutableList<IsJobCard>
 
     /**
      * 根据锁id获取锁数据

+ 1 - 0
data/src/main/java/com/grkj/data/dao/IsSopDao.kt

@@ -80,6 +80,7 @@ interface IsSopDao {
               su.user_id                         AS userId,
               su.nick_name                       AS nickName,
               su.user_name                       AS userName,
+              su.avatar,
               -- 聚合所有卡号(如果没卡则结果里 cardCodes 会是 NULL 或空)
               GROUP_CONCAT(DISTINCT ijc.card_code)       AS cardCodes,
         

+ 1 - 0
data/src/main/java/com/grkj/data/dao/JobTicketDao.kt

@@ -150,6 +150,7 @@ interface JobTicketDao {
               su.user_id                         AS userId,
               su.nick_name                       AS nickName,
               su.user_name                       AS userName,
+              su.avatar,
               -- 聚合所有卡号(如果没卡则结果里 cardCodes 会是 NULL 或空)
               GROUP_CONCAT(DISTINCT ijc.card_code)       AS cardCodes,
         

+ 14 - 0
data/src/main/java/com/grkj/data/dao/UserDao.kt

@@ -64,6 +64,7 @@ interface UserDao {
       su.user_id                         AS userId,
       su.nick_name                       AS nickName,
       su.user_name                       AS userName,
+              su.avatar,
 
       -- 聚合所有卡号(如果没卡则结果里 cardCodes 会是 NULL 或空)
       GROUP_CONCAT(DISTINCT ijc.card_code)       AS cardCodes,
@@ -134,6 +135,7 @@ interface UserDao {
           su.user_id                         AS userId,
           su.nick_name                       AS nickName,
           su.user_name                       AS userName,
+              su.avatar,
     
           -- 聚合所有卡号(如果没卡则结果里 cardCodes 会是 NULL 或空)
           GROUP_CONCAT(DISTINCT ijc.card_code)       AS cardCodes,
@@ -227,4 +229,16 @@ interface UserDao {
      */
     @Query("delete from sys_user_characteristic where type = 2 and user_id = :userId")
     fun deleteFaceDataByUserId(userId: Long)
+
+    /**
+     * 获取用户生物信息
+     */
+    @Query("select * from sys_user_characteristic where user_id = :userId")
+    fun getUserBiometricData(userId: Long): MutableList<SysBiometricDataVo>
+
+    /**
+     * 根据用户id获取用户生物信息
+     */
+    @Query("select * from sys_user_characteristic where user_id in (:userIds)")
+    fun getUserBiometricDataByUserIds(userIds: List<Long>): List<SysBiometricDataVo>
 }

+ 5 - 0
data/src/main/java/com/grkj/data/data/CommonConstants.kt

@@ -28,4 +28,9 @@ object CommonConstants {
      * 人脸文件夹
      */
     const val FACE_FOLDER = "face"
+
+    /**
+     * 头像文件夹
+     */
+    const val AVATAR_FOLDER = "avatar"
 }

+ 9 - 1
data/src/main/java/com/grkj/data/data/MainDomainData.kt

@@ -2,6 +2,7 @@ package com.grkj.data.data
 
 import com.grkj.data.model.dos.IsJobCard
 import com.grkj.data.model.dos.SysUserDo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.sik.sikcore.SIKCore
 
 /**
@@ -14,10 +15,15 @@ object MainDomainData {
     @Volatile
     var userInfo: SysUserDo? = null
 
+    /**
+     * 用户生物信息
+     */
+    var userBiometricDataVo: MutableList<SysBiometricDataVo> = mutableListOf()
+
     /**
      * 用户卡片
      */
-    var userCardList: List<IsJobCard> = mutableListOf()
+    var userCardList: MutableList<IsJobCard> = mutableListOf()
 
     /**
      * 角色关键字
@@ -38,5 +44,7 @@ object MainDomainData {
         userInfo = null
         roleKeys = null
         permissions.clear()
+        userCardList.clear()
+        userBiometricDataVo.clear()
     }
 }

+ 10 - 0
data/src/main/java/com/grkj/data/model/dos/WorkflowStep.kt

@@ -177,4 +177,14 @@ data class WorkflowStep(
         }
         return false
     }
+
+    /**
+     * 是否需要验证人脸
+     */
+    @Ignore
+    fun needCheckFace(): Boolean {
+        return confirmRoleCode != null && confirmUser != null && MainDomainData.roleKeys?.contains(
+            confirmRoleCode!!
+        ) == true && MainDomainData.userInfo?.userId == confirmUser
+    }
 }

+ 1 - 0
data/src/main/java/com/grkj/data/model/vo/UserManageVo.kt

@@ -9,6 +9,7 @@ class UserManageVo {
     var userId: Long = 0
     var nickName: String = ""
     var userName: String = ""
+    var avatar: String? = null
     var cardCodes: List<String?> = listOf()
     var roleIds: List<Long?> = listOf()
     var roleNames: List<String?> = listOf()

+ 20 - 0
data/src/main/java/com/grkj/data/repository/IUserRepository.kt

@@ -23,16 +23,31 @@ interface IUserRepository {
      */
     fun loginWithCard(cardNo: String): Boolean
 
+    /**
+     * 检查卡片
+     */
+    fun checkCard(cardNo: String): Boolean
+
     /**
      * 指纹登录
      */
     fun loginWithFingerprint(fingerprint: String): Boolean
 
+    /**
+     * 检查指纹
+     */
+    fun checkFingerprint(fingerprint: String): Boolean
+
     /**
      * 人脸登录
      */
     fun loginWithFace(face: String): Boolean
 
+    /**
+     * 检查人脸
+     */
+    fun checkFace(face: String): Boolean
+
     /**
      * 退出登录
      */
@@ -116,4 +131,9 @@ interface IUserRepository {
      * 根据用户id删除人脸数据
      */
     fun deleteFaceDataByUserId(userId: Long)
+
+    /**
+     * 根据用户id获取用户生物数据
+     */
+    fun getUserBiometricDataByUserIds(userIds: List<Long>): List<SysBiometricDataVo>
 }

+ 12 - 0
data/src/main/java/com/grkj/data/repository/impl/network/NetworkUserRepository.kt

@@ -29,14 +29,26 @@ class NetworkUserRepository @Inject constructor()  : BaseRepository(), IUserRepo
         TODO("Not yet implemented")
     }
 
+    override fun checkCard(cardNo: String): Boolean {
+        TODO("Not yet implemented")
+    }
+
     override fun loginWithFingerprint(fingerprint: String): Boolean {
         TODO("Not yet implemented")
     }
 
+    override fun checkFingerprint(fingerprint: String): Boolean {
+        TODO("Not yet implemented")
+    }
+
     override fun loginWithFace(face: String): Boolean {
         TODO("Not yet implemented")
     }
 
+    override fun checkFace(face: String): Boolean {
+        TODO("Not yet implemented")
+    }
+
     override fun logout(): Boolean {
         TODO("Not yet implemented")
     }

+ 80 - 0
data/src/main/java/com/grkj/data/repository/impl/standard/UserRepository.kt

@@ -48,6 +48,8 @@ class UserRepository @Inject constructor(
             MainDomainData.userInfo = sysUserDo
             val userCardList = hardwareDao.getIsJobCardByUserId(sysUserDo.userId)
             val roleDatas = roleDao.getRoleDataByUserId(sysUserDo.userId)
+            val userBiometricDataVo = userDao.getUserBiometricData(sysUserDo.userId)
+            MainDomainData.userBiometricDataVo = userBiometricDataVo
             MainDomainData.userCardList = userCardList
             MainDomainData.roleKeys = roleDatas.joinToString(",") { it.roleKey }
             MainDomainData.permissions =
@@ -68,6 +70,8 @@ class UserRepository @Inject constructor(
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
                 val roleDatas = roleDao.getRoleDataByUserId(sysUserDo.userId)
+                val userBiometricDataVo = userDao.getUserBiometricData(sysUserDo.userId)
+                MainDomainData.userBiometricDataVo = userBiometricDataVo
                 MainDomainData.roleKeys = roleDatas.joinToString(",") { it.roleKey }
                 MainDomainData.permissions =
                     sysMenuDao.getPermissionsByRoleIds(roleDatas.map { it.roleId })
@@ -79,6 +83,14 @@ class UserRepository @Inject constructor(
         } ?: false
     }
 
+    override fun checkCard(cardNo: String): Boolean {
+        val userId = hardwareDao.getUserIdByCardNfc(cardNo)?.toString()
+        return userId?.let {
+            val sysUserDo = userDao.getUserInfoByUserId(userId)
+            sysUserDo != null
+        } == true
+    }
+
     override fun loginWithFingerprint(fingerprint: String): Boolean {
         val fingerprintDataList = userDao.getFingerprintData()
         if (fingerprintDataList.isEmpty()) {
@@ -101,6 +113,8 @@ class UserRepository @Inject constructor(
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
                 val roleDatas = roleDao.getRoleDataByUserId(sysUserDo.userId)
+                val userBiometricDataVo = userDao.getUserBiometricData(sysUserDo.userId)
+                MainDomainData.userBiometricDataVo = userBiometricDataVo
                 MainDomainData.roleKeys = roleDatas.joinToString(",") { it.roleKey }
                 MainDomainData.permissions =
                     sysMenuDao.getPermissionsByRoleIds(roleDatas.map { it.roleId })
@@ -117,6 +131,36 @@ class UserRepository @Inject constructor(
         return hasFingerprint
     }
 
+    override fun checkFingerprint(fingerprint: String): Boolean {
+        val fingerprintDataList = userDao.getFingerprintData()
+        if (fingerprintDataList.isEmpty()) {
+            return false
+        }
+        var hasFingerprint = false
+        var userId: String? = null
+        for (fingerprintData in fingerprintDataList) {
+            if (fingerprintData.content.isNotEmpty()) {
+                val fileData = fingerprintData.content.file().readText()
+                if (BiometricVerifier.verifyFingerprint(fingerprint, fileData)) {
+                    hasFingerprint = true
+                    userId = fingerprintData.userId.toString()
+                    break
+                }
+            }
+        }
+        if (userId != null) {
+            val sysUserDo = userDao.getUserInfoByUserId(userId)
+            hasFingerprint = if (sysUserDo != null) {
+                true
+            } else {
+                false
+            }
+        } else {
+            hasFingerprint = false
+        }
+        return hasFingerprint
+    }
+
     override fun loginWithFace(face: String): Boolean {
         val faceDataList = userDao.getFaceData()
         if (faceDataList.isEmpty()) {
@@ -139,6 +183,8 @@ class UserRepository @Inject constructor(
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
                 val roleDatas = roleDao.getRoleDataByUserId(sysUserDo.userId)
+                val userBiometricDataVo = userDao.getUserBiometricData(sysUserDo.userId)
+                MainDomainData.userBiometricDataVo = userBiometricDataVo
                 MainDomainData.roleKeys = roleDatas.joinToString(",") { it.roleKey }
                 MainDomainData.permissions =
                     sysMenuDao.getPermissionsByRoleIds(roleDatas.map { it.roleId })
@@ -155,6 +201,36 @@ class UserRepository @Inject constructor(
         return hasFace
     }
 
+    override fun checkFace(face: String): Boolean {
+        val faceDataList = userDao.getFaceData()
+        if (faceDataList.isEmpty()) {
+            return false
+        }
+        var hasFace = false
+        var userId: String? = null
+        for (faceData in faceDataList) {
+            if (faceData.content.isNotEmpty()) {
+                val fileData = faceData.content.file().readText()
+                if (BiometricVerifier.verifyFaceArcSoft(face, fileData)) {
+                    hasFace = true
+                    userId = faceData.userId.toString()
+                    break
+                }
+            }
+        }
+        if (userId != null) {
+            val sysUserDo = userDao.getUserInfoByUserId(userId)
+            hasFace = if (sysUserDo != null) {
+                true
+            } else {
+                false
+            }
+        } else {
+            hasFace = false
+        }
+        return hasFace
+    }
+
     override fun logout(): Boolean {
         MainDomainData.clear()
         return true
@@ -259,4 +335,8 @@ class UserRepository @Inject constructor(
     override fun deleteFaceDataByUserId(userId: Long) {
         userDao.deleteFaceDataByUserId(userId)
     }
+
+    override fun getUserBiometricDataByUserIds(userIds: List<Long>): List<SysBiometricDataVo> {
+        return userDao.getUserBiometricDataByUserIds(userIds)
+    }
 }

+ 1 - 0
shared/build.gradle.kts

@@ -50,6 +50,7 @@ dependencies {
     implementation(libs.androidx.core.ktx)
     api(libs.sik.extension.core)
     api(libs.sik.extension.encrypt)
+    api(libs.sik.extension.image)
     api("org.mindrot:jbcrypt:0.4")
     implementation("com.machinezoo.sourceafis:sourceafis:3.15.0")
     implementation("com.google.dagger:hilt-android:2.56.2")

+ 8 - 1
shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt

@@ -5,6 +5,7 @@ import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
 import android.graphics.Point
+import android.graphics.Rect
 import android.hardware.Camera
 import android.util.Base64
 import android.util.DisplayMetrics
@@ -26,6 +27,8 @@ import com.grkj.shared.config.Constants
 import com.grkj.shared.utils.face.arcsoft.CameraHelper
 import com.grkj.shared.utils.face.arcsoft.CameraListener
 import com.grkj.shared.utils.face.arcsoft.NV21ToBitmap
+import com.sik.sikimage.CropImageUtils
+import com.sik.sikimage.ImageUtils
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
@@ -171,8 +174,12 @@ object ArcSoftUtil {
                     previewSize!!.width,
                     previewSize!!.height
                 )
+                val faceRect = faceInfoList[0].rect
                 logger.debug("人脸检测结果-识别结果 : ${bitmap == null} - $faceInfoList")
-                callBack(bitmap, faceInfoList.size, true)
+                bitmap?.let {
+                    val faceBitmap = CropImageUtils.cropBitmap(bitmap, faceRect)
+                    callBack(faceBitmap, faceInfoList.size, true)
+                } ?: callBack(null, faceInfoList.size, true)
             }
 
             override fun onCameraClosed() {

+ 6 - 2
ui-base/src/main/java/com/grkj/ui_base/base/BaseFragment.kt

@@ -12,6 +12,7 @@ import androidx.annotation.LayoutRes
 import androidx.databinding.DataBindingUtil
 import androidx.databinding.ViewDataBinding
 import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
 import androidx.navigation.NavController
 import androidx.navigation.fragment.findNavController
 import com.grkj.shared.model.EventBean
@@ -20,6 +21,7 @@ import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.utils.event.LoadingEvent
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikandroid.permission.PermissionUtils
+import kotlinx.coroutines.launch
 import me.jessyan.autosize.internal.CustomAdapt
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
@@ -58,7 +60,9 @@ abstract class BaseFragment<V : ViewDataBinding> : Fragment(), CustomAdapt {
             initView()
             initData()
             initListeners()
-            initObservers()
+            lifecycleScope.launch {
+                initObservers()
+            }
             isFirstLoad = false
             // 点击空白处隐藏键盘
             setupHideKeyboardOnTouch(view)
@@ -159,5 +163,5 @@ abstract class BaseFragment<V : ViewDataBinding> : Fragment(), CustomAdapt {
     protected open fun initListeners() {}
 
     /** 可选:初始化 LiveData/事件观察 */
-    protected open fun initObservers() {}
+    protected open suspend fun initObservers() {}
 }

+ 56 - 2
ui-base/src/main/java/com/grkj/ui_base/base/BaseViewModel.kt

@@ -1,12 +1,66 @@
 package com.grkj.ui_base.base
 
+import androidx.lifecycle.LiveData
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.liveData
+import androidx.lifecycle.viewModelScope
+import com.grkj.data.repository.IUserRepository
+import com.grkj.ui_base.utils.event.UiEvent
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
+import javax.inject.Inject
 
 /**
  * 界面模型基类
  */
-open class BaseViewModel : ViewModel() {
+open class BaseViewModel @Inject constructor(
+    open val userRepository: IUserRepository? = null
+) : ViewModel() {
     protected val logger: Logger = LoggerFactory.getLogger(this::class.java)
-}
+
+    // ① 发给 UI 的一次性事件
+    protected val _uiEvents = Channel<UiEvent>(Channel.BUFFERED)
+    val uiEvents = _uiEvents.receiveAsFlow()
+
+    // ② VM 等待 UI 回传结果的通道
+    protected val _paramResponse = Channel<Any>(Channel.BUFFERED)
+
+    // UI 调用:把用户点“确定”输入回传给 VM
+    fun onParamProvided(input: Any) {
+        viewModelScope.launch {
+            _paramResponse.send(input)
+        }
+    }
+
+    /**
+     * 检查人脸
+     */
+    fun checkFace(faceB64: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(userRepository?.checkFace(faceB64) == true)
+        }
+    }
+
+    /**
+     * 检查指纹
+     */
+    fun checkFinger(fingerB64: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(userRepository?.checkFingerprint(fingerB64) == true)
+        }
+    }
+
+    /**
+     * 检查卡片
+     */
+    fun checkCard(cardNo: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(userRepository?.checkCard(cardNo) == true)
+        }
+    }
+}
+

+ 21 - 0
ui-base/src/main/java/com/grkj/ui_base/utils/event/UiEvent.kt

@@ -0,0 +1,21 @@
+package com.grkj.ui_base.utils.event
+
+/**
+ * ui事件
+ */
+sealed class UiEvent {
+    /**
+     * 人脸检测
+     */
+    object FaceCheck : UiEvent()
+
+    /**
+     * 指纹检测
+     */
+    object FingerprintCheck : UiEvent()
+
+    /**
+     * 卡片检测
+     */
+    object CardCheck : UiEvent()
+}

+ 1 - 0
ui-base/src/main/res/values-en/strings.xml

@@ -370,4 +370,5 @@
     <string name="please_do_uncolock">Please have co-locker remove co-lock</string>
     <string name="can_not_remove_current_locker">can not remove current locker</string>
     <string name="fingerprint_delete_selected_confirm_tip">Confirm to delete selected fingerprint?</string>
+    <string name="doing_checking">Verifying......</string>
 </resources>

+ 1 - 0
ui-base/src/main/res/values-zh/strings.xml

@@ -370,4 +370,5 @@
     <string name="please_do_uncolock">请共锁人解除共锁</string>
     <string name="can_not_remove_current_locker">无法移除当前上锁人</string>
     <string name="fingerprint_delete_selected_confirm_tip">确定要删除选中的指纹吗?</string>
+    <string name="doing_checking">正在验证......</string>
 </resources>

+ 1 - 0
ui-base/src/main/res/values/strings.xml

@@ -370,4 +370,5 @@
     <string name="please_do_uncolock">请共锁人解除共锁</string>
     <string name="can_not_remove_current_locker">无法移除当前上锁人</string>
     <string name="fingerprint_delete_selected_confirm_tip">确定要删除选中的指纹吗?</string>
+    <string name="doing_checking">正在验证......</string>
 </resources>