Преглед изворни кода

refactor(更新)
- 个人信息的人脸、指纹、卡片界面完成,待测试

周文健 пре 10 месеци
родитељ
комит
bbb1f24b17
48 измењених фајлова са 1587 додато и 46 уклоњено
  1. 2 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/AddUserDialog.kt
  2. 2 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UpdateUserDialog.kt
  3. 36 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/user_info/AddFingerprintDialog.kt
  4. 136 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFaceFragment.kt
  5. 141 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFingerprintFragment.kt
  6. 84 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetJobCardFragment.kt
  7. 9 9
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/UserInfoHomeFragment.kt
  8. 100 1
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/user_info/UserInfoViewModel.kt
  9. 7 0
      app/src/main/res/drawable/circle_image_bg.xml
  10. 5 0
      app/src/main/res/drawable/icon_camera.xml
  11. 1 1
      app/src/main/res/layout/dialog_add_card.xml
  12. 41 0
      app/src/main/res/layout/dialog_add_fingerprint.xml
  13. 1 1
      app/src/main/res/layout/dialog_add_key.xml
  14. 1 1
      app/src/main/res/layout/dialog_add_lock.xml
  15. 1 1
      app/src/main/res/layout/dialog_add_rfid_token.xml
  16. 1 1
      app/src/main/res/layout/dialog_add_role.xml
  17. 1 1
      app/src/main/res/layout/dialog_add_user.xml
  18. 1 1
      app/src/main/res/layout/dialog_update_card.xml
  19. 1 1
      app/src/main/res/layout/dialog_update_key.xml
  20. 1 1
      app/src/main/res/layout/dialog_update_lock.xml
  21. 1 1
      app/src/main/res/layout/dialog_update_rfid_token.xml
  22. 1 1
      app/src/main/res/layout/dialog_update_role.xml
  23. 1 1
      app/src/main/res/layout/dialog_update_user.xml
  24. 252 0
      app/src/main/res/layout/fragment_set_face.xml
  25. 131 0
      app/src/main/res/layout/fragment_set_fingerprint.xml
  26. 150 0
      app/src/main/res/layout/fragment_set_job_card.xml
  27. 33 0
      app/src/main/res/layout/item_set_fingerprint.xml
  28. 24 0
      app/src/main/res/navigation/nav_user_info.xml
  29. 15 0
      app/src/main/res/values-en/strings.xml
  30. 3 1
      app/src/main/res/values-land/dimens.xml
  31. 15 0
      app/src/main/res/values-zh/strings.xml
  32. 3 1
      app/src/main/res/values/dimens.xml
  33. 15 0
      app/src/main/res/values/strings.xml
  34. 10 2
      data/src/main/java/com/grkj/data/dao/HardwareDao.kt
  35. 36 3
      data/src/main/java/com/grkj/data/dao/UserDao.kt
  36. 11 0
      data/src/main/java/com/grkj/data/data/CommonConstants.kt
  37. 1 0
      data/src/main/java/com/grkj/data/data/MainDomainData.kt
  38. 1 1
      data/src/main/java/com/grkj/data/model/dos/SysUserCharacteristicDo.kt
  39. 13 0
      data/src/main/java/com/grkj/data/model/vo/SysBiometricDataVo.kt
  40. 10 1
      data/src/main/java/com/grkj/data/repository/IHardwareRepository.kt
  41. 27 0
      data/src/main/java/com/grkj/data/repository/IUserRepository.kt
  42. 31 0
      data/src/main/java/com/grkj/data/repository/impl/HardwareRepository.kt
  43. 36 6
      data/src/main/java/com/grkj/data/repository/impl/UserRepository.kt
  44. 166 0
      data/src/main/java/com/grkj/data/utils/FileStorageUtils.kt
  45. 26 9
      shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt
  46. 1 0
      ui-base/src/main/res/values-en/strings.xml
  47. 1 0
      ui-base/src/main/res/values-zh/strings.xml
  48. 1 0
      ui-base/src/main/res/values/strings.xml

+ 2 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/AddUserDialog.kt

@@ -38,6 +38,8 @@ class AddUserDialog(
         binding.workstationTv.isVisible = ISCSConfig.isWorkstationOn
         binding.workstationNameTv.isVisible = ISCSConfig.isWorkstationOn
 
+        binding.cardcodeEt.isEnabled = false
+
         // 角色多选
         binding.roleTv.setOnClickListener {
             TextDropDownDialog.showMulti(roleData, binding.roleTv) { list ->

+ 2 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UpdateUserDialog.kt

@@ -40,6 +40,8 @@ class UpdateUserDialog(
         binding.workstationTv.isVisible = ISCSConfig.isWorkstationOn
         binding.workstationNameTv.isVisible = ISCSConfig.isWorkstationOn
 
+        binding.cardcodeEt.isEnabled = false
+
         // 预填数据
         binding.nicknameEt.setText(userVo.nickName)
         binding.cardcodeEt.setText(userVo.cardCodes.joinToString(","))

+ 36 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/user_info/AddFingerprintDialog.kt

@@ -0,0 +1,36 @@
+package com.grkj.iscs.features.main.dialog.user_info
+
+import android.view.Gravity
+import android.view.View
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogAddFingerprintBinding
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 添加指纹弹窗
+ */
+class AddFingerprintDialog(val onCancel: () -> Unit) :
+    OnBindView<CustomDialog>(R.layout.dialog_add_fingerprint) {
+    private lateinit var binding: DialogAddFingerprintBinding
+    override fun onBind(dialog: CustomDialog, p1: View) {
+        binding = DialogAddFingerprintBinding.bind(p1)
+        binding.cancel.setDebouncedClickListener {
+            onCancel()
+        }
+    }
+
+    companion object {
+        /**
+         * 显示弹窗
+         */
+        @JvmStatic
+        fun show(onCancel: () -> Unit): CustomDialog {
+            return CustomDialog.show(
+                AddFingerprintDialog(onCancel),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

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

@@ -0,0 +1,136 @@
+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 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.FragmentSetFaceBinding
+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.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
+
+/**
+ * 设置人脸
+ */
+@AndroidEntryPoint
+class SetFaceFragment : BaseFragment<FragmentSetFaceBinding>() {
+    private val viewModel: UserInfoViewModel by viewModels()
+    private var mCapturedBitmap: Bitmap? = null
+    private var isFaceChecking: Boolean = false
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_set_face
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            releaseFace()
+        }
+        binding.setOrResetFace.setDebouncedClickListener {
+            binding.faceViewLayout.isVisible = false
+            binding.faceSetLayout.isVisible = true
+            isFaceChecking = false
+            startFace()
+        }
+        binding.cancel.setDebouncedClickListener {
+            releaseFace()
+            isFaceChecking = false
+            binding.faceViewLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+        }
+        binding.confirm.setDebouncedClickListener {
+            releaseFace()
+            isFaceChecking = false
+            binding.faceViewLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            val saveFileName =
+                "${MainDomainData.userInfo?.userId}_face_${TimeUtils.nowString("yyyyMMddHHmmss")}"
+            FileStorageUtils.writeText(
+                CommonConstants.FACE_FOLDER,
+                saveFileName,
+                ImageConvertUtils.bitmapToBase64(mCapturedBitmap).toString()
+            )
+            val savePath = FileStorageUtils.getFilePath(
+                CommonConstants.FINGERPRINT_FOLDER,
+                saveFileName
+            )
+            viewModel.saveUserFace(savePath).observe(this) {
+                getData()
+            }
+        }
+        binding.recapture.setDebouncedClickListener {
+            isFaceChecking = false
+            binding.image.isVisible = false
+            binding.preview.isVisible = true
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getData()
+    }
+
+    private fun getData() {
+        viewModel.getFaceData().observe(this) {
+            binding.faceViewLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            binding.faceNotSetIv.isVisible = viewModel.sysBiometricDataVo.isEmpty()
+            binding.faceSetIv.isVisible = viewModel.sysBiometricDataVo.isNotEmpty()
+            if (viewModel.sysBiometricDataVo.isEmpty()) {
+                binding.setOrResetFace.text = getString(R.string.set_data_tv)
+                binding.faceSetTipTv.text = getString(R.string.face_not_set_tip)
+            }else{
+                val faceBase64 = viewModel.sysBiometricDataVo[0].content.file().readText()
+                val faceBitmap = ImageConvertUtils.base64ToBitmap(faceBase64)
+                binding.faceSetIv.setImageBitmap(faceBitmap)
+                binding.setOrResetFace.text = getString(R.string.reset_data_tv)
+                binding.faceSetTipTv.text = getString(R.string.face_set_tip)
+            }
+        }
+    }
+
+    private fun startFace() {
+        ArcSoftUtil.initEngine(requireContext())
+        ArcSoftUtil.initCamera(
+            requireContext(),
+            requireActivity().windowManager,
+            binding.preview
+        ) { bitmap, faceSize, alive ->
+            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)
+                return@initCamera
+            }
+            if (alive == false) {
+                binding.tipTv.text =
+                    getString(R.string.real_person_verification_required)
+                return@initCamera
+            }
+            if (isFaceChecking) {
+                return@initCamera
+            }
+            isFaceChecking = true
+            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() {
+        mCapturedBitmap = null
+        ArcSoftUtil.stop()
+    }
+
+}

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

@@ -0,0 +1,141 @@
+package com.grkj.iscs.features.main.fragment.user_info
+
+import android.graphics.Bitmap
+import androidx.fragment.app.viewModels
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.data.CommonConstants
+import com.grkj.data.data.MainDomainData
+import com.grkj.data.model.vo.SysBiometricDataVo
+import com.grkj.data.utils.FileStorageUtils
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentSetFingerprintBinding
+import com.grkj.iscs.databinding.ItemSetFingerprintBinding
+import com.grkj.iscs.features.main.dialog.user_info.AddFingerprintDialog
+import com.grkj.iscs.features.main.viewmodel.user_info.UserInfoViewModel
+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.fingerprint.FingerprintUtil
+import com.sik.sikcore.date.TimeUtils
+import com.sik.sikcore.extension.deleteIfExists
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 设置指纹
+ */
+@AndroidEntryPoint
+class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
+    private val viewModel: UserInfoViewModel by viewModels()
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_set_fingerprint
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.add.setDebouncedClickListener {
+            AddFingerprintDialog.show {
+                FingerprintUtil.stop()
+                FingerprintUtil.unInit()
+            }.apply {
+                FingerprintUtil.init(requireContext())
+                FingerprintUtil.start()
+                FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {
+                    override fun onScan(bitmap: Bitmap) {
+                        FingerprintUtil.stop()
+                        FingerprintUtil.unInit()
+                        val saveFileName =
+                            "${MainDomainData.userInfo?.userId}_fingerprint_${TimeUtils.nowString("yyyyMMddHHmmss")}"
+                        FileStorageUtils.writeText(
+                            CommonConstants.FINGERPRINT_FOLDER,
+                            saveFileName,
+                            ImageConvertUtils.bitmapToBase64(bitmap).toString()
+                        )
+                        val savePath = FileStorageUtils.getFilePath(
+                            CommonConstants.FINGERPRINT_FOLDER,
+                            saveFileName
+                        )
+                        viewModel.saveUserFingerprint(savePath)
+                            .observe(this@SetFingerprintFragment) {
+                                getData()
+                                TipDialog.showSuccess(getString(com.grkj.ui_base.R.string.save_success))
+                                this@apply.dismiss()
+                            }
+                    }
+                })
+            }
+        }
+        binding.delete.setDebouncedClickListener {
+            TipDialog.showInfo(
+                CommonUtils.getStr(
+                    com.grkj.ui_base.R.string.fingerprint_delete_selected_confirm_tip
+                ).toString(),
+                countDownTime = 10,
+                onConfirmClick = {
+                    viewModel.sysBiometricDataVo.filter { it.selected }.forEach { item ->
+                        if (item.content.isNotEmpty()) {
+                            item.content.deleteIfExists()
+                        }
+                    }
+                    viewModel.deleteFingerprintByIds(viewModel.sysBiometricDataVo.map { it.recordId })
+                        .observe(this) {
+                            if (it) {
+                                TipDialog.showSuccess(getString(R.string.delete_success))
+                            }
+                        }
+                }
+            )
+        }
+        binding.listRv.linear().setup {
+            addType<SysBiometricDataVo>(R.layout.item_set_fingerprint)
+            onBind {
+                val itemBinding = getBinding<ItemSetFingerprintBinding>()
+                val item = getModel<SysBiometricDataVo>()
+                itemBinding.fingerprintCode.text =
+                    getString(R.string.fingerprint_code_str, modelPosition)
+                itemBinding.delete.setDebouncedClickListener {
+                    TipDialog.showInfo(
+                        CommonUtils.getStr(
+                            com.grkj.ui_base.R.string.fingerprint_delete_confirm_tip,
+                            getString(R.string.fingerprint_code_str, modelPosition)
+                        ).toString(),
+                        countDownTime = 10,
+                        onConfirmClick = {
+                            if (item.content.isNotEmpty()) {
+                                item.content.deleteIfExists()
+                            }
+                            viewModel.deleteFingerprintByIds(listOf(item.recordId))
+                                .observe(this@SetFingerprintFragment) {
+                                    if (it) {
+                                        TipDialog.showSuccess(getString(R.string.delete_success))
+                                    }
+                                }
+                        }
+                    )
+                }
+                itemBinding.select.setOnCheckedChangeListener(null)
+                itemBinding.select.isChecked = item.isSelected
+                itemBinding.select.setOnCheckedChangeListener { _, checked ->
+                    item.isSelected = checked
+                }
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getData()
+    }
+
+    private fun getData() {
+        viewModel.getFingerprintData().observe(this) {
+            binding.listRv.models = viewModel.sysBiometricDataVo
+        }
+    }
+}

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

@@ -0,0 +1,84 @@
+package com.grkj.iscs.features.main.fragment.user_info
+
+import androidx.core.view.isVisible
+import androidx.fragment.app.viewModels
+import com.grkj.data.data.EventConstants
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentSetJobCardBinding
+import com.grkj.iscs.features.main.viewmodel.user_info.UserInfoViewModel
+import com.grkj.shared.model.EventBean
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.event.RFIDCardReadEvent
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 设置工卡
+ */
+@AndroidEntryPoint
+class SetJobCardFragment : BaseFragment<FragmentSetJobCardBinding>() {
+    private val viewModel: UserInfoViewModel by viewModels()
+    private var canHandlerCardNo: Boolean = false
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_set_job_card
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.setOrResetJobCard.setDebouncedClickListener {
+            binding.jobCardSetLayout.isVisible = true
+            binding.jobCardViewLayout.isVisible = false
+            canHandlerCardNo = true
+        }
+        binding.cancel.setDebouncedClickListener {
+            binding.jobCardSetLayout.isVisible = false
+            binding.jobCardViewLayout.isVisible = true
+            canHandlerCardNo = false
+        }
+    }
+
+    override fun onEvent(event: EventBean<Any>) {
+        super.onEvent(event)
+        when (event.code) {
+            EventConstants.EVENT_RFID_CARD_READ -> {
+                if (!canHandlerCardNo) {
+                    return
+                }
+                canHandlerCardNo = false
+                viewModel.saveUserJobCard((event.data as RFIDCardReadEvent).rfidNo)
+                    .observe(this@SetJobCardFragment) {
+                        TipDialog.show(
+                            msg = getString(com.grkj.ui_base.R.string.save_success),
+                            onConfirmClick = {
+                                getData()
+                            },
+                            onCancelClick = {
+                                getData()
+                            })
+                    }
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getData()
+    }
+
+    private fun getData() {
+        viewModel.getJobCardData().observe(this) {
+            binding.jobCardSetIv.isVisible == viewModel.jobCardDataVo.isNotEmpty()
+            binding.jobCardNotSetIv.isVisible == viewModel.jobCardDataVo.isEmpty()
+            if (viewModel.jobCardDataVo.isEmpty()) {
+                binding.jobCardSetTipTv.text = getString(R.string.job_card_not_set_tip)
+                binding.setOrResetJobCard.text = getString(R.string.set_data_tv)
+            } else {
+                binding.jobCardSetTipTv.text = getString(R.string.job_card_set_tip)
+                binding.setOrResetJobCard.text = getString(R.string.reset_data_tv)
+            }
+        }
+    }
+}

+ 9 - 9
app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/UserInfoHomeFragment.kt

@@ -53,12 +53,12 @@ class UserInfoHomeFragment : BaseFragment<FragmentUserInfoHomeBinding>() {
             RoleFunctionalPermissionsEnum.CARD_SETTING.description,
             RoleFunctionalPermissionsEnum.CARD_SETTING.functionalPermission
         ),
-        MenuItemEntity(
-            5,
-            R.mipmap.icon_data_manage_menu_point_manage,
-            RoleFunctionalPermissionsEnum.MORE_SETTING.description,
-            RoleFunctionalPermissionsEnum.MORE_SETTING.functionalPermission
-        ),
+//        MenuItemEntity(
+//            5,
+//            R.mipmap.icon_data_manage_menu_point_manage,
+//            RoleFunctionalPermissionsEnum.MORE_SETTING.description,
+//            RoleFunctionalPermissionsEnum.MORE_SETTING.functionalPermission
+//        ),
         MenuItemEntity(
             6,
             R.mipmap.icon_data_manage_menu_point_manage,
@@ -115,15 +115,15 @@ class UserInfoHomeFragment : BaseFragment<FragmentUserInfoHomeBinding>() {
             }
 
             2 -> {
-
+                navController.navigate(R.id.action_userInfoHomeFragment_to_setFingerprintFragment)
             }
 
             3 -> {
-
+                navController.navigate(R.id.action_userInfoHomeFragment_to_setFaceFragment)
             }
 
             4 -> {
-
+                navController.navigate(R.id.action_userInfoHomeFragment_to_setJobCardFragment)
             }
 
             5 -> {

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

@@ -3,6 +3,10 @@ package com.grkj.iscs.features.main.viewmodel.user_info
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
 import com.grkj.data.data.MainDomainData
+import com.grkj.data.model.dos.IsJobCard
+import com.grkj.data.model.dos.SysUserCharacteristicDo
+import com.grkj.data.model.vo.SysBiometricDataVo
+import com.grkj.data.repository.IHardwareRepository
 import com.grkj.data.repository.IUserRepository
 import com.grkj.shared.utils.BCryptUtils
 import com.grkj.ui_base.base.BaseViewModel
@@ -11,7 +15,20 @@ import kotlinx.coroutines.Dispatchers
 import javax.inject.Inject
 
 @HiltViewModel
-class UserInfoViewModel @Inject constructor(val userRepository: IUserRepository) : BaseViewModel() {
+class UserInfoViewModel @Inject constructor(
+    val userRepository: IUserRepository,
+    val hardwareRepository: IHardwareRepository
+) : BaseViewModel() {
+    /**
+     * 生物数据
+     */
+    var sysBiometricDataVo: MutableList<SysBiometricDataVo> = mutableListOf()
+
+    /**
+     * 工卡数据
+     */
+    var jobCardDataVo: List<IsJobCard> = mutableListOf()
+
     /**
      * 保存用户信息
      */
@@ -38,4 +55,86 @@ class UserInfoViewModel @Inject constructor(val userRepository: IUserRepository)
             } ?: emit(false)
         }
     }
+
+    /**
+     * 获取指纹数据
+     */
+    fun getFingerprintData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            sysBiometricDataVo =
+                userRepository.getFingerprintDataByUserId(MainDomainData.userInfo?.userId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取人脸数据
+     */
+    fun getFaceData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            sysBiometricDataVo =
+                userRepository.getFaceDataByUserId(MainDomainData.userInfo?.userId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 根据id删除指纹数据
+     */
+    fun deleteFingerprintByIds(fingerprintIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userRepository.deleteFingerprintByIds(fingerprintIds)
+            emit(true)
+        }
+    }
+
+    /**
+     * 保存用户指纹
+     */
+    fun saveUserFingerprint(savePath: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val sysUserCharacteristicDo = SysUserCharacteristicDo()
+            sysUserCharacteristicDo.userId = MainDomainData.userInfo?.userId!!
+            sysUserCharacteristicDo.content = savePath
+            sysUserCharacteristicDo.type = "1"
+            userRepository.saveUserCharacteristic(sysUserCharacteristicDo)
+            emit(true)
+        }
+    }
+
+    /**
+     * 保存人脸数据
+     */
+    fun saveUserFace(savePath: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userRepository.deleteFaceDataByUserId(MainDomainData.userInfo?.userId!!)
+            val sysUserCharacteristicDo = SysUserCharacteristicDo()
+            sysUserCharacteristicDo.userId = MainDomainData.userInfo?.userId!!
+            sysUserCharacteristicDo.content = savePath
+            sysUserCharacteristicDo.type = "2"
+            userRepository.saveUserCharacteristic(sysUserCharacteristicDo)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取工卡数据
+     */
+    fun getJobCardData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            jobCardDataVo =
+                hardwareRepository.getJobCardDataByUserId(MainDomainData.userInfo?.userId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 保存用户工卡
+     */
+    fun saveUserJobCard(rfidNo: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO){
+            hardwareRepository.updateUserJobCard(rfidNo, MainDomainData.userInfo?.userId!!)
+            emit(true)
+        }
+    }
 }

+ 7 - 0
app/src/main/res/drawable/circle_image_bg.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <stroke
+        android:width="2dp"
+        android:color="@color/black"/>
+</shape>

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

@@ -0,0 +1,5 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+      
+    <path android:fillColor="@android:color/white" android:pathData="M14.12,4l1.83,2L20,6v12L4,18L4,6h4.05l1.83,-2h4.24M15,2L9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2zM12,9c1.65,0 3,1.35 3,3s-1.35,3 -3,3 -3,-1.35 -3,-3 1.35,-3 3,-3m0,-2c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5z"/>
+    
+</vector>

+ 1 - 1
app/src/main/res/layout/dialog_add_card.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 41 - 0
app/src/main/res/layout/dialog_add_fingerprint.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_medium"
+        android:background="@drawable/common_card_bg"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="@dimen/login_method_item_layout_width"
+            android:layout_height="@dimen/login_method_item_layout_width"
+            android:layout_gravity="center"
+            android:src="@mipmap/icon_login_menu_fingerprint"
+            android:tint="@color/black" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:text="@string/fingerprint_scan_tip"
+            android:textColor="@color/black"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/common_spacing_2x"
+            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" />
+    </LinearLayout>
+</layout>

+ 1 - 1
app/src/main/res/layout/dialog_add_key.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_add_lock.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_add_rfid_token.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_add_role.xml

@@ -3,7 +3,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_add_user.xml

@@ -5,7 +5,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_update_card.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_update_key.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_update_lock.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_update_rfid_token.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_update_role.xml

@@ -3,7 +3,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

+ 1 - 1
app/src/main/res/layout/dialog_update_user.xml

@@ -4,7 +4,7 @@
 
     <LinearLayout
         android:layout_width="@dimen/dialog_common_root_width"
-        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:layout_height="@dimen/dialog_common_root_height_large"
         android:background="@drawable/common_card_bg"
         android:orientation="vertical">
 

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

@@ -0,0 +1,252 @@
+<?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="@drawable/icon_login_menu_face"
+                android:tint="@color/black" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/set_face_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/face_view_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/face_not_set_iv"
+                    android:layout_width="@dimen/login_method_item_iv_size"
+                    android:layout_height="@dimen/login_method_item_iv_size"
+                    android:src="@drawable/icon_add_box" />
+
+                <ImageView
+                    android:id="@+id/face_set_iv"
+                    android:layout_width="300dp"
+                    android:layout_height="400dp"
+                    android:scaleType="fitXY"
+                    android:src="@drawable/icon_add_box"
+                    android:visibility="gone" />
+
+                <TextView
+                    android:id="@+id/face_set_tip_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    android:textColor="@color/black"
+                    android:textSize="@dimen/common_text_size"
+                    tools:text="您尚未设置人脸数据" />
+
+                <TextView
+                    android:id="@+id/set_or_reset_face"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    android:background="@drawable/common_btn"
+                    android:paddingHorizontal="@dimen/common_spacing_2x"
+                    android:paddingVertical="@dimen/common_spacing"
+                    android:textColor="@color/black"
+                    android:textSize="@dimen/common_text_size"
+                    tools:text="点击设置" />
+            </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/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/cancel"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/confirm"
+                        app:layout_constraintTop_toTopOf="@id/confirm" />
+
+                    <TextView
+                        android:id="@+id/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>

+ 131 - 0
app/src/main/res/layout/fragment_set_fingerprint.xml

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <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_login_menu_fingerprint"
+                android:tint="@color/black" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/set_fingerprint_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" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing">
+
+
+            <TextView
+                android:id="@+id/add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/insert"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <TextView
+                android:id="@+id/delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/delete"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:background="@drawable/common_card_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <CheckBox
+                android:id="@+id/select_all"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center"
+                android:layout_margin="@dimen/common_spacing" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/fingerprint_code"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/operation"
+                android:textSize="@dimen/common_text_size" />
+        </LinearLayout>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/list_rv"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginBottom="@dimen/common_spacing"
+            android:background="@drawable/common_card_bg" />
+    </LinearLayout>
+</layout>

+ 150 - 0
app/src/main/res/layout/fragment_set_job_card.xml

@@ -0,0 +1,150 @@
+<?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="@drawable/icon_login_menu_card"
+                android:tint="@color/black" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/set_job_card_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/job_card_view_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/job_card_not_set_iv"
+                    android:layout_width="@dimen/login_method_item_iv_size"
+                    android:layout_height="@dimen/login_method_item_iv_size"
+                    android:src="@drawable/icon_add_box" />
+
+                <ImageView
+                    android:id="@+id/job_card_set_iv"
+                    android:layout_width="200dp"
+                    android:layout_height="200dp"
+                    android:background="@drawable/circle_image_bg"
+                    android:scaleType="center"
+                    android:src="@drawable/icon_login_menu_card"
+                    android:tint="@color/black"
+                    android:visibility="gone" />
+
+                <TextView
+                    android:id="@+id/job_card_set_tip_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    android:textColor="@color/black"
+                    android:textSize="@dimen/common_text_size"
+                    tools:text="您尚未设置人脸数据" />
+
+                <TextView
+                    android:id="@+id/set_or_reset_job_card"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    android:background="@drawable/common_btn"
+                    android:paddingHorizontal="@dimen/common_spacing_2x"
+                    android:paddingVertical="@dimen/common_spacing"
+                    android:textColor="@color/black"
+                    android:textSize="@dimen/common_text_size"
+                    tools:text="点击设置" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/job_card_set_layout"
+                android:layout_width="@dimen/dialog_common_root_width"
+                android:layout_height="@dimen/dialog_common_root_height_medium"
+                android:layout_gravity="center"
+                android:background="@drawable/common_card_bg"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:visibility="gone">
+
+                <ImageView
+                    android:layout_width="@dimen/login_method_item_layout_width"
+                    android:layout_height="@dimen/login_method_item_layout_width"
+                    android:layout_gravity="center"
+                    android:src="@mipmap/icon_login_menu_fingerprint"
+                    android:tint="@color/black" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing"
+                    android:text="@string/job_card_scan_tip"
+                    android:textColor="@color/black"
+                    android:textSize="@dimen/common_text_size" />
+
+                <TextView
+                    android:id="@+id/cancel"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/common_spacing_2x"
+                    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" />
+            </LinearLayout>
+        </FrameLayout>
+    </LinearLayout>
+</layout>

+ 33 - 0
app/src/main/res/layout/item_set_fingerprint.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:divider="@drawable/divider_table"
+        android:showDividers="middle">
+
+        <CheckBox
+            android:id="@+id/select"
+            android:layout_width="30dp"
+            android:layout_height="30dp"
+            android:layout_gravity="center"
+            android:layout_margin="@dimen/common_spacing" />
+
+        <TextView
+            android:id="@+id/fingerprint_code"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/delete"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

+ 24 - 0
app/src/main/res/navigation/nav_user_info.xml

@@ -14,6 +14,18 @@
         <action
             android:id="@+id/action_userInfoHomeFragment_to_resetPasswordFragment"
             app:destination="@id/resetPasswordFragment" />
+        <action
+            android:id="@+id/action_userInfoHomeFragment_to_setJobCardFragment"
+            app:destination="@id/setJobCardFragment" />
+        <action
+            android:id="@+id/action_userInfoHomeFragment_to_resetPasswordFragment2"
+            app:destination="@id/resetPasswordFragment" />
+        <action
+            android:id="@+id/action_userInfoHomeFragment_to_setFaceFragment"
+            app:destination="@id/setFaceFragment" />
+        <action
+            android:id="@+id/action_userInfoHomeFragment_to_setFingerprintFragment"
+            app:destination="@id/setFingerprintFragment" />
     </fragment>
     <fragment
         android:id="@+id/userInfoFragment"
@@ -23,4 +35,16 @@
         android:id="@+id/resetPasswordFragment"
         android:name="com.grkj.iscs.features.main.fragment.user_info.ResetPasswordFragment"
         android:label="ResetPasswordFragment" />
+    <fragment
+        android:id="@+id/setFingerprintFragment"
+        android:name="com.grkj.iscs.features.main.fragment.user_info.SetFingerprintFragment"
+        android:label="SetFingerprintFragment" />
+    <fragment
+        android:id="@+id/setFaceFragment"
+        android:name="com.grkj.iscs.features.main.fragment.user_info.SetFaceFragment"
+        android:label="SetFaceFragment" />
+    <fragment
+        android:id="@+id/setJobCardFragment"
+        android:name="com.grkj.iscs.features.main.fragment.user_info.SetJobCardFragment"
+        android:label="SetJobCardFragment" />
 </navigation>

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

@@ -381,5 +381,20 @@
     <string name="has_lock_not_unlocked">Your point %s are not unlocked</string>
     <string name="please_do_colock">Please have co-locker perform co-lock</string>
     <string name="please_done_operation">Please done operation %s first</string>
+    <string name="set_fingerprint_title">Set fingerprint</string>
+    <string name="fingerprint_code">Fingerprint code</string>
+    <string name="fingerprint_code_str">Fingerprint_%d</string>
+    <string name="delete_success">Delete success</string>
+    <string name="set_face_title">Set face</string>
+    <string name="only_one_person_allowed">Only one person allowed</string>
+    <string name="real_person_verification_required">Real-person verification required</string>
+    <string name="set_data_tv">Click to Set</string>
+    <string name="face_not_set_tip">You have not set up facial data yet</string>
+    <string name="reset_data_tv">Click reset</string>
+    <string name="face_set_tip">You have set up facial data</string>
+    <string name="job_card_not_set_tip">You have not set up a work card yet</string>
+    <string name="job_card_set_tip">You have set up work card data</string>
+    <string name="set_job_card_title">Set work card</string>
+    <string name="job_card_scan_tip">Please read the card on the card reader</string>
 
 </resources>

+ 3 - 1
app/src/main/res/values-land/dimens.xml

@@ -26,7 +26,9 @@
     <dimen name="home_bottom_nav_text_size">11sp</dimen>
     <dimen name="home_bottom_nav_icon_size">23dp</dimen>
     <dimen name="dialog_common_root_width">680dp</dimen>
-    <dimen name="dialog_common_root_height_big">1020dp</dimen>
+    <dimen name="dialog_common_root_height_large">1020dp</dimen>
+    <dimen name="dialog_common_root_height_big">500dp</dimen>
+    <dimen name="dialog_common_root_height_medium">400dp</dimen>
     <dimen name="dialog_common_root_height_small">340dp</dimen>
     <dimen name="dialog_common_root_height_normal">510dp</dimen>
     <dimen name="title_normal_padding_horizontal">10dp</dimen>

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

@@ -381,5 +381,20 @@
     <string name="has_lock_not_unlocked">您还有%s点位未解锁</string>
     <string name="please_do_colock">请共锁人能完成共锁</string>
     <string name="please_done_operation">请先完成%s</string>
+    <string name="set_fingerprint_title">设置指纹</string>
+    <string name="fingerprint_code">指纹编号</string>
+    <string name="fingerprint_code_str">指纹_%d</string>
+    <string name="delete_success">删除成功</string>
+    <string name="set_face_title">设置人脸</string>
+    <string name="only_one_person_allowed">请保持单人入镜</string>
+    <string name="real_person_verification_required">请保持真人操作</string>
+    <string name="set_data_tv">点击设置</string>
+    <string name="face_not_set_tip">您尚未设置人脸数据</string>
+    <string name="reset_data_tv">点击重设</string>
+    <string name="face_set_tip">您已设置了人脸数据</string>
+    <string name="job_card_not_set_tip">您尚未设置工卡</string>
+    <string name="job_card_set_tip">您已设置了工卡数据</string>
+    <string name="set_job_card_title">设置工卡</string>
+    <string name="job_card_scan_tip">请在读卡器上读卡</string>
 
 </resources>

+ 3 - 1
app/src/main/res/values/dimens.xml

@@ -26,7 +26,9 @@
     <dimen name="home_bottom_nav_text_size">20sp</dimen>
     <dimen name="home_bottom_nav_icon_size">40dp</dimen>
     <dimen name="dialog_common_root_width">400dp</dimen>
-    <dimen name="dialog_common_root_height_big">600dp</dimen>
+    <dimen name="dialog_common_root_height_large">600dp</dimen>
+    <dimen name="dialog_common_root_height_big">500dp</dimen>
+    <dimen name="dialog_common_root_height_medium">400dp</dimen>
     <dimen name="dialog_common_root_height_small">200dp</dimen>
     <dimen name="dialog_common_root_height_normal">300dp</dimen>
     <dimen name="title_normal_padding_horizontal">10dp</dimen>

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

@@ -384,5 +384,20 @@
     <string name="has_lock_not_unlocked">您还有%s点位未解锁</string>
     <string name="please_do_colock">请共锁人能完成共锁</string>
     <string name="please_done_operation">请先完成%s</string>
+    <string name="set_fingerprint_title">设置指纹</string>
+    <string name="fingerprint_code">指纹编号</string>
+    <string name="fingerprint_code_str">指纹_%d</string>
+    <string name="delete_success">删除成功</string>
+    <string name="set_face_title">设置人脸</string>
+    <string name="only_one_person_allowed">请保持单人入镜</string>
+    <string name="real_person_verification_required">请保持真人操作</string>
+    <string name="set_data_tv">点击设置</string>
+    <string name="face_not_set_tip">您尚未设置人脸数据</string>
+    <string name="reset_data_tv">点击重设</string>
+    <string name="face_set_tip">您已设置了人脸数据</string>
+    <string name="job_card_not_set_tip">您尚未设置工卡</string>
+    <string name="job_card_set_tip">您已设置了工卡数据</string>
+    <string name="set_job_card_title">设置工卡</string>
+    <string name="job_card_scan_tip">请在读卡器上读卡</string>
 
 </resources>

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

@@ -52,7 +52,8 @@ interface HardwareDao {
     /**
      * 更新挂锁取出
      */
-    @Query("""
+    @Query(
+        """
         UPDATE is_job_ticket_lock
         SET
           lock_status = '1',
@@ -65,7 +66,8 @@ interface HardwareDao {
             AND lock_id IS NULL
           LIMIT 1
         );
-    """)
+    """
+    )
     fun updateLockTake(
         ticketId: Long?, lockId: Long, updateTime: String = TimeUtils.nowString(
             TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT
@@ -401,4 +403,10 @@ interface HardwareDao {
      */
     @Query("select user_id from is_job_card ijc where ijc.card_nfc = :cardRfid")
     fun getUserIdByCardRfid(cardRfid: String): Long?
+
+    /**
+     * 根据rfid获取卡片数据
+     */
+    @Query("select * from is_job_card where card_nfc = :rfidNo")
+    fun getCardDataByRfid(rfidNo: String): IsJobCard?
 }

+ 36 - 3
data/src/main/java/com/grkj/data/dao/UserDao.kt

@@ -9,6 +9,7 @@ import androidx.room.Update
 import com.grkj.data.converters.Converters
 import com.grkj.data.model.dos.SysUserCharacteristicDo
 import com.grkj.data.model.dos.SysUserDo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.SysUserVo
 import com.grkj.data.model.vo.UserManageVo
 
@@ -36,12 +37,24 @@ interface UserDao {
     @Query("select * from sys_user_characteristic where type = 1")
     fun getFingerprintData(): List<SysUserCharacteristicDo>
 
+    /**
+     * 根据用户id获取指纹数据
+     */
+    @Query("select * from sys_user_characteristic where type = 1 and user_id = :userId")
+    fun getFingerprintDataByUserId(userId: Long): List<SysBiometricDataVo>
+
     /**
      * 获取所有人脸数据
      */
     @Query("select * from sys_user_characteristic where type = 2")
     fun getFaceData(): List<SysUserCharacteristicDo>
 
+    /**
+     * 根据用户id获取人脸数据
+     */
+    @Query("select * from sys_user_characteristic where type = 2 and user_id = :userId")
+    fun getFaceDataByUserId(userId: Long): List<SysBiometricDataVo>
+
     /**
      * 获取用户管理列表(带动态过滤 & 角色聚合)
      */
@@ -177,7 +190,8 @@ interface UserDao {
     /**
      * 获取角色带权限
      */
-    @Query("""
+    @Query(
+        """
         SELECT
           su.*,
           t.roleKeys
@@ -192,6 +206,25 @@ interface UserDao {
           GROUP BY sur.user_id
         ) t ON su.user_id = t.user_id
         WHERE su.del_flag = 0;
-    """)
+    """
+    )
     fun getAllUsersWithRole(): List<SysUserVo>
-}
+
+    /**
+     * 根据id删除指纹
+     */
+    @Query("delete from sys_user_characteristic where record_id in (:fingerprintIds)")
+    fun deleteFingerprintByIds(fingerprintIds: List<Long>)
+
+    /**
+     * 保存用户生物数据
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun saveUserCharacteristic(saveUserCharacteristic: SysUserCharacteristicDo)
+
+    /**
+     * 根据用户id删除人脸数据
+     */
+    @Query("delete from sys_user_characteristic where type = 2 and user_id = :userId")
+    fun deleteFaceDataByUserId(userId: Long)
+}

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

@@ -13,8 +13,19 @@ object CommonConstants {
      * http前缀
      */
     const val PREFIX_HTTP = "http://"
+
     /**
      * https前缀
      */
     const val PREFIX_HTTPS = "https://"
+
+    /**
+     * 指纹文件夹
+     */
+    const val FINGERPRINT_FOLDER = "fingerprint"
+
+    /**
+     * 人脸文件夹
+     */
+    const val FACE_FOLDER = "face"
 }

+ 1 - 0
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.sik.sikcore.SIKCore
 
 /**
  * 登录之后的数据存储

+ 1 - 1
data/src/main/java/com/grkj/data/model/dos/SysUserCharacteristicDo.kt

@@ -15,7 +15,7 @@ import androidx.room.PrimaryKey
     tableName = "sys_user_characteristic",
     indices = [Index("type", name = "type_seq", orders = [Index.Order.ASC])]
 )
-class SysUserCharacteristicDo : BaseBean() {
+open class SysUserCharacteristicDo : BaseBean() {
     @PrimaryKey(autoGenerate = true)
     @ColumnInfo("record_id")
     var recordId: Long = 0

+ 13 - 0
data/src/main/java/com/grkj/data/model/vo/SysBiometricDataVo.kt

@@ -0,0 +1,13 @@
+package com.grkj.data.model.vo
+
+import androidx.room.Ignore
+import com.grkj.data.model.dos.SysUserCharacteristicDo
+
+/**
+ * 生物数据
+ */
+class SysBiometricDataVo : SysUserCharacteristicDo() {
+
+    @Ignore
+    var selected: Boolean = false
+}

+ 10 - 1
data/src/main/java/com/grkj/data/repository/IHardwareRepository.kt

@@ -14,7 +14,6 @@ import com.grkj.data.model.res.KeyInfoRes
 import com.grkj.data.model.res.KeyPageRes
 import com.grkj.data.model.res.LockInfoRes
 import com.grkj.data.model.res.LockPageRes
-import com.grkj.data.model.vo.AddUserDataVo
 import com.grkj.data.model.vo.CardManageFilterVo
 import com.grkj.data.model.vo.KeyManageFilterVo
 import com.grkj.data.model.vo.LockManageFilterVo
@@ -230,4 +229,14 @@ interface IHardwareRepository {
      * 获取默认挂锁名称统计
      */
     fun getDefaultLockNameCount(): Int
+
+    /**
+     * 根据用户id获取工卡数据
+     */
+    fun getJobCardDataByUserId(userId: Long?): List<IsJobCard>
+
+    /**
+     * 更新用户工卡
+     */
+    fun updateUserJobCard(rfidNo: String, userId: Long)
 }

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

@@ -1,7 +1,9 @@
 package com.grkj.data.repository
 
+import com.grkj.data.model.dos.SysUserCharacteristicDo
 import com.grkj.data.model.dos.SysUserDo
 import com.grkj.data.model.vo.AddUserDataVo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.SysUserVo
 import com.grkj.data.model.vo.UpdateUserDataVo
 import com.grkj.data.model.vo.UserManageVo
@@ -89,4 +91,29 @@ interface IUserRepository {
      * 获取用户角色带权限
      */
     fun getAllUsersWithRole(): List<SysUserVo>
+
+    /**
+     * 根据用户id获取生物数据
+     */
+    fun getFingerprintDataByUserId(userId: Long?): MutableList<SysBiometricDataVo>
+
+    /**
+     * 根据用户id获取人脸数据
+     */
+    fun getFaceDataByUserId(userId: Long?): MutableList<SysBiometricDataVo>
+
+    /**
+     * 根据id删除指纹
+     */
+    fun deleteFingerprintByIds(fingerprintIds: List<Long>)
+
+    /**
+     * 保存用户数据
+     */
+    fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo)
+
+    /**
+     * 根据用户id删除人脸数据
+     */
+    fun deleteFaceDataByUserId(userId: Long)
 }

+ 31 - 0
data/src/main/java/com/grkj/data/repository/impl/HardwareRepository.kt

@@ -354,4 +354,35 @@ class HardwareRepository @Inject constructor(
     override fun getDefaultLockNameCount(): Int {
         return hardwareDao.getDefaultLockNameCount()
     }
+
+    override fun getJobCardDataByUserId(userId: Long?): List<IsJobCard> {
+        if (userId == null) {
+            return mutableListOf()
+        }
+        return hardwareDao.getIsJobCardByUserId(userId)
+    }
+
+    override fun updateUserJobCard(rfidNo: String, userId: Long) {
+        val userJobCardData = hardwareDao.getIsJobCardByUserId(userId)
+        if (userJobCardData.any { it.cardNfc == rfidNo }) {
+            return
+        }
+        var jobCardData = hardwareDao.getCardDataByRfid(rfidNo)
+        userJobCardData.forEach {
+            it.userId = null
+            hardwareDao.updateCardInfo(it)
+        }
+        if (jobCardData != null) {
+            jobCardData.userId = userId
+        } else {
+            jobCardData = IsJobCard()
+            var defaultCardCodeSize = hardwareDao.getDefaultCardNameCount()
+            jobCardData.cardCode = "CARD_${defaultCardCodeSize + 1}"
+            jobCardData.userId = userId
+            jobCardData.cardNfc = rfidNo
+            jobCardData.exStatus =
+                CommonDictDataEnum.JOB_CARD_STATUS.commonDictRes.find { it.dictLabel == "正常" }?.dictValue
+        }
+        hardwareDao.updateCardInfo(jobCardData)
+    }
 }

+ 36 - 6
data/src/main/java/com/grkj/data/repository/impl/UserRepository.kt

@@ -7,9 +7,11 @@ import com.grkj.data.dao.UserDao
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.enums.RoleEnum
 import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.data.model.dos.SysUserCharacteristicDo
 import com.grkj.data.model.dos.SysUserDo
 import com.grkj.data.model.dos.SysUserRole
 import com.grkj.data.model.vo.AddUserDataVo
+import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.model.vo.SysUserVo
 import com.grkj.data.model.vo.UpdateUserDataVo
 import com.grkj.data.model.vo.UserManageVo
@@ -18,6 +20,8 @@ import com.grkj.data.repository.BaseRepository
 import com.grkj.data.repository.IUserRepository
 import com.grkj.shared.utils.BCryptUtils
 import com.grkj.shared.utils.BiometricVerifier
+import com.sik.sikcore.extension.file
+import com.sik.sikcore.file.FileUtils
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -84,8 +88,9 @@ class UserRepository @Inject constructor(
         var hasFingerprint = false
         var userId: String? = null
         for (fingerprintData in fingerprintDataList) {
-            if (fingerprintData.content != null) {
-                if (BiometricVerifier.verifyFingerprint(fingerprint, fingerprintData.content!!)) {
+            if (fingerprintData.content.isNotEmpty()) {
+                val fileData = fingerprintData.content.file().readText()
+                if (BiometricVerifier.verifyFingerprint(fingerprint, fileData)) {
                     hasFingerprint = true
                     userId = fingerprintData.userId.toString()
                     break
@@ -96,7 +101,6 @@ class UserRepository @Inject constructor(
             val sysUserDo = userDao.getUserInfoByUserId(userId)
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
-                val roleData = roleDao.getRoleDataByUserId(sysUserDo.userId)
                 val roleDatas = roleDao.getRoleDataByUserId(sysUserDo.userId)
                 MainDomainData.roleKeys = roleDatas.joinToString(",") { it.roleKey }
                 MainDomainData.permissions =
@@ -122,8 +126,9 @@ class UserRepository @Inject constructor(
         var hasFace = false
         var userId: String? = null
         for (faceData in faceDataList) {
-            if (faceData.content != null) {
-                if (BiometricVerifier.verifyFaceArcSoft(face, faceData.content!!)) {
+            if (faceData.content.isNotEmpty()) {
+                val fileData = faceData.content.file().readText()
+                if (BiometricVerifier.verifyFaceArcSoft(face, fileData)) {
                     hasFace = true
                     userId = faceData.userId.toString()
                     break
@@ -134,7 +139,6 @@ class UserRepository @Inject constructor(
             val sysUserDo = userDao.getUserInfoByUserId(userId)
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
-                val roleData = roleDao.getRoleDataByUserId(sysUserDo.userId)
                 val roleDatas = roleDao.getRoleDataByUserId(sysUserDo.userId)
                 MainDomainData.roleKeys = roleDatas.joinToString(",") { it.roleKey }
                 MainDomainData.permissions =
@@ -230,4 +234,30 @@ class UserRepository @Inject constructor(
     override fun getAllUsersWithRole(): List<SysUserVo> {
         return userDao.getAllUsersWithRole()
     }
+
+    override fun getFingerprintDataByUserId(userId: Long?): MutableList<SysBiometricDataVo> {
+        if (userId == null) {
+            return mutableListOf()
+        }
+        return userDao.getFingerprintDataByUserId(userId).toMutableList()
+    }
+
+    override fun getFaceDataByUserId(userId: Long?): MutableList<SysBiometricDataVo> {
+        if (userId == null) {
+            return mutableListOf()
+        }
+        return userDao.getFaceDataByUserId(userId).toMutableList()
+    }
+
+    override fun deleteFingerprintByIds(fingerprintIds: List<Long>) {
+        userDao.deleteFingerprintByIds(fingerprintIds)
+    }
+
+    override fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo) {
+        userDao.saveUserCharacteristic(sysUserCharacteristicDo)
+    }
+
+    override fun deleteFaceDataByUserId(userId: Long) {
+        userDao.deleteFaceDataByUserId(userId)
+    }
 }

+ 166 - 0
data/src/main/java/com/grkj/data/utils/FileStorageUtils.kt

@@ -0,0 +1,166 @@
+package com.grkj.data.utils
+
+import com.sik.sikcore.SIKCore
+import android.content.Context
+import java.io.File
+import java.io.IOException
+
+/**
+ * 工具类:支持在应用私有目录(internal)和外部 SDCard 目录下切换文件读写、删除及判断功能。
+ * internal->[Context.filesDir]/folder
+ * external->[Context.getExternalFilesDir(null)]/folder(若外部不存在,则降级为 internal)
+ * 均不需额外存储权限。
+ */
+object FileStorageUtils {
+
+    /** 存储类型枚举 */
+    enum class StorageType {
+        INTERNAL,  // 应用私有 internal storage
+        EXTERNAL   // 外部 SDCard 应用专属目录
+    }
+
+    private fun getBaseDir(type: StorageType): File {
+        val ctx = SIKCore.getApplication()
+        return if (type == StorageType.EXTERNAL) {
+            ctx.getExternalFilesDir(null)?.apply { if (!exists()) mkdirs() } ?: ctx.filesDir
+        } else {
+            ctx.filesDir
+        }
+    }
+
+    /**
+     * 获取文件路径
+     */
+    fun getFilePath(
+        folder: String,
+        filename: String,
+        type: StorageType = StorageType.INTERNAL
+    ): String {
+        val dir = File(getBaseDir(type), folder).apply { if (!exists()) mkdirs() }
+        val file = File(dir, filename)
+        return file.absolutePath
+    }
+
+    /**
+     * 写文本到指定存储类型的 dataDir/[folder]/[filename]
+     * @param type 存储类型,默认 INTERNAL
+     * @return 成功 true,否则 false
+     */
+    fun writeText(
+        folder: String,
+        filename: String,
+        content: String,
+        append: Boolean = false,
+        type: StorageType = StorageType.INTERNAL
+    ): Boolean {
+        return try {
+            val dir = File(getBaseDir(type), folder).apply { if (!exists()) mkdirs() }
+            val file = File(dir, filename)
+            if (append) file.appendText(content) else file.writeText(content)
+            true
+        } catch (e: IOException) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+    /**
+     * 读文本
+     */
+    fun readText(
+        folder: String,
+        filename: String,
+        type: StorageType = StorageType.INTERNAL
+    ): String? {
+        return try {
+            val file = File(getBaseDir(type), "$folder/$filename")
+            if (!file.exists()) return null
+            file.readText()
+        } catch (e: IOException) {
+            e.printStackTrace()
+            null
+        }
+    }
+
+    /**
+     * 写字节
+     */
+    fun writeBytes(
+        folder: String,
+        filename: String,
+        bytes: ByteArray,
+        append: Boolean = false,
+        type: StorageType = StorageType.INTERNAL
+    ): Boolean {
+        return try {
+            val dir = File(getBaseDir(type), folder).apply { if (!exists()) mkdirs() }
+            val file = File(dir, filename)
+            if (append) file.appendBytes(bytes) else file.writeBytes(bytes)
+            true
+        } catch (e: IOException) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+    /**
+     * 读字节
+     */
+    fun readBytes(
+        folder: String,
+        filename: String,
+        type: StorageType = StorageType.INTERNAL
+    ): ByteArray? {
+        return try {
+            val file = File(getBaseDir(type), "$folder/$filename")
+            if (!file.exists()) return null
+            file.readBytes()
+        } catch (e: IOException) {
+            e.printStackTrace()
+            null
+        }
+    }
+
+    /**
+     * 判断文件是否存在
+     */
+    fun exists(
+        folder: String,
+        filename: String,
+        type: StorageType = StorageType.INTERNAL
+    ): Boolean = File(getBaseDir(type), "$folder/$filename").exists()
+
+    /**
+     * 删除单文件
+     */
+    fun deleteFile(
+        folder: String,
+        filename: String,
+        type: StorageType = StorageType.INTERNAL
+    ): Boolean {
+        return try {
+            val file = File(getBaseDir(type), "$folder/$filename")
+            !file.exists() || file.delete()
+        } catch (e: Exception) {
+            e.printStackTrace()
+            false
+        }
+    }
+
+    /**
+     * 删除整个目录及子文件
+     */
+    fun deleteFolder(
+        folder: String,
+        type: StorageType = StorageType.INTERNAL
+    ): Boolean {
+        return try {
+            val dir = File(getBaseDir(type), folder)
+            if (!dir.exists()) return true
+            dir.deleteRecursively()
+        } catch (e: Exception) {
+            e.printStackTrace()
+            false
+        }
+    }
+}

+ 26 - 9
shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt

@@ -85,6 +85,7 @@ object ArcSoftUtil {
         )
         logger.info("initEngine:  init: $afCode")
         if (afCode != ErrorInfo.MOK) {
+            logger.info("初始化失败")
         }
     }
 
@@ -96,14 +97,20 @@ object ArcSoftUtil {
     }
 
     fun initCamera(
-        context: Context, windowManager: WindowManager, preview: View, callBack: (Bitmap?) -> Unit
+        context: Context,
+        windowManager: WindowManager,
+        preview: View,
+        callBack: (Bitmap?, Int, Boolean) -> Unit
     ) {
         val metrics = DisplayMetrics()
         windowManager.defaultDisplay.getMetrics(metrics)
 
         val cameraListener: CameraListener = object : CameraListener {
             override fun onCameraOpened(
-                camera: Camera, cameraId: Int, displayOrientation: Int, isMirror: Boolean
+                camera: Camera,
+                cameraId: Int,
+                displayOrientation: Int,
+                isMirror: Boolean
             ) {
                 logger.info("onCameraOpened: $cameraId  $displayOrientation $isMirror")
                 previewSize = camera.parameters.previewSize
@@ -119,7 +126,7 @@ object ArcSoftUtil {
                     FaceEngine.CP_PAF_NV21,
                     faceInfoList
                 )
-                if (code == ErrorInfo.MOK && faceInfoList.size > 0) {
+                if (code == ErrorInfo.MOK && faceInfoList.isNotEmpty()) {
                     code = faceEngine!!.process(
                         nv21,
                         previewSize!!.width,
@@ -146,18 +153,22 @@ object ArcSoftUtil {
 
                 // 有其中一个的错误码不为ErrorInfo.MOK,return
                 if ((ageCode or genderCode or face3DAngleCode or livenessCode) != ErrorInfo.MOK) {
+                    logger.debug("人脸检测结果:年龄、性别、角度、获取验证失败")
                     return
                 }
 
                 // 自己加的,必须有活体检测
                 if (faceLivenessInfoList.none { it.liveness == LivenessInfo.ALIVE }) {
+                    callBack(null, faceInfoList.size, false)
                     return
                 }
                 val bitmap = NV21ToBitmap(context).nv21ToBitmap(
-                    nv21, previewSize!!.width, previewSize!!.height
+                    nv21,
+                    previewSize!!.width,
+                    previewSize!!.height
                 )
-                logger.info("识别结果 : ${bitmap == null} - $faceInfoList")
-                callBack(bitmap)
+                logger.debug("人脸检测结果-识别结果 : ${bitmap == null} - $faceInfoList")
+                callBack(bitmap, faceInfoList.size, true)
             }
 
             override fun onCameraClosed() {
@@ -175,14 +186,20 @@ object ArcSoftUtil {
         cameraHelper = CameraHelper.Builder()
             .previewViewSize(Point(preview.measuredWidth, preview.measuredHeight))
             .rotation(windowManager.defaultDisplay.rotation)
-            .specificCameraId(rgbCameraId ?: Camera.CameraInfo.CAMERA_FACING_FRONT).isMirror(false)
-            .previewOn(preview).cameraListener(cameraListener).build()
+            .specificCameraId(rgbCameraId ?: Camera.CameraInfo.CAMERA_FACING_FRONT)
+            .isMirror(false)
+            .previewOn(preview)
+            .cameraListener(cameraListener)
+            .build()
         cameraHelper!!.init()
         cameraHelper!!.start()
     }
 
     fun start(
-        context: Context, windowManager: WindowManager, preview: View, callBack: (Bitmap?) -> Unit
+        context: Context,
+        windowManager: WindowManager,
+        preview: View,
+        callBack: (Bitmap?, Int, Boolean) -> Unit
     ) {
         initEngine(context)
         initCamera(context, windowManager, preview, callBack)

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

@@ -369,4 +369,5 @@
     <string name="scan_complete_app_restarting">scan complete app restarting</string>
     <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>
 </resources>

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

@@ -369,4 +369,5 @@
     <string name="scan_complete_app_restarting">扫描完成,APP将自动重启</string>
     <string name="please_do_uncolock">请共锁人解除共锁</string>
     <string name="can_not_remove_current_locker">无法移除当前上锁人</string>
+    <string name="fingerprint_delete_selected_confirm_tip">确定要删除选中的指纹吗?</string>
 </resources>

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

@@ -369,4 +369,5 @@
     <string name="scan_complete_app_restarting">扫描完成,APP将自动重启</string>
     <string name="please_do_uncolock">请共锁人解除共锁</string>
     <string name="can_not_remove_current_locker">无法移除当前上锁人</string>
+    <string name="fingerprint_delete_selected_confirm_tip">确定要删除选中的指纹吗?</string>
 </resources>