Browse Source

refactor(更新)
- 登录界面完成
- 首页问题修复中

周文健 5 months ago
parent
commit
2a2d3a84a8
100 changed files with 2246 additions and 307 deletions
  1. BIN
      ISCS.jks
  2. 28 3
      app/build.gradle.kts
  3. 41 2
      app/src/main/AndroidManifest.xml
  4. 0 0
      app/src/main/assets/logback.xml
  5. 3 1
      app/src/main/java/com/grkj/iscs/ISCSApplication.kt
  6. 0 17
      app/src/main/java/com/grkj/iscs/features/login/LoginActivity.kt
  7. 138 0
      app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt
  8. 168 0
      app/src/main/java/com/grkj/iscs/features/login/dialog/LoginDialog.kt
  9. 69 0
      app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt
  10. 0 18
      app/src/main/java/com/grkj/iscs/features/main/MainActivity.kt
  11. 88 0
      app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt
  12. 18 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/DataManageHomeFragment.kt
  13. 18 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/exception_manage/ExceptionManageHomeFragment.kt
  14. 18 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/HardwareManageHomeFragment.kt
  15. 18 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/home/HomeFragment.kt
  16. 18 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageHomeFragment.kt
  17. 43 0
      app/src/main/java/com/grkj/iscs/receivers/BootReceiver.kt
  18. 7 0
      app/src/main/res/color/nav_item_color.xml
  19. 5 0
      app/src/main/res/drawable/bg_card_item.xml
  20. 5 0
      app/src/main/res/drawable/bg_card_item_land.xml
  21. 0 170
      app/src/main/res/drawable/ic_launcher_background.xml
  22. 0 30
      app/src/main/res/drawable/ic_launcher_foreground.xml
  23. 6 0
      app/src/main/res/drawable/icon_login_menu_face.xml
  24. 9 0
      app/src/main/res/drawable/icon_login_menu_password.xml
  25. 8 0
      app/src/main/res/drawable/login_tip_circle.xml
  26. 83 0
      app/src/main/res/layout-land/activity_main.xml
  27. 86 0
      app/src/main/res/layout-land/dialog_login.xml
  28. 44 0
      app/src/main/res/layout-land/item_login_method.xml
  29. 122 2
      app/src/main/res/layout/activity_login.xml
  30. 80 14
      app/src/main/res/layout/activity_main.xml
  31. 85 0
      app/src/main/res/layout/dialog_login.xml
  32. 8 0
      app/src/main/res/layout/fragment_data_manage_home.xml
  33. 8 0
      app/src/main/res/layout/fragment_exception_manage_home.xml
  34. 8 0
      app/src/main/res/layout/fragment_hardware_manage_home.xml
  35. 8 0
      app/src/main/res/layout/fragment_home.xml
  36. 8 0
      app/src/main/res/layout/fragment_job_manage_home.xml
  37. 44 0
      app/src/main/res/layout/item_login_method.xml
  38. BIN
      app/src/main/res/mipmap-xhdpi/bg_main.png
  39. BIN
      app/src/main/res/mipmap-xhdpi/icon_avatar.png
  40. BIN
      app/src/main/res/mipmap-xhdpi/icon_bottom_menu_data_manage.png
  41. BIN
      app/src/main/res/mipmap-xhdpi/icon_bottom_menu_exception_manage.png
  42. BIN
      app/src/main/res/mipmap-xhdpi/icon_bottom_menu_hardware_manage.png
  43. BIN
      app/src/main/res/mipmap-xhdpi/icon_bottom_menu_home.png
  44. BIN
      app/src/main/res/mipmap-xhdpi/icon_bottom_menu_job_manage.png
  45. BIN
      app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_area_manage.png
  46. BIN
      app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_point_manage.png
  47. BIN
      app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_role_manage.png
  48. BIN
      app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_user_manage.png
  49. BIN
      app/src/main/res/mipmap-xhdpi/icon_login_menu_fingerprint.png
  50. BIN
      app/src/main/res/mipmap-xhdpi/icon_logo.png
  51. 11 0
      app/src/main/res/navigation/nav_data_manage.xml
  52. 11 0
      app/src/main/res/navigation/nav_exception_manage.xml
  53. 11 0
      app/src/main/res/navigation/nav_hardware_manage.xml
  54. 11 0
      app/src/main/res/navigation/nav_home.xml
  55. 11 0
      app/src/main/res/navigation/nav_job_manage.xml
  56. 5 0
      app/src/main/res/values-en/colors.xml
  57. 9 0
      app/src/main/res/values-en/strings.xml
  58. 5 0
      app/src/main/res/values-zh/colors.xml
  59. 9 0
      app/src/main/res/values-zh/strings.xml
  60. 4 0
      app/src/main/res/values/colors.xml
  61. 6 1
      app/src/main/res/values/strings.xml
  62. 9 0
      build.gradle.kts
  63. 2 0
      data/build.gradle.kts
  64. 16 0
      data/src/main/java/com/grkj/data/dao/HardwareDao.kt
  65. 36 0
      data/src/main/java/com/grkj/data/dao/UserDao.kt
  66. 21 0
      data/src/main/java/com/grkj/data/data/MainDomainData.kt
  67. 43 0
      data/src/main/java/com/grkj/data/database/ISCSDatabase.kt
  68. 11 0
      data/src/main/java/com/grkj/data/database/ISCSMigrations.kt
  69. 50 0
      data/src/main/java/com/grkj/data/model/dos/BaseBean.kt
  70. 43 0
      data/src/main/java/com/grkj/data/model/dos/IsJobCardDo.kt
  71. 55 0
      data/src/main/java/com/grkj/data/model/dos/SysUserCharacteristicDo.kt
  72. 56 0
      data/src/main/java/com/grkj/data/model/dos/SysUserDo.kt
  73. 5 1
      data/src/main/java/com/grkj/data/repository/HardwareRepository.kt
  74. 1 1
      data/src/main/java/com/grkj/data/repository/StepRepository.kt
  75. 1 1
      data/src/main/java/com/grkj/data/repository/TicketRepository.kt
  76. 116 0
      data/src/main/java/com/grkj/data/repository/UserRepository.kt
  77. 11 0
      domain/src/main/java/com/grkj/domain/entity/local/LoginMenuEntity.kt
  78. 12 0
      domain/src/main/java/com/grkj/domain/entity/local/TabConfig.kt
  79. 31 0
      domain/src/main/java/com/grkj/domain/repository/IUserRepository.kt
  80. 2 0
      gradle/libs.versions.toml
  81. 1 0
      settings.gradle.kts
  82. 21 0
      shared/build.gradle.kts
  83. 0 0
      shared/libs/adh_series_sdk.jar
  84. 0 0
      shared/libs/arcsoft_face.jar
  85. 0 0
      shared/libs/arcsoft_image_util.jar
  86. 0 0
      shared/libs/zkandroidcore.jar
  87. 0 0
      shared/libs/zkandroidfingerservice.jar
  88. 0 0
      shared/libs/zkandroidfpreader.jar
  89. 39 0
      shared/src/main/java/com/grkj/shared/config/AESConfig.kt
  90. 8 3
      shared/src/main/java/com/grkj/shared/config/Constants.kt
  91. 5 9
      shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt
  92. 24 0
      shared/src/main/java/com/grkj/shared/utils/BCryptUtils.kt
  93. 176 0
      shared/src/main/java/com/grkj/shared/utils/BiometricVerifier.kt
  94. 1 1
      shared/src/main/java/com/grkj/shared/utils/face/arcsoft/CameraHelper.java
  95. 1 1
      shared/src/main/java/com/grkj/shared/utils/face/arcsoft/CameraListener.java
  96. 1 1
      shared/src/main/java/com/grkj/shared/utils/face/arcsoft/FaceUtils.kt
  97. 1 1
      shared/src/main/java/com/grkj/shared/utils/face/arcsoft/NV21ToBitmap.java
  98. 2 8
      ui-base/build.gradle.kts
  99. 55 18
      ui-base/src/main/java/com/grkj/ui_base/base/BaseActivity.kt
  100. 5 4
      ui-base/src/main/java/com/grkj/ui_base/base/BaseFragment.kt

BIN
ISCS.jks


+ 28 - 3
app/build.gradle.kts

@@ -13,14 +13,33 @@ android {
         minSdk = 24
         targetSdk = 35
         versionCode = 1
-        versionName = "1.0"
+        versionName = "v1.0"
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
     }
+    signingConfigs {
+        create("release") {
+            storeFile = file("../ISCS.jks")
+            storePassword = "iscs123456"
+            keyAlias = "iscs"
+            keyPassword = "iscs123456"
+        }
+    }
 
     buildTypes {
-        release {
+        getByName("debug") {
+            // 如果你确实想用 release 签名
+            signingConfig = signingConfigs.getByName("release")
+            // Debug 不混淆、不裁剪
             isMinifyEnabled = false
+            isShrinkResources = false
+        }
+        getByName("release") {
+            signingConfig = signingConfigs.getByName("release")
+            // 混淆 & 去掉无用资源
+            isMinifyEnabled = true
+            isShrinkResources = true
+
             proguardFiles(
                 getDefaultProguardFile("proguard-android-optimize.txt"),
                 "proguard-rules.pro"
@@ -37,6 +56,13 @@ android {
     buildFeatures {
         dataBinding = true
     }
+    packaging {
+        resources {
+            pickFirsts += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
+            pickFirsts += "META-INF/INDEX.LIST"
+            pickFirsts += "META-INF/io.netty.versions.properties"
+        }
+    }
 }
 
 dependencies {
@@ -47,7 +73,6 @@ dependencies {
     implementation(libs.androidx.activity)
     implementation(libs.androidx.constraintlayout)
     implementation(libs.brv)
-    implementation(libs.android.autosize)
     implementation(libs.viewmodel.ktx)
     implementation(libs.viewmodel.livedata.ktx)
     implementation(libs.viewmodel.savestate)

+ 41 - 2
app/src/main/AndroidManifest.xml

@@ -2,7 +2,28 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools">
 
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+    <!--  蓝牙  -->
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+    <uses-permission
+        android:name="android.permission.BLUETOOTH_SCAN"
+        android:usesPermissionFlags="neverForLocation" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.CAMERA" />
+
+    <uses-feature android:name="android.hardware.camera" />
+    <!--    开机自启动-->
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
     <application
         android:name=".ISCSApplication"
@@ -11,12 +32,11 @@
         android:fullBackupContent="@xml/backup_rules"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/Theme.ISCS_BASE_APP"
         tools:targetApi="31">
         <activity
-            android:name=".features.main.MainActivity"
+            android:name=".features.login.activity.LoginActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -24,6 +44,25 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity
+            android:name=".features.main.activity.MainActivity"
+            android:exported="true" />
+        <receiver
+            android:name=".receivers.BootReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter android:priority="1000">
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.media.AUDIO_BECOMEING_NOISY" />
+            </intent-filter>
+        </receiver>
+
+        <meta-data
+            android:name="design_width_in_dp"
+            android:value="600" />
+        <meta-data
+            android:name="design_height_in_dp"
+            android:value="1024" />
     </application>
 
 </manifest>

+ 0 - 0
app/logback.xml → app/src/main/assets/logback.xml


+ 3 - 1
app/src/main/java/com/grkj/iscs/ISCSApplication.kt

@@ -1,9 +1,9 @@
 package com.grkj.iscs
 
 import android.app.Application
+import com.grkj.shared.utils.ArcSoftUtil
 import com.kongzue.dialogx.DialogX
 import com.sik.sikcore.SIKCore
-import com.sik.sikcore.log.LogUtils
 
 /**
  * 启动入口
@@ -16,5 +16,7 @@ class ISCSApplication : Application() {
         super.onCreate()
         DialogX.init(this)
         SIKCore.init(this)
+        //todo 模拟器不支持
+//        ArcSoftUtil.checkActiveStatus(this)
     }
 }

+ 0 - 17
app/src/main/java/com/grkj/iscs/features/login/LoginActivity.kt

@@ -1,17 +0,0 @@
-package com.grkj.iscs.features.login
-
-import com.grkj.iscs.R
-import com.grkj.iscs.databinding.ActivityLoginBinding
-import com.grkj.ui_base.base.BaseActivity
-import com.sik.sikcore.SIKCore
-import com.tencent.mmkv.MMKV
-
-class LoginActivity: BaseActivity<ActivityLoginBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.activity_login
-    }
-
-    override fun initView() {
-
-    }
-}

+ 138 - 0
app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -0,0 +1,138 @@
+package com.grkj.iscs.features.login.activity
+
+import android.content.Intent
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.ViewModelProvider
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.divider
+import com.drake.brv.utils.grid
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.domain.entity.local.LoginMenuEntity
+import com.grkj.iscs.R
+import com.grkj.data.data.MainDomainData
+import com.grkj.iscs.databinding.ActivityLoginBinding
+import com.grkj.iscs.databinding.ItemLoginMethodBinding
+import com.grkj.iscs.features.login.dialog.LoginDialog
+import com.grkj.iscs.features.login.viewmodel.LoginViewModel
+import com.grkj.iscs.features.main.activity.MainActivity
+import com.grkj.shared.config.Constants
+import com.grkj.ui_base.base.BaseActivity
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.getAppVersionName
+import com.grkj.ui_base.utils.fingerprint.FingerprintUtil
+
+/**
+ * 登录页
+ */
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    private val viewModel: LoginViewModel by lazy { ViewModelProvider(this)[LoginViewModel::class.java] }
+
+    //登录方式列表
+    private val loginMenuList: MutableList<LoginMenuEntity> = mutableListOf()
+    private var cardNo = ""
+
+    override fun getLayoutId(): Int {
+        return R.layout.activity_login
+    }
+
+    override fun initView() {
+        binding.loginTypeRv.apply {
+            if (isLandscape()) {
+                linear(orientation = LinearLayout.HORIZONTAL)
+                divider(
+                    com.grkj.ui_base.R.drawable.common_divider_large_space_grid_land,
+                    DividerOrientation.GRID
+                )
+            } else {
+                grid(2)
+                divider(
+                    com.grkj.ui_base.R.drawable.common_divider_large_space_grid,
+                    DividerOrientation.GRID
+                )
+            }
+        }.setup {
+            addType<LoginMenuEntity>(R.layout.item_login_method)
+            onBind {
+                onLoginMenuBinding(this)
+            }
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onLoginMenuBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemLoginMethodBinding>()
+        val item = holder.getModel<LoginMenuEntity>()
+        itemBinding.loginTipV.isVisible == item.needTip
+        itemBinding.loginMethodTv.text = item.menuText
+        itemBinding.loginMethodIv.setBackgroundResource(item.menuIconId)
+        holder.itemView.setOnClickListener {
+            LoginDialog.show(this@LoginActivity, viewModel, item.loginType) {
+                if (it) {
+                    startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                }
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        requestPermissionsIfNeeded(*Constants.needPermission) {
+            if (it) {
+                logger.info("权限获取成功")
+            } else {
+                logger.info("权限获取失败")
+            }
+        }
+        binding.version.text = this.getAppVersionName()
+        loginMenuList.apply {
+            add(
+                LoginMenuEntity(
+                    R.drawable.icon_login_menu_face,
+                    CommonUtils.getStr(R.string.face_login).toString(),
+                    false,
+                    0
+                )
+            )
+            add(
+                LoginMenuEntity(
+                    R.drawable.icon_login_menu_password,
+                    CommonUtils.getStr(R.string.account_login).toString(),
+                    false,
+                    3
+                )
+            )
+            add(
+                LoginMenuEntity(
+                    R.mipmap.icon_login_menu_fingerprint,
+                    CommonUtils.getStr(R.string.fingerprint_login).toString(),
+                    true,
+                    1
+                )
+            )
+            add(
+                LoginMenuEntity(
+                    R.drawable.icon_login_menu_card,
+                    CommonUtils.getStr(R.string.card_login).toString(),
+                    true,
+                    2
+                )
+            )
+        }
+        binding.loginTypeRv.models = loginMenuList
+    }
+
+    override fun onResume() {
+        super.onResume()
+        MainDomainData.clear()
+    }
+
+    override fun onStop() {
+        super.onStop()
+        cardNo = ""
+        FingerprintUtil.stop()
+        FingerprintUtil.unInit()
+    }
+}

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

@@ -0,0 +1,168 @@
+package com.grkj.iscs.features.login.dialog
+
+import android.graphics.Bitmap
+import android.view.View
+import androidx.lifecycle.LifecycleOwner
+import com.grkj.domain.entity.res.UserInfoRes
+import com.grkj.iscs.R
+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.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.sikimage.ImageConvertUtils
+import com.sik.sikimage.ImageUtils
+
+class LoginDialog(
+    private var lifecycleOwner: LifecycleOwner,
+    private var viewModel: LoginViewModel,
+    private var callBack: ((Boolean) -> Unit)? = null
+) : OnBindView<CustomDialog>(R.layout.dialog_login) {
+
+    private var cardNo = ""
+    private var mLoginType = 3 // 0:人脸 1:指纹 2:工卡 3:账号
+    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: DialogLoginBinding
+
+    override fun onBind(customDialog: CustomDialog, contentView: View) {
+        mBinding = DialogLoginBinding.bind(contentView)
+        mBinding.tvLogin.setOnClickListener {
+            if (mBinding.etAccount.text.toString().isEmpty()) {
+                PopTip.tip(CommonUtils.getStr(com.grkj.ui_base.R.string.please_input_account))
+                return@setOnClickListener
+            }
+            if (mBinding.etPassword.text.toString().isEmpty()) {
+                PopTip.tip(CommonUtils.getStr(com.grkj.ui_base.R.string.please_input_password))
+                return@setOnClickListener
+            }
+            viewModel.loginWithAccount(
+                "罗成",
+                "123456"
+//                mBinding.etAccount.text.toString(),
+//                mBinding.etPassword.text.toString()
+            ).observe(lifecycleOwner) {
+                callBack?.invoke(it)
+            }
+        }
+        customDialog.setDialogLifecycleCallback(object : DialogLifecycleCallback<CustomDialog>() {
+            override fun onDismiss(dialog: CustomDialog?) {
+                when (mLoginType) {
+                    0 -> {
+                        ArcSoftUtil.stop()
+                    }
+
+                    1 -> {
+                        FingerprintUtil.stop()
+                        FingerprintUtil.unInit()
+                    }
+
+                    3 -> {
+                        mBinding.etAccount.text?.clear()
+                        mBinding.etPassword.text?.clear()
+                    }
+                }
+                super.onDismiss(dialog)
+            }
+        })
+        mBinding.tvCancel.setOnClickListener { customDialog.dismiss() }
+        if (mLoginType == 3) {
+            mBinding.llAccountContainer.visibility = View.VISIBLE
+            mBinding.llEasyContainer.visibility = View.GONE
+        } else {
+            mBinding.llAccountContainer.visibility = View.GONE
+            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) {
+                            viewModel.loginWithFingerprint(
+                                ImageConvertUtils.bitmapToBase64(bitmap).toString()
+                            ).observe(lifecycleOwner) {
+                                callBack?.invoke(it)
+                            }
+                        }
+                    })
+                }
+            }
+        }
+    }
+
+    fun showByType(loginType: Int) {
+        this.mLoginType = loginType
+    }
+
+    companion object {
+        /**
+         * 根据类型显示弹框
+         *
+         * @param loginType 0:人脸 1:指纹 2:工卡 3:账号
+         */
+        @JvmStatic
+        fun show(
+            lifecycleOwner: LifecycleOwner,
+            viewModel: LoginViewModel,
+            loginType: Int,
+            callBack: ((Boolean) -> Unit)?
+        ) {
+            CustomDialog.show(LoginDialog(lifecycleOwner, viewModel, callBack).apply {
+                showByType(loginType)
+            })
+        }
+    }
+
+
+    private fun startFace() {
+        ActivityTracker.getCurrentActivity()?.let { context ->
+            ArcSoftUtil.initEngine(context)
+            ArcSoftUtil.initCamera(context, context.windowManager, mBinding?.preview!!) {
+                it?.let { itBitmap ->
+                    viewModel.loginWithFace(
+                        ImageConvertUtils.bitmapToBase64(itBitmap).toString()
+                    ).observe(lifecycleOwner) {
+                        callBack?.invoke(it)
+                    }
+                    LoadingEvent.sendLoadingEvent(
+                        context.getString(com.grkj.ui_base.R.string.face_detected_do_login),
+                        true
+                    )
+                    ArcSoftUtil.stop()
+                }
+            }
+        }
+    }
+}

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

@@ -0,0 +1,69 @@
+package com.grkj.iscs.features.login.viewmodel
+
+import android.graphics.Bitmap
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.repository.UserRepository
+import com.grkj.domain.repository.IUserRepository
+import com.grkj.ui_base.base.BaseViewModel
+import kotlinx.coroutines.Dispatchers
+
+/**
+ * 登录界面模型
+ */
+class LoginViewModel : BaseViewModel() {
+    private val userRepository: IUserRepository by lazy { UserRepository() }
+
+    /**
+     * 用户名登录
+     */
+    fun loginWithAccount(
+        username: String,
+        password: String,
+    ): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val loginSuccess = userRepository.loginWithAccount(username, password)
+            emit(loginSuccess)
+        }
+    }
+
+    /**
+     * 刷卡登录
+     */
+    fun loginWithCard(cardNo: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val loginSuccess = userRepository.loginWithCard(cardNo)
+            emit(loginSuccess)
+        }
+    }
+
+    /**
+     * 指纹登录
+     */
+    fun loginWithFingerprint(fingerprint: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val loginSuccess = userRepository.loginWithFingerprint(fingerprint)
+            emit(loginSuccess)
+        }
+    }
+
+    /**
+     * 人脸登录
+     */
+    fun loginWithFace(face: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val loginSuccess = userRepository.loginWithFace(face)
+            emit(loginSuccess)
+        }
+    }
+
+    /**
+     * 退出登录
+     */
+    fun logout(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val logoutSuccess = userRepository.logout()
+            emit(logoutSuccess)
+        }
+    }
+}

+ 0 - 18
app/src/main/java/com/grkj/iscs/features/main/MainActivity.kt

@@ -1,18 +0,0 @@
-package com.grkj.iscs.features.main
-
-import com.grkj.iscs.R
-import com.grkj.iscs.databinding.ActivityMainBinding
-import com.grkj.ui_base.base.BaseActivity
-
-/**
- * 首页
- */
-class MainActivity() : BaseActivity<ActivityMainBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.activity_main
-    }
-
-    override fun initView() {
-
-    }
-}

+ 88 - 0
app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt

@@ -0,0 +1,88 @@
+package com.grkj.iscs.features.main.activity
+
+import android.view.Menu
+import androidx.core.view.get
+import androidx.core.view.isNotEmpty
+import com.grkj.data.data.MainDomainData
+import com.grkj.domain.entity.local.TabConfig
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.ActivityMainBinding
+import com.grkj.ui_base.base.BaseActivity
+
+/**
+ * 首页
+ */
+class MainActivity() : BaseActivity<ActivityMainBinding>() {
+    private val tabConfigs = listOf(
+        TabConfig(100, R.id.nav_home, "主页", R.mipmap.icon_bottom_menu_home, "PERM_HOME"),
+        TabConfig(
+            101,
+            R.navigation.nav_data_manage,
+            "数据管理",
+            R.mipmap.icon_bottom_menu_data_manage,
+            "PERM_DATA_MANAGE"
+        ),
+        TabConfig(
+            102,
+            R.navigation.nav_job_manage,
+            "作业管理",
+            R.mipmap.icon_bottom_menu_job_manage,
+            "PERM_JOB_MANAGE"
+        ),
+        TabConfig(
+            102,
+            R.navigation.nav_hardware_manage,
+            "硬件管理",
+            R.mipmap.icon_bottom_menu_hardware_manage,
+            "PERM_HARDWARE_MANAGE"
+        ),
+        TabConfig(
+            102,
+            R.navigation.nav_exception_manage,
+            "异常管理",
+            R.mipmap.icon_bottom_menu_exception_manage,
+            "PERM_EXCEPTION_MANAGE"
+        ),
+    )
+
+    override fun enableNavigation() = true
+    override fun navHostFragmentId() = R.id.nav_host_fragment
+    override fun getLayoutId(): Int {
+        return R.layout.activity_main
+    }
+
+    override fun initView() {
+        binding.nickname.text = MainDomainData.userInfo?.nickName ?: ""
+        //todo 可以增加权限控制
+        // 动态构造底部菜单(可加权限过滤)
+        binding.bottomNav.menu.clear()
+        val userPerms = mutableListOf<String>(
+            "PERM_HOME",
+            "PERM_JOB_MANAGE",
+            "PERM_JOB_MANAGE",
+            "PERM_HARDWARE_MANAGE",
+            "PERM_EXCEPTION_MANAGE"
+        )
+        tabConfigs.forEachIndexed { index, cfg ->
+            if (userPerms.contains(cfg.permission)) {
+                binding.bottomNav.menu
+                    .add(Menu.NONE, cfg.id, index, cfg.title)
+                    .setIcon(cfg.icon)
+            }
+        }
+
+        // 构造 map: menuItemId -> navGraphId
+        val graphMap = tabConfigs
+            .filter { binding.bottomNav.menu.findItem(it.id) != null }
+            .associate { it.id to it.graphRes }
+
+        // 把 BottomNavigationView 和 NavController 绑定
+        setupBottomNavigation(binding.bottomNav, graphMap)
+
+        // 默认选中第一个
+        if (binding.bottomNav.menu.isNotEmpty()) {
+            val firstId = binding.bottomNav.menu[0].itemId
+            binding.bottomNav.selectedItemId = firstId
+        }
+    }
+}

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

@@ -0,0 +1,18 @@
+package com.grkj.iscs.features.main.fragment.data_manage
+
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentDataManageHomeBinding
+import com.grkj.ui_base.base.BaseFragment
+
+/**
+ * 数据管理首页
+ */
+class DataManageHomeFragment: BaseFragment<FragmentDataManageHomeBinding>() {
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_data_manage_home
+    }
+
+    override fun initView() {
+
+    }
+}

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

@@ -0,0 +1,18 @@
+package com.grkj.iscs.features.main.fragment.exception_manage
+
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentExceptionManageHomeBinding
+import com.grkj.ui_base.base.BaseFragment
+
+/**
+ * 异常管理首页
+ */
+class ExceptionManageHomeFragment : BaseFragment<FragmentExceptionManageHomeBinding>() {
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_exception_manage_home
+    }
+
+    override fun initView() {
+
+    }
+}

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

@@ -0,0 +1,18 @@
+package com.grkj.iscs.features.main.fragment.hardware_manage
+
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentHardwareManageHomeBinding
+import com.grkj.ui_base.base.BaseFragment
+
+/**
+ * 硬件管理首页
+ */
+class HardwareManageHomeFragment : BaseFragment<FragmentHardwareManageHomeBinding>() {
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_hardware_manage_home
+    }
+
+    override fun initView() {
+
+    }
+}

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

@@ -0,0 +1,18 @@
+package com.grkj.iscs.features.main.fragment.home
+
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentHomeBinding
+import com.grkj.ui_base.base.BaseFragment
+
+/**
+ * 首页
+ */
+class HomeFragment: BaseFragment<FragmentHomeBinding>(){
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_home
+    }
+
+    override fun initView() {
+
+    }
+}

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

@@ -0,0 +1,18 @@
+package com.grkj.iscs.features.main.fragment.job_manage
+
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentJobManageHomeBinding
+import com.grkj.ui_base.base.BaseFragment
+
+/**
+ * 作业管理首页
+ */
+class JobManageHomeFragment : BaseFragment<FragmentJobManageHomeBinding>() {
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_job_manage_home
+    }
+
+    override fun initView() {
+
+    }
+}

+ 43 - 0
app/src/main/java/com/grkj/iscs/receivers/BootReceiver.kt

@@ -0,0 +1,43 @@
+package com.grkj.iscs.receivers
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.grkj.iscs.features.login.activity.LoginActivity
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import kotlin.apply
+import kotlin.jvm.java
+import kotlin.text.equals
+
+/**
+ *
+ * 开机启动接收器
+ * */
+class BootReceiver : BroadcastReceiver() {
+    private val logger: Logger = LoggerFactory.getLogger(BootReceiver::class.java)
+    override fun onReceive(context: Context, intent: Intent?) {
+        if (intent!!.action.equals("android.intent.action.BOOT_COMPLETED")) {
+            logger.debug("接收到启动通知,开始启动应用")
+            //开机2秒后启动程序
+            val startAppIntent = Intent(
+                context, LoginActivity::class.java
+            ).apply {
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            }
+            //启动应用,得使用PendingIntent
+            val startAppPendingIntent =
+                PendingIntent.getActivity(
+                    context, 0, startAppIntent,
+                    PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+                );
+            val mAlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+            mAlarmManager.set(
+                AlarmManager.RTC, System.currentTimeMillis() + 2000,
+                startAppPendingIntent
+            ) // 2秒钟后重启应用
+        }
+    }
+}

+ 7 - 0
app/src/main/res/color/nav_item_color.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- 选中时的颜色 -->
+    <item android:color="@color/color_1daeff" android:state_checked="true" />
+    <!-- 默认未选中时的颜色 -->
+    <item android:color="@color/color_7b7d7e" />
+</selector>

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

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/white20" />
+    <corners android:radius="30dp" />
+</shape>

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

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/white20" />
+    <corners android:radius="15dp" />
+</shape>

+ 0 - 170
app/src/main/res/drawable/ic_launcher_background.xml

@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path
-        android:fillColor="#3DDC84"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-</vector>

+ 0 - 30
app/src/main/res/drawable/ic_launcher_foreground.xml

@@ -1,30 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="85.84757"
-                android:endY="92.4963"
-                android:startX="42.9492"
-                android:startY="49.59793"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
-        android:strokeWidth="1"
-        android:strokeColor="#00000000" />
-</vector>

File diff suppressed because it is too large
+ 6 - 0
app/src/main/res/drawable/icon_login_menu_face.xml


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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="103dp"
+    android:height="101dp"
+    android:viewportWidth="103"
+    android:viewportHeight="101">
+  <path
+      android:pathData="M42,42.93C31.55,42.93 22.47,33.55 22.47,21.44C22.47,9.48 31.55,0.35 42,0.35C52.45,0.35 61.53,9.29 61.53,21.35C61.53,33.55 52.5,42.93 42,42.93ZM87.07,40.05C95.66,40.05 102.6,47.03 102.6,55.57C102.6,62.36 98.4,67.83 90.88,70.91L97.42,77.4C98.01,77.99 98.01,78.72 97.52,79.16L90.83,85.7L95.66,90.54C96.15,91.02 96.15,91.71 95.66,92.24L87.9,99.96C87.36,100.5 86.68,100.45 86.19,99.96L82.09,95.86C81.7,95.37 81.4,94.93 81.4,94.34V70.17C75.4,67.83 71.49,62.21 71.49,55.57C71.49,47.03 78.38,40.05 87.07,40.05ZM87.02,47.32C84.33,47.32 82.19,49.47 82.19,52.11C82.19,54.84 84.38,56.99 87.02,56.99C89.66,56.99 91.8,54.84 91.8,52.11C91.8,49.47 89.66,47.32 87.02,47.32ZM9.24,88.14C4.01,88.14 0.89,85.7 0.89,81.65C0.89,69.05 16.66,51.67 42,51.67C50.89,51.67 58.6,53.82 64.85,57.14C65.29,64.22 69,70.42 74.71,74.18V88.14H9.24Z"
+      android:fillColor="#D7D2D2"/>
+</vector>

+ 8 - 0
app/src/main/res/drawable/login_tip_circle.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="10dp"
+        android:height="10dp" />
+    <solid android:color="@color/login_tip_circle_bg" />
+</shape>

+ 83 - 0
app/src/main/res/layout-land/activity_main.xml

@@ -0,0 +1,83 @@
+<?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">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@mipmap/bg_main"
+        android:fitsSystemWindows="true"
+        android:orientation="vertical">
+
+        <FrameLayout
+            android:id="@+id/header_layout"
+            android:layout_width="match_parent"
+            android:layout_height="35dp"
+            android:layout_toRightOf="@+id/bottom_nav"
+            android:paddingHorizontal="5dp">
+
+            <ImageView
+                android:layout_width="97dp"
+                android:layout_height="17.5dp"
+                android:layout_gravity="center_vertical"
+                android:src="@mipmap/icon_logo" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="right"
+                android:orientation="horizontal">
+
+                <TextClock
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:format12Hour="yyyy-MM-dd HH:mm:ss"
+                    android:format24Hour="yyyy-MM-dd HH:mm:ss"
+                    android:gravity="center_vertical"
+                    android:textColor="@color/white"
+                    android:paddingHorizontal="5dp"
+                    android:textSize="10sp" />
+
+                <ImageView
+                    android:layout_width="12.5dp"
+                    android:layout_height="12.5dp"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginLeft="1.5dp"
+                    android:src="@mipmap/icon_avatar" />
+
+                <TextView
+                    android:id="@+id/nickname"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="5dp"
+                    android:textColor="@color/white"
+                    android:textSize="10sp" />
+            </LinearLayout>
+        </FrameLayout>
+
+        <View
+            android:id="@+id/header_line"
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:layout_below="@+id/header_layout"
+            android:layout_toRightOf="@+id/bottom_nav"
+            android:background="@color/white30" />
+
+        <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/nav_host_fragment"
+            android:name="androidx.navigation.fragment.NavHostFragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_below="@+id/header_line"
+            android:layout_toRightOf="@+id/bottom_nav"
+            app:defaultNavHost="true"
+            app:navGraph="@navigation/nav_home" />
+
+        <com.google.android.material.bottomnavigation.BottomNavigationView
+            android:id="@+id/bottom_nav"
+            android:layout_width="45dp"
+            android:layout_height="match_parent"
+            android:background="@color/white30" />
+    </RelativeLayout>
+</layout>

+ 86 - 0
app/src/main/res/layout-land/dialog_login.xml

@@ -0,0 +1,86 @@
+<?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="150dp"
+        android:layout_height="200dp"
+        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="165dp"
+            android:layout_height="146dp"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:visibility="gone">
+
+            <ImageView
+                android:id="@+id/iv_icon"
+                android:layout_width="100dp"
+                android:layout_height="100dp"
+                android:layout_marginBottom="2.5dp"
+                android:adjustViewBounds="false" />
+
+            <TextView
+                android:id="@+id/tv_tip"
+                style="@style/CommonTextView"
+                android:textSize="@dimen/common_text_size_land" />
+        </LinearLayout>
+
+        <FrameLayout
+            android:layout_width="150dp"
+            android:layout_height="200dp">
+
+            <TextureView
+                android:id="@+id/preview"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="invisible" />
+        </FrameLayout>
+
+        <LinearLayout
+            android:id="@+id/ll_account_container"
+            android:layout_width="150dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:padding="10dp">
+
+            <EditText
+                android:id="@+id/et_account"
+                style="@style/CommonEdit_land"
+                android:layout_width="match_parent"
+                android:layout_marginBottom="5dp"
+                android:hint="@string/please_input_account" />
+
+            <EditText
+                android:id="@+id/et_password"
+                style="@style/CommonEdit_land"
+                android:layout_width="match_parent"
+                android:layout_marginBottom="5dp"
+                android:hint="@string/please_input_password"
+                android:inputType="textPassword" />
+
+            <TextView
+                android:id="@+id/tv_login"
+                style="@style/CommonBtn_land"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="5dp"
+                android:text="@string/login" />
+
+            <TextView
+                android:id="@+id/tv_cancel"
+                style="@style/CommonBtn_land"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/white_stroke_bg"
+                android:text="@string/cancel" />
+        </LinearLayout>
+    </com.google.android.material.card.MaterialCardView>
+</layout>

+ 44 - 0
app/src/main/res/layout-land/item_login_method.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent">
+
+        <RelativeLayout
+            android:layout_width="100.5dp"
+            android:layout_height="113dp"
+            android:layout_gravity="center"
+            android:background="@drawable/bg_card_item_land">
+
+            <View
+                android:id="@+id/login_tip_v"
+                android:layout_width="10dp"
+                android:layout_height="10dp"
+                android:layout_alignParentRight="true"
+                android:layout_marginTop="9.5dp"
+                android:layout_marginRight="7dp"
+                android:background="@drawable/login_tip_circle"
+                android:visibility="gone" />
+
+            <ImageView
+                android:id="@+id/login_method_iv"
+                android:layout_width="60.5dp"
+                android:layout_height="60.5dp"
+                android:layout_centerHorizontal="true"
+                android:layout_marginTop="11.25dp"
+                android:adjustViewBounds="false"
+                android:scaleType="center" />
+
+            <TextView
+                android:id="@+id/login_method_tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/login_method_iv"
+                android:layout_marginTop="10dp"
+                android:gravity="center_horizontal"
+                android:textColor="@color/login_method_tv_color"
+                android:textSize="17.5sp" />
+        </RelativeLayout>
+    </FrameLayout>
+</layout>

+ 122 - 2
app/src/main/res/layout/activity_login.xml

@@ -1,8 +1,128 @@
 <?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:android="http://schemas.android.com/apk/res/android">
+<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">
+
     <RelativeLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:background="@mipmap/bg_main"
+        android:fitsSystemWindows="true"
+        android:orientation="vertical">
+
+        <FrameLayout
+            android:id="@+id/header_layout"
+            android:layout_width="match_parent"
+            android:layout_height="70dp"
+            android:paddingHorizontal="10dp">
+
+            <ImageView
+                android:layout_width="194dp"
+                android:layout_height="35dp"
+                android:layout_gravity="center_vertical"
+                android:src="@mipmap/icon_logo" />
+
+            <TextClock
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="right"
+                android:format12Hour="yyyy-MM-dd HH:mm:ss"
+                android:format24Hour="yyyy-MM-dd HH:mm:ss"
+                android:gravity="center_vertical"
+                android:textColor="@color/white"
+                android:textSize="20sp" />
+        </FrameLayout>
+
+        <View
+            android:id="@+id/header_line"
+            android:layout_width="match_parent"
+            android:layout_height="3dp"
+            android:layout_below="@+id/header_layout"
+            android:background="@color/white30" />
+
+        <LinearLayout
+            android:id="@+id/main_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/header_line"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/version"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="right"
+                android:layout_marginTop="10dp"
+                android:layout_marginRight="10dp"
+                android:textColor="@color/white"
+                android:textSize="14sp"
+                tools:text="v1.0" />
+
+            <com.grkj.ui_base.widget.ShadowTextView
+                android:id="@+id/title_cn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginTop="20dp"
+                android:text="@string/loto"
+                android:textColor="@color/white"
+                android:textSize="60sp"
+                android:textStyle="bold" />
+
+            <com.grkj.ui_base.widget.ShadowTextView
+                android:id="@+id/title_en"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginTop="20dp"
+                android:text="@string/loto_en"
+                android:textColor="@color/white"
+                android:textSize="25sp"
+                android:textStyle="bold" />
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/login_type_rv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginVertical="66dp" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:divider="@drawable/common_divider_normal_space_horizontal"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:showDividers="middle">
+
+                <View
+                    android:layout_width="20dp"
+                    android:layout_height="20dp"
+                    android:background="@drawable/login_tip_circle" />
+
+                <TextView
+                    android:id="@+id/login_tip_tv"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentBottom="true"
+                    android:gravity="center_horizontal"
+                    android:text="@string/login_tip"
+                    android:textColor="@color/white"
+                    android:textSize="25sp" />
+            </LinearLayout>
+        </LinearLayout>
+
 
+        <TextView
+            android:id="@+id/tec_support"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="10dp"
+            android:gravity="center_horizontal"
+            android:text="@string/tec_support"
+            android:textColor="@color/white"
+            android:textSize="18sp" />
     </RelativeLayout>
 </layout>

+ 80 - 14
app/src/main/res/layout/activity_main.xml

@@ -3,20 +3,86 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools">
 
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/main"
+    <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        tools:context=".features.main.MainActivity">
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Hello World!"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
+        android:background="@mipmap/bg_main"
+        android:fitsSystemWindows="true"
+        android:orientation="vertical">
+
+        <FrameLayout
+            android:id="@+id/header_layout"
+            android:layout_width="match_parent"
+            android:layout_height="70dp"
+            android:paddingHorizontal="10dp">
+
+            <ImageView
+                android:layout_width="194dp"
+                android:layout_height="35dp"
+                android:layout_gravity="center_vertical"
+                android:src="@mipmap/icon_logo" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="right"
+                android:orientation="horizontal">
+
+                <TextClock
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:format12Hour="yyyy-MM-dd HH:mm:ss"
+                    android:format24Hour="yyyy-MM-dd HH:mm:ss"
+                    android:gravity="center_vertical"
+                    android:textColor="@color/white"
+                    android:paddingHorizontal="5dp"
+                    android:textSize="20sp" />
+
+                <ImageView
+                    android:layout_width="25dp"
+                    android:layout_height="25dp"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginLeft="3dp"
+                    android:src="@mipmap/icon_avatar" />
+
+                <TextView
+                    android:id="@+id/nickname"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:textColor="@color/white"
+                    android:textSize="20sp" />
+            </LinearLayout>
+
+
+        </FrameLayout>
+
+        <View
+            android:id="@+id/header_line"
+            android:layout_width="match_parent"
+            android:layout_height="3dp"
+            android:layout_below="@+id/header_layout"
+            android:background="@color/white30" />
+
+        <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/nav_host_fragment"
+            android:name="androidx.navigation.fragment.NavHostFragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_above="@+id/bottom_nav"
+            android:layout_below="@+id/header_line"
+            app:defaultNavHost="true"
+            app:navGraph="@navigation/nav_home" />
+
+        <com.google.android.material.bottomnavigation.BottomNavigationView
+            android:id="@+id/bottom_nav"
+            android:layout_width="match_parent"
+            android:layout_height="90dp"
+            android:layout_alignParentBottom="true"
+            android:layout_gravity="bottom"
+            android:background="@color/white30"
+            app:itemIconTint="@color/nav_item_color"
+            app:itemTextColor="@color/nav_item_color"
+            app:labelVisibilityMode="labeled" />
+    </RelativeLayout>
 </layout>

+ 85 - 0
app/src/main/res/layout/dialog_login.xml

@@ -0,0 +1,85 @@
+<?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="300dp"
+        android:layout_height="400dp"
+        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="330dp"
+            android:layout_height="292dp"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:visibility="visible">
+
+            <ImageView
+                android:id="@+id/iv_icon"
+                android:layout_width="200dp"
+                android:layout_height="200dp"
+                android:layout_marginBottom="5dp" />
+
+            <TextView
+                android:id="@+id/tv_tip"
+                style="@style/CommonTextView"
+                android:textSize="@dimen/common_text_size_big" />
+        </LinearLayout>
+
+        <FrameLayout
+            android:layout_width="300dp"
+            android:layout_height="400dp">
+
+            <TextureView
+                android:id="@+id/preview"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="invisible" />
+        </FrameLayout>
+
+        <LinearLayout
+            android:id="@+id/ll_account_container"
+            android:layout_width="300dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:padding="20dp">
+
+            <EditText
+                android:id="@+id/et_account"
+                style="@style/CommonEdit"
+                android:layout_width="match_parent"
+                android:layout_marginBottom="10dp"
+                android:hint="@string/please_input_account" />
+
+            <EditText
+                android:id="@+id/et_password"
+                style="@style/CommonEdit"
+                android:layout_width="match_parent"
+                android:layout_marginBottom="10dp"
+                android:hint="@string/please_input_password"
+                android:inputType="textPassword" />
+
+            <TextView
+                android:id="@+id/tv_login"
+                style="@style/CommonBtn"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:text="@string/login" />
+
+            <TextView
+                android:id="@+id/tv_cancel"
+                style="@style/CommonBtn"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/white_stroke_bg"
+                android:text="@string/cancel" />
+        </LinearLayout>
+    </com.google.android.material.card.MaterialCardView>
+</layout>

+ 8 - 0
app/src/main/res/layout/fragment_data_manage_home.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    </RelativeLayout>
+</layout>

+ 8 - 0
app/src/main/res/layout/fragment_exception_manage_home.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        
+    </RelativeLayout>
+</layout>

+ 8 - 0
app/src/main/res/layout/fragment_hardware_manage_home.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        
+    </RelativeLayout>
+</layout>

+ 8 - 0
app/src/main/res/layout/fragment_home.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        
+    </RelativeLayout>
+</layout>

+ 8 - 0
app/src/main/res/layout/fragment_job_manage_home.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        
+    </RelativeLayout>
+</layout>

+ 44 - 0
app/src/main/res/layout/item_login_method.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <RelativeLayout
+            android:layout_width="201dp"
+            android:layout_height="226dp"
+            android:layout_gravity="center"
+            android:background="@drawable/bg_card_item">
+
+            <View
+                android:id="@+id/login_tip_v"
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:layout_alignParentRight="true"
+                android:layout_marginTop="19dp"
+                android:layout_marginRight="14dp"
+                android:background="@drawable/login_tip_circle"
+                android:visibility="gone" />
+
+            <ImageView
+                android:id="@+id/login_method_iv"
+                android:layout_width="121dp"
+                android:layout_height="121dp"
+                android:layout_centerHorizontal="true"
+                android:layout_marginTop="22.5dp"
+                android:adjustViewBounds="false"
+                android:scaleType="center"/>
+
+            <TextView
+                android:id="@+id/login_method_tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/login_method_iv"
+                android:layout_marginTop="20dp"
+                android:gravity="center_horizontal"
+                android:textColor="@color/login_method_tv_color"
+                android:textSize="35sp" />
+        </RelativeLayout>
+    </FrameLayout>
+</layout>

BIN
app/src/main/res/mipmap-xhdpi/bg_main.png


BIN
app/src/main/res/mipmap-xhdpi/icon_avatar.png


BIN
app/src/main/res/mipmap-xhdpi/icon_bottom_menu_data_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_bottom_menu_exception_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_bottom_menu_hardware_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_bottom_menu_home.png


BIN
app/src/main/res/mipmap-xhdpi/icon_bottom_menu_job_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_area_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_point_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_role_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_data_manage_menu_user_manage.png


BIN
app/src/main/res/mipmap-xhdpi/icon_login_menu_fingerprint.png


BIN
app/src/main/res/mipmap-xhdpi/icon_logo.png


+ 11 - 0
app/src/main/res/navigation/nav_data_manage.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_data_manage"
+    app:startDestination="@id/dataManageHomeFragment">
+
+    <fragment
+        android:id="@+id/dataManageHomeFragment"
+        android:name="com.grkj.iscs.features.main.fragment.data_manage.DataManageHomeFragment"
+        android:label="DataManageHomeFragment" />
+</navigation>

+ 11 - 0
app/src/main/res/navigation/nav_exception_manage.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_exception_manage"
+    app:startDestination="@id/exceptionManageHomeFragment">
+
+    <fragment
+        android:id="@+id/exceptionManageHomeFragment"
+        android:name="com.grkj.iscs.features.main.fragment.exception_manage.ExceptionManageHomeFragment"
+        android:label="ExceptionManageHomeFragment" />
+</navigation>

+ 11 - 0
app/src/main/res/navigation/nav_hardware_manage.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_hardware_manage"
+    app:startDestination="@id/hardwareManageHomeFragment">
+
+    <fragment
+        android:id="@+id/hardwareManageHomeFragment"
+        android:name="com.grkj.iscs.features.main.fragment.hardware_manage.HardwareManageHomeFragment"
+        android:label="HardwareManageHomeFragment" />
+</navigation>

+ 11 - 0
app/src/main/res/navigation/nav_home.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_home"
+    app:startDestination="@id/homeFragment">
+
+    <fragment
+        android:id="@+id/homeFragment"
+        android:name="com.grkj.iscs.features.main.fragment.home.HomeFragment"
+        android:label="HomeFragment" />
+</navigation>

+ 11 - 0
app/src/main/res/navigation/nav_job_manage.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_job_manage"
+    app:startDestination="@id/jobManageHomeFragment">
+
+    <fragment
+        android:id="@+id/jobManageHomeFragment"
+        android:name="com.grkj.iscs.features.main.fragment.job_manage.JobManageHomeFragment"
+        android:label="JobManageHomeFragment" />
+</navigation>

+ 5 - 0
app/src/main/res/values-en/colors.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="login_tip_circle_bg">#00ddfa</color>
+    <color name="login_method_tv_color">#D7D2D2</color>
+</resources>

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

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="loto_en">Intelligent Lock Control System</string>
+    <string name="tec_support">温州博士安全用品有限公司</string>
+    <string name="face_login">face login</string>
+    <string name="card_login">card login</string>
+    <string name="fingerprint_login">fingerprint login</string>
+    <string name="account_login">account login</string>
+</resources>

+ 5 - 0
app/src/main/res/values-zh/colors.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="login_tip_circle_bg">#00ddfa</color>
+    <color name="login_method_tv_color">#D7D2D2</color>
+</resources>

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

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="loto_en">Intelligent Lock Control System</string>
+    <string name="tec_support">温州博士安全用品有限公司</string>
+    <string name="face_login">人脸登录</string>
+    <string name="card_login">刷卡登录</string>
+    <string name="fingerprint_login">指纹登录</string>
+    <string name="account_login">用户名登录</string>
+</resources>

+ 4 - 0
app/src/main/res/values/colors.xml

@@ -2,4 +2,8 @@
 <resources>
     <color name="black">#FF000000</color>
     <color name="white">#FFFFFFFF</color>
+    <color name="login_tip_circle_bg">#00ddfa</color>
+    <color name="login_method_tv_color">#D7D2D2</color>
+    <color name="color_7b7d7e">#7b7d7e</color>
+    <color name="color_1daeff">#1daeff</color>
 </resources>

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

@@ -1,3 +1,8 @@
 <resources>
-    <string name="app_name">ISCS_BASE_APP</string>
+    <string name="loto_en">Intelligent Lock Control System</string>
+    <string name="tec_support">温州博士安全用品有限公司</string>
+    <string name="face_login">人脸登录</string>
+    <string name="card_login">刷卡登录</string>
+    <string name="fingerprint_login">指纹登录</string>
+    <string name="account_login">用户名登录</string>
 </resources>

+ 9 - 0
build.gradle.kts

@@ -5,4 +5,13 @@ plugins {
     alias(libs.plugins.android.library) apply false
     alias(libs.plugins.jetbrains.kotlin.jvm) apply false
     alias(libs.plugins.kotlin.ksp) apply false
+}
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath("org.xerial:sqlite-jdbc:3.36.0.3")
+    }
 }

+ 2 - 0
data/build.gradle.kts

@@ -38,6 +38,7 @@ dependencies {
     implementation(libs.androidx.core.ktx)
     implementation(project(":shared"))
     api(project(":domain"))
+    api(libs.sik.extension.net)
     testImplementation(libs.junit)
     // 引入 Room Runtime 和 KTX
     implementation(libs.androidx.room.runtime)  // 即 androidx.room:room-runtime :contentReference[oaicite:1]{index=1}
@@ -45,4 +46,5 @@ dependencies {
 
     // 注解处理器(编译时生成 DAO/Database 实现)
     ksp(libs.androidx.room.compiler)           // 即 androidx.room:room-compiler :contentReference[oaicite:3]{index=3}
+
 }

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

@@ -0,0 +1,16 @@
+package com.grkj.data.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+
+/**
+ * 硬件数据库查询
+ */
+@Dao
+interface HardwareDao {
+    /**
+     * 根据工卡nfc查询用户id
+     */
+    @Query("select user_id from is_job_card where card_nfc = :cardNfc")
+    fun getUserIdByCardNfc(cardNfc: String): Int?
+}

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

@@ -0,0 +1,36 @@
+package com.grkj.data.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.grkj.data.model.dos.SysUserCharacteristicDo
+import com.grkj.data.model.dos.SysUserDo
+
+/**
+ * 用户数据库查询
+ */
+@Dao
+interface UserDao {
+    /**
+     * 根据用户名查询用户数据
+     */
+    @Query("select * from sys_user where user_name = :userName")
+    fun getUserInfoByUsername(userName: String): SysUserDo?
+
+    /**
+     * 根据userid查询用户信息
+     */
+    @Query("select * from sys_user where user_id = :userId")
+    fun getUserInfoByUserId(userId: String): SysUserDo?
+
+    /**
+     * 获取所有指纹数据
+     */
+    @Query("select * from sys_user_characteristic where type = 1")
+    fun getFingerprintData(): List<SysUserCharacteristicDo>
+
+    /**
+     * 获取所有人脸数据
+     */
+    @Query("select * from sys_user_characteristic where type = 2")
+    fun getFaceData(): List<SysUserCharacteristicDo>
+}

+ 21 - 0
data/src/main/java/com/grkj/data/data/MainDomainData.kt

@@ -0,0 +1,21 @@
+package com.grkj.data.data
+
+import com.grkj.data.model.dos.SysUserDo
+
+/**
+ * 登录之后的数据存储
+ */
+object MainDomainData {
+    /**
+     * 用户信息
+     */
+    @Volatile
+    var userInfo: SysUserDo? = null
+
+    /**
+     * 清除数据
+     */
+    fun clear() {
+        userInfo = null
+    }
+}

+ 43 - 0
data/src/main/java/com/grkj/data/database/ISCSDatabase.kt

@@ -0,0 +1,43 @@
+package com.grkj.data.database
+
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import com.grkj.data.dao.HardwareDao
+import com.grkj.data.dao.UserDao
+import com.grkj.data.model.dos.IsJobCardDo
+import com.grkj.data.model.dos.SysUserCharacteristicDo
+import com.grkj.data.model.dos.SysUserDo
+import com.sik.sikcore.SIKCore
+
+/**
+ * 本地数据库
+ */
+@Database(
+    entities = [IsJobCardDo::class, SysUserDo::class, SysUserCharacteristicDo::class],
+    version = ISCSMigrations.VERSION
+)
+abstract class ISCSDatabase : RoomDatabase() {
+    companion object {
+        /**
+         * 单例
+         */
+        @JvmStatic
+        val instance: ISCSDatabase by lazy {
+            Room.databaseBuilder(
+                SIKCore.getApplication(),
+                ISCSDatabase::class.java,
+                "iscs_database"
+            )
+                // 如需在主线程查询,可取消下行注释(不推荐):
+                // .allowMainThreadQueries()
+                .fallbackToDestructiveMigration(true)
+                .build()
+        }
+    }
+
+    abstract fun userDao(): UserDao
+
+    abstract fun hardwareDao(): HardwareDao
+}

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

@@ -0,0 +1,11 @@
+package com.grkj.data.database
+
+/**
+ * 数据库升级、版本相关信息
+ */
+object ISCSMigrations {
+    /**
+     * 版本号
+     */
+    const val VERSION = 1
+}

+ 50 - 0
data/src/main/java/com/grkj/data/model/dos/BaseBean.kt

@@ -0,0 +1,50 @@
+package com.grkj.data.model.dos
+
+import androidx.room.ColumnInfo
+import androidx.room.Ignore
+import com.sik.sikcore.date.TimeUtils
+import java.io.Serializable
+
+/**
+ * Bean基类
+ *
+ * @author ruoyi
+ */
+open class BaseBean : Serializable {
+    @ColumnInfo("create_by")
+    var createBy: String? = null
+
+    @ColumnInfo("create_time")
+    var createTime: String? = TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+
+    /**
+     * 更新者
+     */
+    @ColumnInfo("update_by")
+    var updateBy: String? = null
+
+    /**
+     * 更新时间
+     */
+    @ColumnInfo("update_time")
+    var updateTime: String? = TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+
+    /**
+     * 备注
+     */
+    @ColumnInfo("remark")
+    var remark: String? = null
+
+    @Ignore
+    var paramMap: MutableMap<String?, Any?>? = null
+        get() {
+            if (field == null) {
+                field = HashMap<String?, Any?>()
+            }
+            return field
+        }
+
+    companion object {
+        private const val serialVersionUID = 1L
+    }
+}

+ 43 - 0
data/src/main/java/com/grkj/data/model/dos/IsJobCardDo.kt

@@ -0,0 +1,43 @@
+package com.grkj.data.model.dos
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.sik.sikcore.date.TimeUtils
+
+/**
+ * 工卡
+ */
+@Entity(tableName = "is_job_card")
+class IsJobCardDo : BaseBean() {
+    @PrimaryKey
+    @ColumnInfo("card_id")
+    var cardId: Int = 0
+
+    @ColumnInfo("card_code")
+    var cardCode: String = ""
+
+    @ColumnInfo("hardware_id")
+    var hardwareId: Int? = null
+
+    @ColumnInfo("card_nfc")
+    var cardNfc: String? = null
+
+    @ColumnInfo("card_type")
+    var cardType: Int? = null
+
+    @ColumnInfo("user_id")
+    var userId: Int? = null
+
+    @ColumnInfo("user_name")
+    var userName: String? = null
+
+    @ColumnInfo("del_flag")
+    var delFlag: String? = null
+
+    @ColumnInfo("ex_status")
+    var exStatus: String? = null
+
+    @ColumnInfo("ex_remark")
+    var exRemark: String? = null
+}

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

@@ -0,0 +1,55 @@
+package com.grkj.data.model.dos
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+/**
+ * 用户特征(指纹、面部)对象 sys_user_characteristic
+ *
+ * @author cgj
+ * @date 2025-03-10
+ */
+@Entity("sys_user_characteristic")
+class SysUserCharacteristicDo : BaseBean() {
+    @PrimaryKey
+    @ColumnInfo("record_id")
+    var recordId: Long? = null
+
+    @ColumnInfo("user_id")
+    var userId: Long? = null
+
+    @ColumnInfo("type")
+    var type: String? = null
+
+    @ColumnInfo("content")
+    var content: String? = null
+
+    @ColumnInfo("image_url")
+    var imageUrl: String? = null
+
+    @ColumnInfo("image_path")
+    var imagePath: String? = null
+
+    @ColumnInfo("order_num")
+    var orderNum: Int? = null
+
+    @ColumnInfo("del_flag")
+    var delFlag: String? = null
+
+    @ColumnInfo("mat_rows")
+    var matRows: Int? = null
+
+    @ColumnInfo("mat_cols")
+    var matCols: Int? = null
+
+    @ColumnInfo("mat_type")
+    var matType: Int? = null
+
+    @ColumnInfo("mat_data")
+    var matData: String? = null
+
+    companion object {
+        private const val serialVersionUID = 1L
+    }
+}

+ 56 - 0
data/src/main/java/com/grkj/data/model/dos/SysUserDo.kt

@@ -0,0 +1,56 @@
+package com.grkj.data.model.dos
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.sik.sikcore.date.TimeUtils
+
+@Entity(tableName = "sys_user")
+class SysUserDo {
+    @PrimaryKey
+    @ColumnInfo("user_id")
+    var userId: Int = 0
+
+    @ColumnInfo("dept_id")
+    var deptId: Int? = 0
+
+    @ColumnInfo("user_name")
+    var userName: String = ""
+
+    @ColumnInfo("nick_name")
+    var nickName: String? = null
+
+    @ColumnInfo("user_type")
+    var userType: String? = ""
+
+    @ColumnInfo("email")
+    var email: String? = null
+
+    @ColumnInfo("phonenumber")
+    var phoneNumber: String? = ""
+
+    @ColumnInfo("sex")
+    var sex: String? = ""
+
+    @ColumnInfo("avatar")
+    var avatar: String? = ""
+
+    @ColumnInfo("password")
+    var password: String? = ""
+
+    @ColumnInfo("key_code")
+    var keyCode: String? = null
+
+    @ColumnInfo("status")
+    var status: String? = null
+
+    @ColumnInfo("del_flag")
+    var delFlag: String? = null
+
+    @ColumnInfo("login_ip")
+    var loginIp: String? = null
+
+    @ColumnInfo("login_date")
+    var loginData: String? = null
+
+}

+ 5 - 1
data/src/main/java/com/grkj/data/HardwareRepository.kt → data/src/main/java/com/grkj/data/repository/HardwareRepository.kt

@@ -1,5 +1,7 @@
-package com.grkj.data
+package com.grkj.data.repository
 
+import com.grkj.data.dao.HardwareDao
+import com.grkj.data.database.ISCSDatabase
 import com.grkj.domain.entity.req.LockPointUpdateReq
 import com.grkj.domain.entity.req.LockTakeUpdateReq
 import com.grkj.domain.entity.res.CabinetSlotsRes
@@ -13,6 +15,8 @@ import com.grkj.domain.repository.IHardwareRepository
  * 硬件仓储
  */
 class HardwareRepository : IHardwareRepository {
+    private val hardwareDao: HardwareDao by lazy { ISCSDatabase.instance.hardwareDao() }
+
     /**
      * 获取锁信息
      */

+ 1 - 1
data/src/main/java/com/grkj/data/StepRepository.kt → data/src/main/java/com/grkj/data/repository/StepRepository.kt

@@ -1,4 +1,4 @@
-package com.grkj.data
+package com.grkj.data.repository
 
 import com.grkj.domain.entity.res.StepDetailRes
 import com.grkj.domain.repository.IStepRepository

+ 1 - 1
data/src/main/java/com/grkj/data/TicketRepository.kt → data/src/main/java/com/grkj/data/repository/TicketRepository.kt

@@ -1,4 +1,4 @@
-package com.grkj.data
+package com.grkj.data.repository
 
 import com.grkj.domain.entity.res.TicketDetailRes
 import com.grkj.domain.repository.ITicketRepository

+ 116 - 0
data/src/main/java/com/grkj/data/repository/UserRepository.kt

@@ -0,0 +1,116 @@
+package com.grkj.data.repository
+
+import android.util.Base64
+import com.grkj.data.dao.HardwareDao
+import com.grkj.data.dao.UserDao
+import com.grkj.data.data.MainDomainData
+import com.grkj.data.database.ISCSDatabase
+import com.grkj.data.model.dos.SysUserDo
+import com.grkj.domain.repository.IUserRepository
+import com.grkj.shared.utils.BCryptUtils
+import com.grkj.shared.utils.BiometricVerifier
+import com.sik.sikcore.extension.file
+import com.sik.sikcore.string.RegexUtils
+
+/**
+ * 用户仓储层
+ */
+class UserRepository : IUserRepository {
+    private val hardwareDao: HardwareDao by lazy { ISCSDatabase.instance.hardwareDao() }
+
+    private val userDao: UserDao by lazy { ISCSDatabase.instance.userDao() }
+
+    override fun loginWithAccount(
+        username: String, password: String
+    ): Boolean {
+        MainDomainData.userInfo = SysUserDo().apply {
+            nickName = "罗成"
+        }
+        return true
+        var sysUserDo = userDao.getUserInfoByUsername(username)
+        if (sysUserDo == null) {
+            return false
+        }
+        val matched = BCryptUtils.matchPassword(password, sysUserDo?.password ?: "")
+        if (matched) {
+            MainDomainData.userInfo = sysUserDo
+        }
+        return matched
+    }
+
+    override fun loginWithCard(cardnfc: String): Boolean {
+        val userId = hardwareDao.getUserIdByCardNfc(cardnfc)?.toString()
+        return userId?.let {
+            val sysUserDo = userDao.getUserInfoByUserId(it)
+            if (sysUserDo != null) {
+                MainDomainData.userInfo = sysUserDo
+            }
+            return userDao.getUserInfoByUserId(it) != null
+        } ?: false
+    }
+
+    override fun loginWithFingerprint(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 != null) {
+                if (BiometricVerifier.verifyFingerprint(fingerprint, fingerprintData.content!!)) {
+                    hasFingerprint = true
+                    userId = fingerprintData.userId.toString()
+                    break
+                }
+            }
+        }
+        if (userId != null) {
+            val sysUserDo = userDao.getUserInfoByUserId(userId)
+            if (sysUserDo != null) {
+                MainDomainData.userInfo = sysUserDo
+                hasFingerprint = true
+            } else {
+                hasFingerprint = false
+            }
+        } else {
+            hasFingerprint = false
+        }
+        return hasFingerprint
+    }
+
+    override fun loginWithFace(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 != null) {
+                if (BiometricVerifier.verifyFaceArcSoft(face, faceData.content!!)) {
+                    hasFace = true
+                    userId = faceData.userId.toString()
+                    break
+                }
+            }
+        }
+        if (userId != null) {
+            val sysUserDo = userDao.getUserInfoByUserId(userId)
+            if (sysUserDo != null) {
+                MainDomainData.userInfo = sysUserDo
+                hasFace = true
+            } else {
+                hasFace = false
+            }
+        } else {
+            hasFace = false
+        }
+        return hasFace
+    }
+
+    override fun logout(): Boolean {
+        MainDomainData.clear()
+        return true
+    }
+}

+ 11 - 0
domain/src/main/java/com/grkj/domain/entity/local/LoginMenuEntity.kt

@@ -0,0 +1,11 @@
+package com.grkj.domain.entity.local
+
+/**
+ * 登录菜单实体
+ */
+data class LoginMenuEntity(
+    val menuIconId: Int,
+    val menuText: String,
+    val needTip: Boolean = false,
+    val loginType: Int
+)

+ 12 - 0
domain/src/main/java/com/grkj/domain/entity/local/TabConfig.kt

@@ -0,0 +1,12 @@
+package com.grkj.domain.entity.local
+
+/**
+ * 底部页签
+ */
+data class TabConfig(
+    val id: Int,        // 唯一的 menuItemId
+    val graphRes: Int,        // nav graph resource id
+    val title: String,
+    val icon: Int,
+    val permission: String    // 对应的运行时权限标识
+)

+ 31 - 0
domain/src/main/java/com/grkj/domain/repository/IUserRepository.kt

@@ -0,0 +1,31 @@
+package com.grkj.domain.repository
+
+/**
+ * 用户仓储层
+ */
+interface IUserRepository {
+    /**
+     * 账号密码登录
+     */
+    fun loginWithAccount(username: String, password: String): Boolean
+
+    /**
+     * 刷卡登录
+     */
+    fun loginWithCard(cardNo: String): Boolean
+
+    /**
+     * 指纹登录
+     */
+    fun loginWithFingerprint(fingerprint: String): Boolean
+
+    /**
+     * 人脸登录
+     */
+    fun loginWithFace(face: String): Boolean
+
+    /**
+     * 退出登录
+     */
+    fun logout(): Boolean
+}

+ 2 - 0
gradle/libs.versions.toml

@@ -6,6 +6,7 @@ junit = "4.13.2"
 junitVersion = "1.1.5"
 espressoCore = "3.5.1"
 appcompat = "1.6.1"
+avi_library = "2.1.3"
 material = "1.10.0"
 activity = "1.8.0"
 constraintlayout = "2.1.4"
@@ -30,6 +31,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
 androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
 androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+avi-library = { module = "com.wang.avi:library", version.ref = "avi_library" }
 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
 androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }

+ 1 - 0
settings.gradle.kts

@@ -17,6 +17,7 @@ dependencyResolutionManagement {
         google()
         mavenCentral()
         maven("https://jitpack.io")
+        maven("https://maven.aliyun.com/repository/public")
     }
 }
 

+ 21 - 0
shared/build.gradle.kts

@@ -12,6 +12,11 @@ android {
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles("consumer-rules.pro")
+        externalNativeBuild {
+            cmake {
+                cppFlags += ""
+            }
+        }
     }
 
     buildTypes {
@@ -30,6 +35,12 @@ android {
     kotlinOptions {
         jvmTarget = "11"
     }
+    externalNativeBuild {
+        cmake {
+            path = file("src/main/cpp/CMakeLists.txt")
+            version = "3.22.1"
+        }
+    }
 }
 
 dependencies {
@@ -37,5 +48,15 @@ dependencies {
     implementation(libs.androidx.core.ktx)
     api(libs.sik.extension.core)
     api(libs.sik.extension.encrypt)
+    api("org.mindrot:jbcrypt:0.4")
+    implementation("com.machinezoo.sourceafis:sourceafis:3.15.0")
     testImplementation(libs.junit)
+    api(
+        fileTree(
+            mapOf(
+                "dir" to "libs",
+                "include" to listOf("*.jar", "*.aar")
+            )
+        )
+    )
 }

+ 0 - 0
ui-base/libs/adh_series_sdk.jar → shared/libs/adh_series_sdk.jar


+ 0 - 0
ui-base/libs/arcsoft_face.jar → shared/libs/arcsoft_face.jar


+ 0 - 0
ui-base/libs/arcsoft_image_util.jar → shared/libs/arcsoft_image_util.jar


+ 0 - 0
ui-base/libs/zkandroidcore.jar → shared/libs/zkandroidcore.jar


+ 0 - 0
ui-base/libs/zkandroidfingerservice.jar → shared/libs/zkandroidfingerservice.jar


+ 0 - 0
ui-base/libs/zkandroidfpreader.jar → shared/libs/zkandroidfpreader.jar


+ 39 - 0
shared/src/main/java/com/grkj/shared/config/AESConfig.kt

@@ -0,0 +1,39 @@
+package com.grkj.shared.config
+
+import com.sik.sikencrypt.EncryptAlgorithm
+import com.sik.sikencrypt.EncryptMode
+import com.sik.sikencrypt.EncryptPadding
+import com.sik.sikencrypt.IEncryptConfig
+
+/**
+ * AES加密配置
+ */
+class AESConfig : IEncryptConfig {
+
+    init {
+        System.loadLibrary("iscs_shared")
+    }
+
+    companion object{
+        @JvmStatic
+        val defaultConfig: AESConfig by lazy { AESConfig() }
+    }
+
+    override fun algorithm(): EncryptAlgorithm {
+        return EncryptAlgorithm.AES
+    }
+
+    override fun key(): ByteArray {
+        return aesKey().toByteArray()
+    }
+
+    override fun mode(): EncryptMode {
+        return EncryptMode.CBC
+    }
+
+    override fun padding(): EncryptPadding {
+        return EncryptPadding.PKCS5Padding
+    }
+
+    external fun aesKey(): String
+}

+ 8 - 3
ui-base/src/main/java/com/grkj/ui_base/data/Constants.kt → shared/src/main/java/com/grkj/shared/config/Constants.kt

@@ -1,4 +1,4 @@
-package com.grkj.ui_base.data
+package com.grkj.shared.config
 
 import android.Manifest
 
@@ -17,11 +17,16 @@ object Constants {
         Manifest.permission.BLUETOOTH_ADVERTISE,
         Manifest.permission.BLUETOOTH_PRIVILEGED,
         Manifest.permission.READ_EXTERNAL_STORAGE,
-        Manifest.permission.WRITE_EXTERNAL_STORAGE
-    )
+        Manifest.permission.WRITE_EXTERNAL_STORAGE,
+        Manifest.permission.READ_PHONE_STATE,
+        Manifest.permission.ACCESS_FINE_LOCATION,
+        Manifest.permission.ACCESS_COARSE_LOCATION,
+        Manifest.permission.CAMERA,
+    ).toTypedArray()
 
     const val USER_TYPE_LOCKER = "0"                // 上锁人
     const val USER_TYPE_COLOCKER = "1"              // 共锁人
+
     /*************************  虹软ArcSoft  *************************/
     const val APP_ID = "FTN3G4pk8n2RKwjD955sRapRjbYQFefwhHd4sBZMYEz6"
     const val SDK_KEY = "BjJomNU2bQc2SYhT7NNqwvFd3WD4wTqecifNcDRuKD5G"

+ 5 - 9
ui-base/src/main/java/com/grkj/ui_base/utils/ArcSoftUtil.kt → shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt

@@ -1,4 +1,4 @@
-package com.grkj.ui_base.utils
+package com.grkj.shared.utils
 
 import android.Manifest
 import android.content.Context
@@ -18,12 +18,10 @@ import com.arcsoft.face.GenderInfo
 import com.arcsoft.face.LivenessInfo
 import com.arcsoft.face.enums.DetectFaceOrientPriority
 import com.arcsoft.face.enums.DetectMode
-import com.grkj.ui_base.R
-import com.grkj.ui_base.data.Constants
-import com.grkj.ui_base.utils.face.arcsoft.CameraHelper
-import com.grkj.ui_base.utils.face.arcsoft.CameraListener
-import com.grkj.ui_base.utils.face.arcsoft.NV21ToBitmap
-import com.kongzue.dialogx.dialogs.PopTip
+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 org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
@@ -66,7 +64,6 @@ object ArcSoftUtil {
             else -> {
                 isActivated = false
                 logger.error("checkActiveStatus : active failed $activeCode")
-                PopTip.tip(CommonUtils.getStr(R.string.face_active_fail))
             }
         }
         val activeFileInfo = ActiveFileInfo()
@@ -88,7 +85,6 @@ object ArcSoftUtil {
         )
         logger.info("initEngine:  init: $afCode")
         if (afCode != ErrorInfo.MOK) {
-            PopTip.tip(CommonUtils.getStr(R.string.face_active_fail))
         }
     }
 

+ 24 - 0
shared/src/main/java/com/grkj/shared/utils/BCryptUtils.kt

@@ -0,0 +1,24 @@
+package com.grkj.shared.utils
+
+import org.mindrot.jbcrypt.BCrypt
+
+/**
+ * BC编码工具
+ */
+object BCryptUtils {
+    /**
+     * 加密密码
+     */
+    @JvmStatic
+    fun encryptPassword(password: String): String {
+        return BCrypt.hashpw(password, BCrypt.gensalt())
+    }
+
+    /**
+     * 验证密码
+     */
+    @JvmStatic
+    fun matchPassword(password: String, savePassoword: String): Boolean {
+        return BCrypt.checkpw(password, savePassoword)
+    }
+}

+ 176 - 0
shared/src/main/java/com/grkj/shared/utils/BiometricVerifier.kt

@@ -0,0 +1,176 @@
+package com.grkj.shared.utils
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Build
+import android.util.Base64
+import androidx.annotation.RequiresApi
+import com.arcsoft.face.ErrorInfo
+import com.arcsoft.face.FaceEngine
+import com.arcsoft.face.FaceFeature
+import com.arcsoft.face.FaceInfo
+import com.arcsoft.face.FaceSimilar
+import com.arcsoft.face.enums.DetectFaceOrientPriority
+import com.arcsoft.face.enums.DetectMode
+import com.grkj.shared.config.Constants
+import com.machinezoo.sourceafis.FingerprintMatcher
+import com.machinezoo.sourceafis.FingerprintTemplate
+import com.sik.sikcore.SIKCore
+import java.util.concurrent.atomic.AtomicBoolean
+
+object BiometricVerifier {
+
+    // —— 指纹比对 ——
+    /**
+     * @param b64a 指纹1 的 Base64 图像(灰度或彩色均可)
+     * @param b64b 指纹2 的 Base64 图像
+     * @return 两者是否认为同一人(score >= threshold)
+     */
+    fun verifyFingerprint(b64a: String, b64b: String, threshold: Double = 40.0): Boolean {
+        // 1. 解码成 Bitmap
+        val bmpA = Base64.decode(b64a, Base64.DEFAULT)
+        val bmpB = Base64.decode(b64b, Base64.DEFAULT)
+
+        // 2. 构建指纹模板
+        val templateA = FingerprintTemplate()
+            .dpi(500.0) // 根据图像 DPI 调整
+            .create(bmpA)
+        val templateB = FingerprintTemplate()
+            .dpi(500.0)
+            .create(bmpB)
+
+        // 3. 比对
+        val score = FingerprintMatcher(templateA).match(templateB)
+        return score >= threshold
+    }
+
+
+    // —— 人脸比对(ArcSoft) ——
+    private lateinit var faceEngine: FaceEngine
+    private val initDone = AtomicBoolean(false)
+
+    /** 初始化 ArcSoft SDK,只调用一次 */
+    fun initArcSoft(
+        context: Context = SIKCore.getApplication(),
+        appId: String = Constants.APP_ID,
+        sdkKey: String = Constants.SDK_KEY
+    ) {
+        if (initDone.compareAndSet(false, true)) {
+            faceEngine = FaceEngine()
+            val code = faceEngine.init(
+                context,
+                DetectMode.ASF_DETECT_MODE_IMAGE,
+                DetectFaceOrientPriority.ASF_OP_ALL_OUT,
+                16,    // 最大检测人脸数
+                16,    // 线程数
+                FaceEngine.ASF_FACE_RECOGNITION or
+                        FaceEngine.ASF_FACE_DETECT
+            )
+            if (code != ErrorInfo.MOK) {
+                throw RuntimeException("ArcSoft 初始化失败, code=$code")
+            }
+            // 激活:只需在首次运行时激活
+            val activeCode = FaceEngine.activeOnline(context, appId, sdkKey)
+            if (activeCode != ErrorInfo.MOK && activeCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
+                throw RuntimeException("ArcSoft 激活失败, code=$activeCode")
+            }
+        }
+    }
+
+    /**
+     * 比对两张 Base64 人脸,返回是否同人
+     * @param threshold 阈值,一般设置 0.7f 左右
+     */
+    fun verifyFaceArcSoft(
+        b64a: String,
+        b64b: String,
+        threshold: Float = 0.7f
+    ): Boolean {
+        // 1. 解码成 Bitmap
+        val bmpA = decodeBase64ToBitmap(b64a)
+        val bmpB = decodeBase64ToBitmap(b64b)
+
+        // 2. 人脸检测
+        val facesA = mutableListOf<FaceInfo>()
+        val facesB = mutableListOf<FaceInfo>()
+        val imgA = bitmapToBgr24(bmpA)
+        val imgB = bitmapToBgr24(bmpB)
+
+        faceEngine.detectFaces(
+            imgA,
+            bmpA.width,
+            bmpA.height,
+            FaceEngine.CP_PAF_BGR24,
+            facesA
+        )
+        faceEngine.detectFaces(
+            imgB,
+            bmpB.width,
+            bmpB.height,
+            FaceEngine.CP_PAF_BGR24,
+            facesB
+        )
+
+        if (facesA.isEmpty() || facesB.isEmpty()) {
+            return false
+        }
+
+        // 3. 特征提取
+        val ftA = FaceFeature()
+        val ftB = FaceFeature()
+        faceEngine.extractFaceFeature(
+            imgA, bmpA.width, bmpA.height, FaceEngine.CP_PAF_BGR24,
+            facesA[0], ftA
+        )
+        faceEngine.extractFaceFeature(
+            imgB, bmpB.width, bmpB.height, FaceEngine.CP_PAF_BGR24,
+            facesB[0], ftB
+        )
+
+        // 4. 特征比对
+        val compareResult = FaceSimilar()
+        val compareCode = faceEngine.compareFaceFeature(ftA, ftB, compareResult)
+        if (compareCode != ErrorInfo.MOK) {
+            return false
+        }
+        // compareResult.score 在 [0,1],越大越像
+        return compareResult.score >= threshold
+    }
+
+    /** Bitmap ARGB_8888 -> BGR24 byte[] */
+    private fun bitmapToBgr24(bitmap: Bitmap): ByteArray {
+        val width = bitmap.width
+        val height = bitmap.height
+        val pixels = IntArray(width * height)
+        bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
+
+        val bgr = ByteArray(width * height * 3)
+        var idx = 0
+        for (pixel in pixels) {
+            // pixel is ARGB
+            val r = (pixel shr 16 and 0xFF).toByte()
+            val g = (pixel shr 8 and 0xFF).toByte()
+            val b = (pixel and 0xFF).toByte()
+            bgr[idx++] = b
+            bgr[idx++] = g
+            bgr[idx++] = r
+        }
+        return bgr
+    }
+
+    private fun decodeBase64ToBitmap(b64: String): Bitmap {
+        val bytes = Base64.decode(b64, Base64.DEFAULT)
+        return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
+    }
+
+    /** 释放资源,Activity/Fragment 销毁时调用 */
+    @RequiresApi(Build.VERSION_CODES.O)
+    fun releaseArcSoft() {
+        if (initDone?.get() != null) {
+            faceEngine.unInit()
+            initDone.set(false)
+        }
+    }
+
+}

+ 1 - 1
ui-base/src/main/java/com/grkj/ui_base/utils/face/arcsoft/CameraHelper.java → shared/src/main/java/com/grkj/shared/utils/face/arcsoft/CameraHelper.java

@@ -1,4 +1,4 @@
-package com.grkj.ui_base.utils.face.arcsoft;
+package com.grkj.shared.utils.face.arcsoft;
 
 import android.graphics.ImageFormat;
 import android.graphics.Point;

+ 1 - 1
ui-base/src/main/java/com/grkj/ui_base/utils/face/arcsoft/CameraListener.java → shared/src/main/java/com/grkj/shared/utils/face/arcsoft/CameraListener.java

@@ -1,4 +1,4 @@
-package com.grkj.ui_base.utils.face.arcsoft;
+package com.grkj.shared.utils.face.arcsoft;
 
 import android.hardware.Camera;
 

+ 1 - 1
ui-base/src/main/java/com/grkj/ui_base/utils/face/arcsoft/FaceUtils.kt → shared/src/main/java/com/grkj/shared/utils/face/arcsoft/FaceUtils.kt

@@ -1,4 +1,4 @@
-package com.grkj.ui_base.utils.face.arcsoft
+package com.grkj.shared.utils.face.arcsoft
 
 import android.content.Context
 import android.graphics.Bitmap

+ 1 - 1
ui-base/src/main/java/com/grkj/ui_base/utils/face/arcsoft/NV21ToBitmap.java → shared/src/main/java/com/grkj/shared/utils/face/arcsoft/NV21ToBitmap.java

@@ -1,4 +1,4 @@
-package com.grkj.ui_base.utils.face.arcsoft;
+package com.grkj.shared.utils.face.arcsoft;
 
 import android.content.Context;
 import android.graphics.Bitmap;

+ 2 - 8
ui-base/build.gradle.kts

@@ -52,17 +52,11 @@ dependencies {
     implementation(libs.android.navigation.dynamic.features.fragment)
     implementation(libs.kotlinx.serialization.json)
     implementation(libs.sik.camera)
+    api(libs.android.autosize)
+    api(libs.avi.library)
     api(libs.sik.extension.android)
     api(libs.dialogx)
     api(libs.fastble)
-    api(
-        fileTree(
-            mapOf(
-                "dir" to "libs",
-                "include" to listOf("*.jar", "*.aar")
-            )
-        )
-    )
     implementation(project(":data"))
     implementation(project(":shared"))
     testImplementation(libs.junit)

+ 55 - 18
ui-base/src/main/java/com/grkj/ui_base/base/BaseActivity.kt

@@ -1,24 +1,33 @@
 package com.grkj.ui_base.base
 
+import android.content.res.Configuration
+import android.content.res.Resources
 import android.os.Bundle
-import com.kongzue.dialogx.dialogs.PopTip
-import com.sik.sikandroid.permission.PermissionUtils
 import androidx.activity.enableEdgeToEdge
 import androidx.appcompat.app.AppCompatActivity
 import androidx.databinding.DataBindingUtil
 import androidx.databinding.ViewDataBinding
-import com.grkj.shared.model.EventBean
-import com.google.android.material.bottomnavigation.BottomNavigationView
-import androidx.navigation.fragment.NavHostFragment
 import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import com.grkj.shared.model.EventBean
+import com.grkj.ui_base.data.EventConstants
+import com.grkj.ui_base.dialog.LoadingDialog
+import com.grkj.ui_base.utils.event.LoadingEvent
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikandroid.permission.PermissionUtils
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
 
 /**
  * BaseActivity: 支持 ViewBinding, EventBus, 权限管理, 串口 & 蓝牙, 以及 Navigation 多 Graph 切换 & BottomNav
  */
 abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity() {
+    protected val logger: Logger = LoggerFactory.getLogger(this::class.java)
+
     protected lateinit var binding: V
 
     /** 是否启用 Navigation 多 Graph 与 BottomNav */
@@ -26,7 +35,12 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity() {
 
     /** NavHostFragment 容器 ID,子类返回对应 fragment 布局 ID */
     protected open fun navHostFragmentId(): Int = 0
-    protected lateinit var navController: NavController
+    protected val navController: NavController by lazy {
+        val host = supportFragmentManager
+            .findFragmentById(navHostFragmentId())
+                as NavHostFragment
+        host.navController
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -38,9 +52,10 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity() {
         if (!EventBus.getDefault().isRegistered(this)) {
             EventBus.getDefault().register(this)
         }
-
         initView()
-        if (enableNavigation()) setupNavController()
+        if (enableNavigation()){
+
+        }
         initData()
         initListeners()
         initObservers()
@@ -57,12 +72,6 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity() {
         )
     }
 
-    /** 初始化 NavController */
-    private fun setupNavController() {
-        val host = supportFragmentManager.findFragmentById(navHostFragmentId()) as NavHostFragment
-        navController = host.navController
-    }
-
     /** 动态切换 Nav Graph */
     protected fun replaceNavGraph(graphId: Int) {
         navController.setGraph(graphId)
@@ -83,7 +92,18 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity() {
 
     /** EventBus 事件,子类可重写 */
     @Subscribe(threadMode = ThreadMode.MAIN)
-    protected open fun onEvent(event: EventBean<Any>) {
+    open fun onEvent(event: EventBean<Any>) {
+        when (event.code) {
+            EventConstants.EVENT_LOADING_CODE -> {
+                (event.data as LoadingEvent).apply {
+                    if (isShow) {
+                        showLoading(msg)
+                    } else {
+                        hideLoading()
+                    }
+                }
+            }
+        }
     }
 
     override fun onDestroy() {
@@ -94,13 +114,13 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity() {
     }
 
     /** 显示加载框,子类实现 */
-    protected fun showLoading(msg: String) {
-        // TODO: 子类实现统一 Loading 样式
+    protected fun showLoading(msg: String?) {
+        LoadingDialog.show(msg)
     }
 
     /** 隐藏加载框,子类实现 */
     protected fun hideLoading() {
-        // TODO: 子类实现
+        LoadingDialog.hide()
     }
 
     /** 通用吐司 */
@@ -108,6 +128,23 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity() {
         PopTip.tip(msg)
     }
 
+
+    /**
+     * 判断当前屏幕方向
+     * @return true: 横屏;false: 竖屏
+     */
+    protected fun isLandscape(resources: Resources = this.resources): Boolean {
+        return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+    }
+
+    /**
+     * 判断当前屏幕方向
+     * @return true: 竖屏;false: 横屏
+     */
+    protected fun isPortrait(resources: Resources = this.resources): Boolean {
+        return resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
+    }
+
     /** 子类必须实现:布局资源 ID */
     protected abstract fun getLayoutId(): Int
 

+ 5 - 4
ui-base/src/main/java/com/grkj/ui_base/base/BaseFragment.kt

@@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment
 import androidx.navigation.NavController
 import androidx.navigation.fragment.findNavController
 import com.grkj.shared.model.EventBean
+import com.grkj.ui_base.utils.event.LoadingEvent
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikandroid.permission.PermissionUtils
 import org.greenrobot.eventbus.EventBus
@@ -70,7 +71,7 @@ abstract class BaseFragment<V : ViewDataBinding> : Fragment() {
 
     /** EventBus 事件,子类可重写 */
     @Subscribe(threadMode = ThreadMode.MAIN)
-    protected open fun onEvent(event: EventBean<Any>) {
+    open fun onEvent(event: EventBean<Any>) {
     }
 
     override fun onDestroyView() {
@@ -81,13 +82,13 @@ abstract class BaseFragment<V : ViewDataBinding> : Fragment() {
     }
 
     /** 显示加载框,子类实现 */
-    protected fun showLoading(msg: String) {
-        // TODO: 子类实现统一 Loading
+    protected fun showLoading(msg: String?) {
+        LoadingEvent.sendLoadingEvent(msg)
     }
 
     /** 隐藏加载框,子类实现 */
     protected fun hideLoading() {
-        // TODO
+        LoadingEvent.sendLoadingEvent()
     }
 
     /** 通用吐司 */

Some files were not shown because too many files changed in this diff