Ver Fonte

feat: 迁移部分模块

周文健 há 2 meses atrás
pai
commit
ab4bacb1e3
100 ficheiros alterados com 7030 adições e 251 exclusões
  1. 1 0
      app/build.gradle.kts
  2. 1 1
      app/src/main/AndroidManifest.xml
  3. 40 0
      app/src/main/assets/i18n/zh-CN.json
  4. 4 0
      app/src/main/assets/themes/Default/icons/audit.svg
  5. 1 1
      app/src/main/assets/themes/Default/icons/back-up.svg
  6. 4 0
      app/src/main/assets/themes/Default/icons/boss.svg
  7. 1 0
      app/src/main/assets/themes/Default/icons/box-loading.svg
  8. 2 0
      app/src/main/assets/themes/Default/icons/boxes.svg
  9. 2 0
      app/src/main/assets/themes/Default/icons/chart-user.svg
  10. 1 0
      app/src/main/assets/themes/Default/icons/data-transfer.svg
  11. 4 0
      app/src/main/assets/themes/Default/icons/dialogue-exchange.svg
  12. 2 0
      app/src/main/assets/themes/Default/icons/diamond-exclamation.svg
  13. 2 0
      app/src/main/assets/themes/Default/icons/hexagon-exclamation.svg
  14. 26 0
      app/src/main/assets/themes/Default/icons/home.svg
  15. 1 0
      app/src/main/assets/themes/Default/icons/inventory-alt.svg
  16. 2 0
      app/src/main/assets/themes/Default/icons/light-emergency-on.svg
  17. 1 1
      app/src/main/assets/themes/Default/icons/limit-hand.svg
  18. 1 0
      app/src/main/assets/themes/Default/icons/log-file.svg
  19. 4 0
      app/src/main/assets/themes/Default/icons/member-list.svg
  20. 4 0
      app/src/main/assets/themes/Default/icons/money-income.svg
  21. 1 0
      app/src/main/assets/themes/Default/icons/play-alt.svg
  22. 2 0
      app/src/main/assets/themes/Default/icons/sensor-alert.svg
  23. 4 0
      app/src/main/assets/themes/Default/icons/shelves.svg
  24. 4 0
      app/src/main/assets/themes/Default/icons/store-buyer.svg
  25. 1 0
      app/src/main/assets/themes/Default/icons/supplier-alt.svg
  26. 4 0
      app/src/main/assets/themes/Default/icons/table-list.svg
  27. 1 0
      app/src/main/assets/themes/Default/icons/users.svg
  28. 2 2
      app/src/main/java/com/grkj/iscs_mc/features/login/activity/LoginActivity.kt
  29. 0 1
      app/src/main/java/com/grkj/iscs_mc/features/login/adapter/TwoFragmentAdapter.kt
  30. 1 1
      app/src/main/java/com/grkj/iscs_mc/features/login/dialog/LoginDialog.kt
  31. 166 80
      app/src/main/java/com/grkj/iscs_mc/features/login/fragment/LoginFragment.kt
  32. 0 20
      app/src/main/java/com/grkj/iscs_mc/features/login/fragment/MaterialShowFragment.kt
  33. 14 4
      app/src/main/java/com/grkj/iscs_mc/features/main/activity/MainActivity.kt
  34. 196 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/QuickEntranceConfigDialog.kt
  35. 285 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/TextDropDownDialog.kt
  36. 186 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/AddRoleDialog.kt
  37. 96 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/AddUserDialog.kt
  38. 66 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/FilterRoleDialog.kt
  39. 60 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/FilterUserDialog.kt
  40. 198 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/UpdateRoleDialog.kt
  41. 107 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/UpdateUserDialog.kt
  42. 41 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/user_info/AddFingerprintDialog.kt
  43. 11 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/AddRoleDataEntity.kt
  44. 40 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/ExceptionSourceDataEntity.kt
  45. 16 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/MenuItemEntity.kt
  46. 69 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/QuickEntranceMenuItemEntity.kt
  47. 98 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/RoleManageFunctionalPermissionsEntity.kt
  48. 13 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/UpdateRoleDataEntity.kt
  49. 0 18
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/DataHomeFragment.kt
  50. 0 19
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/ExceptionHomeFragment.kt
  51. 0 18
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/HomeFragment.kt
  52. 271 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/BackupAndRestoreFragment.kt
  53. 122 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/DataExportFragment.kt
  54. 103 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/DataManageHomeFragment.kt
  55. 301 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/RoleManageFragment.kt
  56. 249 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/UserManageFragment.kt
  57. 125 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/home/HomeFragment.kt
  58. 98 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/ResetPasswordFragment.kt
  59. 199 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/SetFaceFragment.kt
  60. 235 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/SetFingerprintFragment.kt
  61. 87 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/SetJobCardFragment.kt
  62. 99 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/SettingsFragment.kt
  63. 235 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/UserInfoFragment.kt
  64. 145 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/UserInfoHomeFragment.kt
  65. 1 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/MainViewModel.kt
  66. 111 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/BackupAndRestoreViewModel.kt
  67. 61 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/DataExportViewModel.kt
  68. 144 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/RoleManageViewModel.kt
  69. 118 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/UserManageViewModel.kt
  70. 45 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/home/HomeViewModel.kt
  71. 176 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/user_info/UserInfoViewModel.kt
  72. 0 20
      app/src/main/java/com/grkj/iscs_mc/features/manage/activity/ManageActivity.kt
  73. 0 20
      app/src/main/java/com/grkj/iscs_mc/features/material_exchange/activity/MaterialExchangeActivity.kt
  74. 5 0
      app/src/main/res/drawable/bg_card_color_menu_bg_radius_md.xml
  75. 6 0
      app/src/main/res/drawable/bg_home_card_num.xml
  76. 7 0
      app/src/main/res/drawable/circle_image_bg.xml
  77. 6 0
      app/src/main/res/drawable/common_divider_normal_space_horizontal.xml
  78. 6 0
      app/src/main/res/drawable/common_divider_normal_space_vertical.xml
  79. 6 0
      app/src/main/res/drawable/common_divider_small_space_horizontal.xml
  80. 6 0
      app/src/main/res/drawable/common_divider_small_space_vertical.xml
  81. 5 0
      app/src/main/res/drawable/icon_add_box.xml
  82. 13 0
      app/src/main/res/drawable/icon_add_box_disable.xml
  83. 13 0
      app/src/main/res/drawable/icon_add_box_enable.xml
  84. 9 0
      app/src/main/res/drawable/oval_shape.xml
  85. 446 0
      app/src/main/res/layout-land/fragment_backup_and_restore.xml
  86. 236 0
      app/src/main/res/layout-land/fragment_home.xml
  87. 128 27
      app/src/main/res/layout-land/fragment_login.xml
  88. 297 0
      app/src/main/res/layout-land/fragment_set_face.xml
  89. 425 0
      app/src/main/res/layout-land/fragment_user_info.xml
  90. 37 0
      app/src/main/res/layout-land/item_home_menu.xml
  91. 36 0
      app/src/main/res/layout-land/item_home_quick_entrance.xml
  92. 1 1
      app/src/main/res/layout-land/item_login_method.xml
  93. 55 0
      app/src/main/res/layout-land/item_quick_entrance_config.xml
  94. 54 0
      app/src/main/res/layout-land/item_quick_entrance_not_config.xml
  95. 0 8
      app/src/main/res/layout/activity_manage.xml
  96. 0 9
      app/src/main/res/layout/activity_material_exchange.xml
  97. 46 0
      app/src/main/res/layout/dialog_add_fingerprint.xml
  98. 233 0
      app/src/main/res/layout/dialog_add_role.xml
  99. 213 0
      app/src/main/res/layout/dialog_add_user.xml
  100. 30 0
      app/src/main/res/layout/dialog_drop_down_list.xml

+ 1 - 0
app/build.gradle.kts

@@ -76,6 +76,7 @@ dependencies {
     implementation(libs.sik.camera)
     implementation("com.google.dagger:hilt-android:2.56.2")
     ksp("com.google.dagger:hilt-android-compiler:2.56.2")
+    kapt("com.github.bingoogolapple.BGABadgeView-Android:compiler:1.2.0")
     implementation(project(":data"))
     implementation(project(":shared"))
     implementation(project(":ui-base"))

+ 1 - 1
app/src/main/AndroidManifest.xml

@@ -45,7 +45,7 @@
             android:windowSoftInputMode="stateHidden|adjustPan" />
 
         <activity
-            android:name=".features.manage.activity.ManageActivity"
+            android:name=".features.main.activity.MainActivity"
             android:configChanges="orientation|screenSize|keyboardHidden"
             android:exported="true"
             android:windowSoftInputMode="stateHidden|adjustPan" />

+ 40 - 0
app/src/main/assets/i18n/zh-CN.json

@@ -39,6 +39,11 @@
     "type": "text",
     "value": "取消"
   },
+  "back": {
+    "key": "back",
+    "type": "text",
+    "value": "返回"
+  },
   "administrator": {
     "key": "administrator",
     "type": "text",
@@ -163,5 +168,40 @@
     "key": "doing_login",
     "type": "text",
     "value": "正在登录······"
+  },
+  "material_data_title": {
+    "key": "material_data_title",
+    "type": "text",
+    "value": "物资数据"
+  },
+  "total_material": {
+    "key": "total_material",
+    "type": "text",
+    "value": "物资\n总数"
+  },
+  "borrow_material": {
+    "key": "borrow_material",
+    "type": "text",
+    "value": "借出\n物资"
+  },
+  "unborrowed_material": {
+    "key": "unborrowed_material",
+    "type": "text",
+    "value": "可借\n物资"
+  },
+  "you_have_borrowed_materials": {
+    "key": "you_have_borrowed_materials",
+    "type": "text",
+    "value": "您已借出{0}物资"
+  },
+  "account_login_tip": {
+    "key": "account_login_tip",
+    "type": "text",
+    "value": "请输入您的账号和密码,然后点击登录。"
+  },
+  "face_login_tip": {
+    "key": "face_login_tip",
+    "type": "text",
+    "value": "请将面部对准摄像头,完成人脸认证后,将自动登录。"
   }
 }

+ 4 - 0
app/src/main/assets/themes/Default/icons/audit.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="m12,7V.46c.913.346,1.753.879,2.465,1.59l3.484,3.486c.712.711,1.245,1.551,1.591,2.464h-6.54c-.552,0-1-.449-1-1Zm-3.416,12h-3.584c-.552,0-1-.448-1-1s.448-1,1-1h3.07c-.041-.328-.07-.66-.07-1s.022-.672.063-1h-3.063c-.552,0-1-.448-1-1s.448-1,1-1h3.593c.296-.728.699-1.398,1.185-2h-4.778c-.552,0-1-.448-1-1s.448-1,1-1h5.774c-.479-.531-.774-1.23-.774-2V.024c-.161-.011-.322-.024-.485-.024h-4.515C2.243,0,0,2.243,0,5v14c0,2.757,2.243,5,5,5h10c.114,0,.221-.026.333-.034-3.066-.254-5.641-2.234-6.749-4.966Zm12.327.497c.939-1.319,1.365-3.028.96-4.843-.494-2.211-2.277-3.996-4.49-4.481-4.365-.956-8.163,2.843-7.208,7.208.485,2.213,2.27,3.996,4.481,4.49,1.816.406,3.525-.021,4.843-.96l2.796,2.796c.39.39,1.024.39,1.414,0h0c.39-.39.39-1.024,0-1.414l-2.796-2.796Zm-4.135-1.033l-.004.004c-.744.744-2.058.746-2.823-.019l-1.515-1.575c-.372-.387-.372-.999,0-1.386h0c.393-.409,1.047-.409,1.44,0l1.495,1.553,2.9-2.971c.392-.402,1.038-.402,1.43,0h0c.38.388.38,1.009,0,1.397l-2.925,2.997Z"/>
+</svg>

+ 1 - 1
app/src/main/assets/themes/Default/icons/back-up.svg

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
-  <path d="m8.5,16c3.58,0,6.624-.839,8.5-2.173v1.74c0,1.621-3.635,3.434-8.5,3.434S0,17.188,0,15.566v-1.74c1.876,1.334,4.92,2.174,8.5,2.174ZM0,18.826v.96c0,2.767,4.276,4.214,8.5,4.214s8.5-1.447,8.5-4.214v-.96c-1.876,1.334-4.92,2.174-8.5,2.174s-6.624-.839-8.5-2.174ZM22,0v1.534c-1.078-.97-2.482-1.534-4-1.534-2.967,0-5.431,2.167-5.91,5h2.052c.447-1.72,1.999-3,3.858-3,1,0,1.928.367,2.644,1h-1.644v2h5V0h-2Zm-4,10c-.994,0-1.929-.368-2.646-1h1.646v-2h-5v5h2v-1.531c1.08.966,2.494,1.531,4,1.531,2.967,0,5.431-2.167,5.91-5h-2.052c-.447,1.72-1.999,3-3.858,3Zm-9.5-1c.513,0,1.012-.028,1.5-.074v-2.926c0-2.151.854-4.1,2.235-5.538-1.128-.293-2.393-.462-3.735-.462C3.806,0,0,2.015,0,4.5s3.806,4.5,8.5,4.5Zm0,5c.516,0,1.015-.024,1.5-.063v-3.003c-.489.04-.987.066-1.5.066-3.58,0-6.624-1.004-8.5-2.6v2.167c0,1.621,3.635,3.433,8.5,3.433Z"/>
+  <path d="m24,1.22v3.06c0,.398-.322.72-.72.72h-3.06c-.291,0-.554-.175-.665-.444-.112-.269-.05-.579.156-.785l.842-.842c-.705-.585-1.6-.929-2.553-.929-1.591,0-3.03.942-3.668,2.4-.22.505-.805.738-1.316.516-.506-.221-.737-.811-.516-1.317.956-2.187,3.115-3.6,5.5-3.6,1.494,0,2.899.555,3.976,1.506l.795-.796c.206-.206.515-.267.785-.156.269.111.444.374.444.665Zm-1.016,5.864c-.511-.219-1.096.012-1.316.516-.638,1.458-2.077,2.4-3.668,2.4-.953,0-1.848-.344-2.553-.929l.842-.842c.206-.206.268-.515.156-.785-.111-.269-.374-.444-.665-.444h-3.06c-.398,0-.72.322-.72.72v3.06c0,.291.175.554.444.665.27.111.579.05.785-.156l.795-.795c1.076.951,2.481,1.506,3.976,1.506,2.386,0,4.545-1.413,5.5-3.6.222-.506-.01-1.096-.516-1.317Zm-14.484,1.917c.513,0,1.012-.028,1.5-.074v-2.926c0-2.151.854-4.1,2.235-5.538-1.128-.293-2.393-.462-3.735-.462C3.806,0,0,2.015,0,4.5s3.806,4.5,8.5,4.5Zm0,5c1.57,0,3.009-.191,4.242-.501-.006,0-.012.002-.019.002-.349,0-.701-.067-1.038-.205-.978-.405-1.616-1.322-1.674-2.363-.493.041-.994.067-1.511.067-3.58,0-6.624-1.004-8.5-2.6v2.167c0,1.621,3.635,3.433,8.5,3.433Zm-8.5-.174v1.74c0,1.621,3.635,3.434,8.5,3.434s8.5-1.812,8.5-3.434v-1.74c-1.876,1.334-4.92,2.173-8.5,2.173s-6.624-.839-8.5-2.174Zm0,5v.96c0,2.767,4.276,4.214,8.5,4.214s8.5-1.447,8.5-4.214v-.96c-1.876,1.334-4.92,2.174-8.5,2.174s-6.624-.839-8.5-2.174Z"/>
 </svg>

+ 4 - 0
app/src/main/assets/themes/Default/icons/boss.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="m12,10c2.757,0,5-2.243,5-5S14.757,0,12,0s-5,2.243-5,5,2.243,5,5,5Zm2.413,1.38c2.639.839,4.689,3.011,5.352,5.724.111.454-.232.896-.7.896h-5.352l-1.116-3.897,1.816-2.723Zm-10.179,5.724c.663-2.713,2.713-4.885,5.352-5.724l1.816,2.723-1.116,3.897h-5.352c-.468,0-.81-.442-.7-.896Zm19.766,3.896c0,.552-.448,1-1,1h-1v1c0,.552-.448,1-1,1H3c-.552,0-1-.448-1-1v-1h-1c-.552,0-1-.448-1-1s.448-1,1-1h22c.552,0,1,.448,1,1Z"/>
+</svg>

+ 1 - 0
app/src/main/assets/themes/Default/icons/box-loading.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m13 12.326 4-2.294v3.454c0 .766-.395 1.478-1.044 1.884l-2.956 1.843zm-4.956 3.044 2.956 1.86v-4.908l-4-2.307v3.47c0 .766.395 1.478 1.044 1.884zm7.876-7.017-2.742-1.714c-.721-.45-1.635-.45-2.356 0l-2.742 1.714 3.92 2.24zm-11.852-1.734c.42 0 .837-.175 1.134-.517.188-.216.385-.423.592-.619.6-.571.624-1.521.053-2.121-.572-.602-1.521-.623-2.121-.053-.275.263-.539.538-.789.826-.543.625-.477 1.573.149 2.116.284.247.634.367.982.367zm18.539 5.396c-.803-.12-1.584.447-1.698 1.271-.042.287-.097.569-.163.846-.192.806.304 1.615 1.109 1.808.808.195 1.615-.304 1.809-1.109.088-.366.16-.739.214-1.118.118-.82-.451-1.581-1.271-1.699zm-2.676 5.366c-.42 0-.837.175-1.134.517-.188.216-.385.423-.592.619-.6.571-.624 1.521-.053 2.121.572.602 1.521.623 2.121.053.275-.263.539-.538.789-.826.543-.625.477-1.573-.149-2.116-.284-.247-.634-.367-.982-.367zm1.9-15.019-1.264 1.264c-2.24-2.288-5.314-3.626-8.567-3.626-1.167 0-2.32.167-3.429.498-.794.237-1.245 1.072-1.009 1.866.237.795 1.071 1.243 1.866 1.009.829-.247 1.694-.373 2.571-.373 2.45 0 4.767 1.013 6.45 2.744l-1.075 1.075c-.8.8-.234 2.169.898 2.169h4.283c.798 0 1.445-.647 1.445-1.445v-4.282c0-1.132-1.368-1.699-2.169-.898zm-20.438 9.623c.803.12 1.584-.447 1.698-1.271.042-.287.097-.569.163-.846.192-.806-.304-1.615-1.109-1.808-.808-.195-1.615.304-1.809 1.109-.088.366-.16.739-.214 1.118-.118.82.451 1.581 1.271 1.699zm13.179 8.643c-.829.247-1.694.373-2.571.373-2.45 0-4.767-1.013-6.45-2.744l1.075-1.075c.8-.8.234-2.169-.898-2.169h-4.283c-.798 0-1.445.647-1.445 1.445v4.283c0 1.132 1.368 1.698 2.169.898l1.264-1.264c2.24 2.289 5.315 3.627 8.567 3.627 1.167 0 2.32-.167 3.429-.498.794-.237 1.245-1.072 1.009-1.866-.237-.795-1.073-1.246-1.866-1.009z"/></svg>

+ 2 - 0
app/src/main/assets/themes/Default/icons/boxes.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M11,13H3c-1.657,0-3,1.343-3,3v5c0,1.657,1.343,3,3,3H11V13Zm-7.5,4h0c0-.552,.448-1,1-1h2c.552,0,1,.448,1,1h0c0,.552-.448,1-1,1h-2c-.552,0-1-.448-1-1Zm17.5-4H13v11h8c1.657,0,3-1.343,3-3v-5c0-1.657-1.343-3-3-3Zm-1.5,5h-2c-.552,0-1-.448-1-1h0c0-.552,.448-1,1-1h2c.552,0,1,.448,1,1h0c0,.552-.448,1-1,1ZM15,0h-6c-1.657,0-3,1.343-3,3V11h12V3c0-1.657-1.343-3-3-3Zm-2,5h-2c-.552,0-1-.448-1-1h0c0-.552,.448-1,1-1h2c.552,0,1,.448,1,1h0c0,.552-.448,1-1,1Z"/></svg>

+ 2 - 0
app/src/main/assets/themes/Default/icons/chart-user.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M14,23h0c0,.55-.45,1-1,1H1c-.55,0-1-.45-1-1H0c0-3.87,3.13-7,7-7h0c3.87,0,7,3.13,7,7Zm-3-13c0-2.21-1.79-4-4-4s-4,1.79-4,4,1.79,4,4,4,4-1.79,4-4ZM19,0H9c-2.69,0-4.87,2.14-4.98,4.8,.88-.51,1.89-.8,2.98-.8,2.74,0,5.04,1.84,5.76,4.35l2.24,2.24,3.59-3.59h-1.59c-.55,0-1-.45-1-1s.45-1,1-1h3c1.1,0,2,.9,2,2v3c0,.55-.45,1-1,1s-1-.45-1-1v-1.59l-3.59,3.59c-.78,.78-2.05,.78-2.83,0l-.72-.72s0,.01,0,.02c-.31,1.41-1.12,2.63-2.23,3.47,1.57,.69,2.9,1.82,3.85,3.23h4.53c2.76,0,5-2.24,5-5V5c0-2.76-2.24-5-5-5Z"/></svg>

+ 1 - 0
app/src/main/assets/themes/Default/icons/data-transfer.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m12 4v1a4 4 0 0 1 -4 4h-1v2h2a1 1 0 0 1 0 2h-6a1 1 0 0 1 0-2h2v-2h-1a4 4 0 0 1 -4-4v-1a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4zm3 1h2a1 1 0 0 1 1 1v2a1 1 0 0 0 2 0v-2a3 3 0 0 0 -3-3h-2a1 1 0 0 0 0 2zm-4 14h-3a1 1 0 0 1 -1-1v-2a1 1 0 0 0 -2 0v2a3 3 0 0 0 3 3h3a1 1 0 0 0 0-2zm13-4v5a4 4 0 0 1 -4 4h-2a4 4 0 0 1 -4-4v-5a4 4 0 0 1 4-4h2a4 4 0 0 1 4 4zm-4 5a1 1 0 1 0 -1 1 1 1 0 0 0 1-1z"/></svg>

+ 4 - 0
app/src/main/assets/themes/Default/icons/dialogue-exchange.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="M2.594,8.419L.087,5.913l1.414-1.414,1.499,1.499V3c0-1.654,1.346-3,3-3h4V2H6c-.551,0-1,.449-1,1v3.006l1.499-1.508,1.414,1.414-2.506,2.506c-.388,.388-.897,.581-1.406,.581s-1.019-.193-1.406-.581Zm16,7.162l-2.506,2.506,1.414,1.414,1.499-1.508v3.006c0,.551-.449,1-1,1h-4v2h4c1.654,0,3-1.346,3-3v-2.997l1.499,1.499,1.414-1.414-2.507-2.507c-.774-.774-2.037-.775-2.812,0ZM21,0h-6c-1.654,0-3,1.346-3,3V11h6.896l2.802,1.754c.248,.166,.534,.249,.822,.249,.239,0,.479-.058,.698-.175,.483-.258,.782-.758,.782-1.306V3c0-1.654-1.346-3-3-3ZM0,13.997v8.522c0,.547,.3,1.047,.782,1.306,.219,.117,.459,.175,.698,.175,.287,0,.573-.083,.822-.249l2.802-1.754h6.896V13.997c0-1.654-1.346-3-3-3H3c-1.654,0-3,1.346-3,3Z"/>
+</svg>

+ 2 - 0
app/src/main/assets/themes/Default/icons/diamond-exclamation.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="m23.161,9.873L14.122.834c-1.134-1.133-3.11-1.133-4.243,0L.839,9.873c-1.17,1.17-1.17,3.073,0,4.243l9.039,9.039c.567.566,1.32.879,2.122.879s1.555-.312,2.121-.879l9.04-9.039c1.17-1.17,1.17-3.073,0-4.243Zm-10.161,8.127h-2v-2h2v2Zm0-4h-2V6h2v8Z"/></svg>

+ 2 - 0
app/src/main/assets/themes/Default/icons/hexagon-exclamation.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="m23.34,9.48l-3.5-6c-.893-1.53-2.547-2.48-4.319-2.48h-7.072c-1.771,0-3.426.95-4.319,2.48L.631,9.48c-.907,1.554-.907,3.485,0,5.039l3.5,6c.893,1.53,2.547,2.48,4.319,2.48h7.072c1.771,0,3.426-.95,4.319-2.48l3.5-6c.907-1.554.907-3.485,0-5.039Zm-12.34-2.48c0-.553.448-1,1-1s1,.447,1,1v5.5c0,.553-.448,1-1,1s-1-.447-1-1v-5.5Zm1,11c-.828,0-1.5-.672-1.5-1.5s.672-1.5,1.5-1.5,1.5.672,1.5,1.5-.672,1.5-1.5,1.5Z"/></svg>

+ 26 - 0
app/src/main/assets/themes/Default/icons/home.svg

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="512" height="512">
+<g>
+	<path d="M256,319.841c-35.346,0-64,28.654-64,64v128h128v-128C320,348.495,291.346,319.841,256,319.841z"/>
+	<g>
+		<path d="M362.667,383.841v128H448c35.346,0,64-28.654,64-64V253.26c0.005-11.083-4.302-21.733-12.011-29.696l-181.29-195.99    c-31.988-34.61-85.976-36.735-120.586-4.747c-1.644,1.52-3.228,3.103-4.747,4.747L12.395,223.5    C4.453,231.496-0.003,242.31,0,253.58v194.261c0,35.346,28.654,64,64,64h85.333v-128c0.399-58.172,47.366-105.676,104.073-107.044    C312.01,275.383,362.22,323.696,362.667,383.841z"/>
+		<path d="M256,319.841c-35.346,0-64,28.654-64,64v128h128v-128C320,348.495,291.346,319.841,256,319.841z"/>
+	</g>
+</g>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+</svg>

+ 1 - 0
app/src/main/assets/themes/Default/icons/inventory-alt.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m17.864 4.533c-.108-.274-.333-.487-.612-.581-.056-.019-1.261-.418-3.312-.693-.545-1.519-1.841-2.259-3.94-2.259s-3.396.74-3.94 2.259c-2.051.275-3.256.675-3.312.693-.279.094-.503.307-.612.581-.046.117-1.136 2.939-1.136 8.467s1.09 8.35 1.136 8.467c.104.265.316.472.583.57.106.04 2.627.951 7.2.962 0 0 .614-.013.844-.018-.053-.12-.101-.243-.138-.372-.024-.081-.625-2.147-.625-4.609s.601-4.528.627-4.615c.321-1.105 1.273-1.919 2.423-2.068.431-.055 2.593-.316 4.45-.316.476 0 .971.017 1.452.044-.21-4.238-1.048-6.408-1.089-6.511zm-8.097 7.609c-.787.941-1.685 1.792-2.667 2.528-.178.133-.389.199-.6.199s-.423-.066-.601-.2c-.563-.423-1.102-.886-1.6-1.374-.395-.387-.4-1.02-.014-1.414.388-.396 1.021-.401 1.414-.014.256.25.523.493.8.726.627-.526 1.208-1.107 1.733-1.735.353-.423.982-.48 1.408-.125.424.354.479.985.125 1.409zm0-5c-.787.941-1.685 1.792-2.667 2.528-.178.133-.389.2-.6.2s-.422-.066-.6-.2c-.56-.419-1.098-.882-1.601-1.375-.395-.386-.4-1.02-.014-1.414.386-.395 1.021-.4 1.414-.014.257.251.524.495.8.726.627-.526 1.208-1.107 1.733-1.735.353-.423.982-.48 1.408-.125.424.354.479.985.125 1.409zm4.233 1.858h-2c-.553 0-1-.448-1-1s.447-1 1-1h2c.553 0 1 .448 1 1s-.447 1-1 1zm8.455 4.95c-.101-.348-.4-.603-.762-.65-.093-.012-2.321-.3-4.193-.3s-4.1.288-4.193.3c-.362.047-.661.302-.762.65-.022.076-.545 1.901-.545 4.05s.523 3.974.545 4.05c.101.348.4.603.762.65.093.012 2.321.3 4.193.3s4.1-.288 4.193-.3c.362-.047.661-.302.762-.65.022-.076.545-1.901.545-4.05s-.523-3.974-.545-4.05zm-3.955 4.05h-2c-.553 0-1-.447-1-1s.447-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/></svg>

+ 2 - 0
app/src/main/assets/themes/Default/icons/light-emergency-on.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="m24,24H0v-1c0-1.654,1.346-3,3-3h18c1.654,0,3,1.346,3,3v1ZM3.718,5.202L1.4,2.982.018,4.426l2.317,2.22,1.383-1.443Zm3.862-2.493L6.367-.008l-1.826.814,1.213,2.717,1.826-.814Zm16.402,1.717l-1.383-1.443-2.317,2.22,1.383,1.443,2.317-2.22Zm-4.523-3.619L17.633-.008l-1.213,2.717,1.826.814,1.213-2.717Zm1.541,12.193c0-4.963-4.037-9-9-9S3,8.037,3,13v5h18v-5Zm-9-2c-1.103,0-2,.897-2,2h-2c0-2.206,1.794-4,4-4v2Z"/></svg>

+ 1 - 1
app/src/main/assets/themes/Default/icons/limit-hand.svg

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
-  <path d="m15.654,13.773l-4.599,7.884c-.606,1.039.143,2.343,1.346,2.343h9.197c1.203,0,1.952-1.304,1.346-2.343l-4.599-7.884c-.601-1.031-2.091-1.031-2.692,0Zm1.346,9.227h0c-.552,0-1-.448-1-1h0c0-.552.448-1,1-1h0c.552,0,1,.448,1,1h0c0,.552-.448,1-1,1Zm-1-4v-2c0-.552.448-1,1-1h0c.552,0,1,.448,1,1v2c0,.552-.448,1-1,1h0c-.552,0-1-.448-1-1Zm-6.94,4.652c-1.285-.391-2.465-1.101-3.417-2.084L.646,16.412c-.713-.715-.88-1.87-.276-2.68.63-.845,1.741-1.031,2.584-.561.201.131.353.245.441.333l2.605,2.65V3.5c0-.911.812-1.632,1.752-1.479.737.12,1.248.813,1.248,1.56v5.42c0,.552.448,1,1,1s1-.448,1-1V1.5c0-.828.672-1.5,1.5-1.5s1.5.672,1.5,1.5v7.5c0,.552.448,1,1,1s1-.448,1-1V2.5c0-.828.672-1.5,1.5-1.5s1.5.672,1.5,1.5v6.5c0,.552.448,1,1,1s1-.448,1-1v-4.5c0-.828.672-1.5,1.5-1.5s1.5.672,1.5,1.5v14.5c0,.145-.031.282-.043.424l-3.883-6.658c-.645-1.106-1.794-1.767-3.074-1.767s-2.43.66-3.074,1.767l-4.597,7.882c-.546.932-.631,2.018-.269,3.003Z"/>
+  <path d="m18.346,13.773c-.601-1.031-2.091-1.031-2.692,0l-4.599,7.884c-.606,1.039.143,2.343,1.346,2.343h9.198c1.203,0,1.952-1.305,1.346-2.343l-4.599-7.884Zm-1.346,9.227c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Zm1-4c0,.552-.448,1-1,1s-1-.448-1-1v-2c0-.552.448-1,1-1s1,.448,1,1v2Zm6-13v10.5c0,.553-.447,1-1,1s-1-.447-1-1V6c0-.294-.129-.572-.353-.763-.229-.192-.521-.272-.822-.223-.463.075-.825.555-.825,1.093v3.893c0,.553-.447,1-1,1s-1-.447-1-1v-6c0-.552-.448-1-1-1s-1,.448-1,1v6c0,.553-.447,1-1,1s-1-.447-1-1V3c0-.552-.448-1-1-1s-1,.448-1,1v7c0,.553-.447,1-1,1s-1-.447-1-1v-4.893c0-.538-.362-1.018-.825-1.093-.303-.051-.595.029-.822.223-.224.19-.353.469-.353.763v11c0,.398-.236.759-.602.917-.363.157-.789.086-1.081-.186l-2.638-2.462c-.195-.183-.44-.265-.715-.269-.266.009-.513.121-.694.315-.378.403-.356,1.038.046,1.416l4.468,4.307c.496.479,1.059.88,1.671,1.192.491.251.687.854.436,1.346-.177.346-.527.545-.892.545-.153,0-.308-.035-.454-.109-.787-.402-1.511-.918-2.149-1.534L.938,17.182c-1.196-1.121-1.259-3.023-.129-4.231,1.128-1.205,3.027-1.271,4.235-.143l.955.892V5c0-.882.386-1.715,1.058-2.286.672-.572,1.56-.815,2.439-.674.216.035.42.102.617.184.344-1.277,1.501-2.224,2.885-2.224,1.065,0,1.997.562,2.529,1.401.437-.248.934-.401,1.471-.401,1.384,0,2.541.947,2.886,2.224.197-.082.401-.149.617-.184.88-.142,1.768.102,2.439.674.672.571,1.058,1.404,1.058,2.286Z"/>
 </svg>

+ 1 - 0
app/src/main/assets/themes/Default/icons/log-file.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m14 7v-6.54c.913.346 1.753.879 2.465 1.59l3.484 3.486c.712.711 1.245 1.551 1.591 2.464h-6.54c-.552 0-1-.449-1-1zm-1.771 7.985c0-.394-.342-.726-.75-.733-.402.007-.725.333-.724.736 0 .095.008 1.886.008 2.024 0 .403.322.729.724.736.408-.007.75-.338.75-.733l-.008-2.015zm9.747-4.985h-6.976c-1.654 0-3-1.346-3-3v-6.976c-.161-.011-.322-.024-.485-.024h-4.515c-2.757 0-5 2.243-5 5v14c0 2.757 2.243 5 5 5h10c2.757 0 5-2.243 5-5v-8.515c0-.163-.013-.324-.024-.485zm-13.254 8.368c0 .346-.28.627-.626.627l-1.468.003c-.347 0-.629-.28-.629-.627l-.005-4.744c0-.347.281-.628.628-.628s.627.28.627.626c.001 1.249.003 2.869.004 4.118.71-.001.592-.003.837-.004.348-.002.631.28.631.628zm4.765-1.353c0 1.094-.902 1.985-2.012 1.985-1.086 0-1.967-.881-1.967-1.967l-.008-2.066c0-1.086.881-1.967 1.967-1.967 1.109 0 2.012.891 2.012 1.985v.015l.008 2zm5 0c0 1.094-.902 1.985-2.012 1.985-1.086 0-1.967-.881-1.967-1.967l-.008-2.066c0-1.086.881-1.967 1.967-1.967.772 0 1.445.432 1.782 1.064.223.417-.08.921-.553.921-.228 0-.441-.122-.548-.324-.125-.237-.38-.404-.669-.409-.402.007-.725.333-.724.736 0 .095.008 1.886.008 2.024 0 .403.322.729.724.736.32-.005.593-.212.698-.491-.036 0-.063 0-.085 0-.349.004-.633-.278-.633-.627 0-.346.28-.626.625-.627l.766-.002c.346-.001.627.278.628.624v.374s.001.015.001.015z"/></svg>

+ 4 - 0
app/src/main/assets/themes/Default/icons/member-list.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="M21,11h-5c-1.654,0-3,1.346-3,3v7c0,1.654,1.346,3,3,3h5c1.654,0,3-1.346,3-3v-7c0-1.654-1.346-3-3-3Zm-1,9h-3c-.553,0-1-.448-1-1s.447-1,1-1h3c.553,0,1,.448,1,1s-.447,1-1,1Zm0-4.003h-3c-.553,0-1-.448-1-1s.447-1,1-1h3c.553,0,1,.448,1,1s-.447,1-1,1ZM3,6C3,2.691,5.691,0,9,0s6,2.691,6,6-2.691,6-6,6S3,9.309,3,6ZM12.026,24H1c-.557,0-1.001-.46-1-1.017,.009-4.955,4.043-8.983,9-8.983h0c.688,0,1.356,.085,2,.232v6.768c0,1.13,.391,2.162,1.026,3Z"/>
+</svg>

+ 4 - 0
app/src/main/assets/themes/Default/icons/money-income.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="M0,5c0-1.657,2.686-3,6-3s6,1.343,6,3-2.686,3-6,3S0,6.657,0,5Zm6,11c3.421,0,6-1.505,6-3.5v-2c0,1.971-2.5,3.5-6,3.5S0,12.471,0,10.5v2c0,1.995,2.579,3.5,6,3.5Zm0-4c3.421,0,6-1.505,6-3.5v-2c0,1.971-2.5,3.5-6,3.5S0,8.471,0,6.5v2c0,1.995,2.579,3.5,6,3.5Zm12,4c3.314,0,6-1.343,6-3s-2.686-3-6-3-6,1.343-6,3,2.686,3,6,3Zm0,6c-3.5,0-6-1.529-6-3.5,0,1.971-2.5,3.5-6,3.5s-6-1.529-6-3.5v2c0,1.995,2.579,3.5,6,3.5s6-1.505,6-3.5c0,1.995,2.579,3.5,6,3.5s6-1.505,6-3.5v-2c0,1.971-2.5,3.5-6,3.5Zm0-4c-3.5,0-6-1.529-6-3.5,0,1.971-2.5,3.5-6,3.5S0,16.471,0,14.5v2c0,1.995,2.579,3.5,6,3.5s6-1.505,6-3.5c0,1.995,2.579,3.5,6,3.5s6-1.505,6-3.5v-2c0,1.971-2.5,3.5-6,3.5Zm-2.7-13.75c.254,0,.508-.096,.702-.289l.998-.981V7c0,.552,.447,1,1,1s1-.448,1-1V2.99l1.003,.976c.396,.385,1.028,.376,1.414-.019,.385-.396,.377-1.029-.02-1.414l-2.003-1.951c-.779-.775-2.041-.775-2.812-.004l-1.984,1.959c-.394,.388-.397,1.021-.01,1.414,.196,.198,.454,.297,.712,.297Z"/>
+</svg>

+ 1 - 0
app/src/main/assets/themes/Default/icons/play-alt.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m14.823 11.708a.325.325 0 0 1 .169.292.314.314 0 0 1 -.12.266l-5.372 2.688a.337.337 0 0 1 -.5-.293v-5.322a.327.327 0 0 1 .168-.292.314.314 0 0 1 .157-.042.462.462 0 0 1 .228.068zm9.177-6.708v14a5.006 5.006 0 0 1 -5 5h-14a5.006 5.006 0 0 1 -5-5v-14a5.006 5.006 0 0 1 5-5h14a5.006 5.006 0 0 1 5 5zm-7.008 7a2.332 2.332 0 0 0 -1.226-2.055l-5.278-2.635a2.337 2.337 0 0 0 -3.5 2.029v5.322a2.313 2.313 0 0 0 1.164 2.021 2.368 2.368 0 0 0 1.186.323 2.2 2.2 0 0 0 1.1-.289l5.376-2.687a2.313 2.313 0 0 0 1.178-2.029z"/></svg>

+ 2 - 0
app/src/main/assets/themes/Default/icons/sensor-alert.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M23.843,22.248l-6.855-11.67c-.441-.771-1.533-.771-1.974,0l-6.855,11.67c-.444,.777,.105,1.752,.987,1.752h13.711c.882,0,1.432-.976,.987-1.752Zm-7.843-.248c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Zm1-4c0,.552-.447,1-1,1s-1-.448-1-1v-3c0-.552,.447-1,1-1s1,.448,1,1v3Zm-10.579,3.254l6.867-11.689c.549-.958,1.592-1.565,2.712-1.565s2.163,.607,2.723,1.584l5.277,8.982V5c0-2.757-2.243-5-5-5H5C2.243,0,0,2.243,0,5v14c0,2.757,2.243,5,5,5h1.252c-.352-.891-.314-1.901,.169-2.746ZM8,3c.552,0,1,.448,1,1s-.448,1-1,1-1-.448-1-1,.448-1,1-1Zm-4,2c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Z"/></svg>

+ 4 - 0
app/src/main/assets/themes/Default/icons/shelves.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="m17.5,0c-1.381,0-2.5,1.119-2.5,2.5v4.5H2V0H0v24h2v-3h20v3h2V0h-6.5Zm4.5,19h-4v-5c0-1.105-.895-2-2-2h-4c-1.105,0-2,.895-2,2v5h-2v-5c0-1.105-.895-2-2-2H2v-3h20v10Z"/>
+</svg>

+ 4 - 0
app/src/main/assets/themes/Default/icons/store-buyer.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="m23,24h-8c-.311,0-.604-.145-.793-.391-.189-.247-.254-.567-.173-.868.591-2.203,2.633-3.741,4.966-3.741s4.375,1.538,4.966,3.741c.081.301.017.621-.173.868-.188.247-.482.391-.793.391Zm-4-6c-1.379,0-2.5-1.121-2.5-2.5s1.121-2.5,2.5-2.5,2.5,1.121,2.5,2.5-1.121,2.5-2.5,2.5Zm4.962-10.275l-1.172-4.099c-.61-2.135-2.588-3.626-4.808-3.626h-.982v4c0,.552-.447,1-1,1s-1-.448-1-1V0h-6v4c0,.552-.448,1-1,1s-1-.448-1-1V0h-.983C3.797,0,1.82,1.491,1.209,3.626L.039,7.725c-.025.089-.039.182-.039.275,0,2.206,1.794,4,4,4h1c1.2,0,2.266-.542,3-1.382.734.84,1.8,1.382,3,1.382h2c1.201,0,2.266-.542,3-1.382.734.84,1.799,1.382,3,1.382h1c2.206,0,4-1.794,4-4,0-.093-.013-.186-.038-.275Zm-11.859,14.495c.481-1.794,1.659-3.256,3.192-4.176-.499-.725-.795-1.6-.795-2.545,0-.652.146-1.268.396-1.827-.607.207-1.244.327-1.896.327h-2c-1.062,0-2.095-.288-3-.819-.905.531-1.938.819-3,.819h-1c-1.093,0-2.116-.299-3-.812v6.812c0,2.206,1.794,4,4,4h7.192c-.201-.568-.248-1.189-.089-1.78Z"/>
+</svg>

+ 1 - 0
app/src/main/assets/themes/Default/icons/supplier-alt.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m23 20.5c0 .767-.166 1.532-.173 1.564-.083.381-.381.678-.762.762-.032.007-.798.174-1.565.174s-1.533-.167-1.565-.174c-.381-.084-.678-.381-.762-.762-.007-.032-.173-.798-.173-1.564s.166-1.532.173-1.564c.083-.381.381-.678.762-.762.032-.007.798-.174 1.565-.174s1.533.167 1.565.174c.381.084.678.381.762.762.007.032.173.798.173 1.564zm-6.935-2.326c-.032-.007-.798-.174-1.565-.174s-1.533.167-1.565.174c-.381.084-.678.381-.762.762-.007.032-.173.798-.173 1.564s.166 1.532.173 1.564c.083.381.381.678.762.762.032.007.798.174 1.565.174s1.533-.167 1.565-.174c.381-.084.678-.381.762-.762.007-.032.173-.798.173-1.564s-.166-1.532-.173-1.564c-.083-.381-.381-.678-.762-.762zm3.762-2.109c.007-.032.173-.798.173-1.564s-.166-1.532-.173-1.564c-.083-.381-.381-.678-.762-.762-.032-.007-.798-.174-1.565-.174s-1.533.167-1.565.174c-.381.084-.678.381-.762.762-.007.032-.173.798-.173 1.564s.166 1.532.173 1.564c.083.381.381.678.762.762.032.007.798.174 1.565.174s1.533-.167 1.565-.174c.381-.084.678-.381.762-.762zm-13.827-11.065c1.429 0 2-.571 2-2s-.571-2-2-2-2 .571-2 2 .571 2 2 2zm7.408 4.277c.817.054 1.541-.576 1.589-1.404.051-.827-.578-1.539-1.405-1.59-1.366-.088-5.286-.294-7.592-.283-1.111 0-2.354.138-3.202.253-.635.086-1.118.559-1.255 1.148-.085.221-.526 1.614-.543 4.099 0 2.337.45 4.32.47 4.403.068.297.266.54.53.671v4.926c0 .828.671 1.5 1.5 1.5s1.5-.672 1.5-1.5v-4.535c.335.02.673.035 1 .035v4.5c0 .828.671 1.5 1.5 1.5s1.5-.672 1.5-1.5v-12.439c1.818.064 3.541.163 4.408.216z"/></svg>

+ 4 - 0
app/src/main/assets/themes/Default/icons/table-list.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
+  <path d="m24,15H8v-6h16v6ZM8,7h16c0-2.757-2.243-5-5-5h-11v5Zm-2,2H0v6h6v-6Zm2,13h11c2.757,0,5-2.243,5-5H8v5ZM6,2h-1C2.243,2,0,4.243,0,7h6V2Zm0,15H0c0,2.757,2.243,5,5,5h1v-5Z"/>
+</svg>

+ 1 - 0
app/src/main/assets/themes/Default/icons/users.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m7.5 13a4.5 4.5 0 1 1 4.5-4.5 4.505 4.505 0 0 1 -4.5 4.5zm6.5 11h-13a1 1 0 0 1 -1-1v-.5a7.5 7.5 0 0 1 15 0v.5a1 1 0 0 1 -1 1zm3.5-15a4.5 4.5 0 1 1 4.5-4.5 4.505 4.505 0 0 1 -4.5 4.5zm-1.421 2.021a6.825 6.825 0 0 0 -4.67 2.831 9.537 9.537 0 0 1 4.914 5.148h6.677a1 1 0 0 0 1-1v-.038a7.008 7.008 0 0 0 -7.921-6.941z"/></svg>

+ 2 - 2
app/src/main/java/com/grkj/iscs_mc/features/login/activity/LoginActivity.kt

@@ -8,7 +8,7 @@ import com.grkj.iscs_mc.features.login.adapter.TwoFragmentAdapter
 import com.grkj.shared.utils.extension.toByteArrays
 import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.ui_base.base.BaseActivity
-import com.grkj.ui_base.utils.event.RFIDCardReadEvent
+import com.grkj.ui_base.utils.event.RFIDReadEvent
 import dagger.hilt.android.AndroidEntryPoint
 
 /**
@@ -40,7 +40,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                 try {
                     cardNo = cardNo.toLong().toByteArrays().toHexStrings(false)
                     logger.info("Swipe card login: $cardNo")
-                    RFIDCardReadEvent.sendRFIDCardReadEvent(cardNo)
+                    RFIDReadEvent.sendRFIDCardReadEvent(cardNo)
                     // 重置cardNo
                     cardNo = ""
                 } catch (e: Exception) {

+ 0 - 1
app/src/main/java/com/grkj/iscs_mc/features/login/adapter/TwoFragmentAdapter.kt

@@ -4,7 +4,6 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentActivity
 import androidx.viewpager2.adapter.FragmentStateAdapter
 import com.grkj.iscs_mc.features.login.fragment.LoginFragment
-import com.grkj.iscs_mc.features.login.fragment.MaterialShowFragment
 
 class TwoFragmentAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
 //    override fun getItemCount() = 2

+ 1 - 1
app/src/main/java/com/grkj/iscs_mc/features/login/dialog/LoginDialog.kt

@@ -12,7 +12,7 @@ import com.grkj.shared.utils.ArcSoftUtil
 import com.grkj.shared.utils.ArcSoftUtil.inDetecting
 import com.grkj.ui_base.skin.loadSkinIcon
 import com.grkj.ui_base.utils.CommonUtils
-import com.grkj.ui_base.utils.event.LoadingEvent
+import com.grkj.data.utils.event.LoadingEvent
 import com.kongzue.dialogx.dialogs.CustomDialog
 import com.kongzue.dialogx.dialogs.PopTip
 import com.kongzue.dialogx.interfaces.DialogLifecycleCallback

+ 166 - 80
app/src/main/java/com/grkj/iscs_mc/features/login/fragment/LoginFragment.kt

@@ -20,8 +20,6 @@ import com.grkj.data.common.EventConstants
 import com.grkj.data.common.MainDomainData
 import com.grkj.data.enums.LoginModeEnum
 import com.grkj.data.enums.LoginResultEnum
-import com.grkj.data.hardware.can.CanCommands
-import com.grkj.data.hardware.can.CanHelper
 import com.grkj.data.hardware.fingerprint.FingerprintUtil
 import com.grkj.iscs_mc.R
 import com.grkj.iscs_mc.databinding.FragmentLoginBinding
@@ -40,16 +38,18 @@ import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.skin.loadSkinIcon
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.changeBgTint
-import com.grkj.ui_base.utils.event.LoadingEvent
-import com.grkj.ui_base.utils.event.RFIDCardReadEvent
+import com.grkj.data.utils.event.LoadingEvent
+import com.grkj.shared.utils.ArcSoftUtil
+import com.grkj.shared.utils.ArcSoftUtil.inDetecting
+import com.grkj.ui_base.utils.event.RFIDReadEvent
 import com.grkj.ui_base.utils.extension.getAppVersionName
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikandroid.activity.ActivityTracker
 import com.sik.sikcore.extension.setDebouncedClickListener
 import com.sik.sikcore.shell.ShellUtils
 import com.sik.sikcore.thread.ThreadUtils
 import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
 import java.util.Locale
 
 /**
@@ -61,7 +61,6 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
 
     //登录方式列表
     private val loginMenuList: MutableList<LoginMenuEntity> = mutableListOf()
-    private var isKeyDockOpen = true
 
     /**
      * 登录模式切换
@@ -72,6 +71,8 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
     private var currentClickTime = 0L
     private var currentSupportClickTime = 0L
 
+    private var inFaceChecking: Boolean = false
+
     override fun getLayoutId(): Int {
         return R.layout.fragment_login
     }
@@ -140,6 +141,28 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
             }
         }
         binding.modeTip.isVisible = false
+        binding.tvCancel.setDebouncedClickListener {
+            ArcSoftUtil.stop()
+            hideLogin()
+        }
+        binding.tvLogin.setDebouncedClickListener {
+            if (binding.etAccount.text.toString().isEmpty()) {
+                PopTip.tip(CommonUtils.getStr("please_input_account"))
+                return@setDebouncedClickListener
+            }
+            if (binding.etPassword.text.toString().isEmpty()) {
+                PopTip.tip(CommonUtils.getStr("please_input_password"))
+                return@setDebouncedClickListener
+            }
+            LoadingEvent.sendLoadingEvent(
+                CommonUtils.getStr("doing_login"), true
+            )
+            viewModel.loginWithAccount(
+                binding.etAccount.text.toString(), binding.etPassword.text.toString()
+            ).observe(this) {
+                checkLoginResult(it)
+            }
+        }
     }
 
     private fun BindingAdapter.BindingViewHolder.onLoginMenuBinding(holder: BindingAdapter.BindingViewHolder) {
@@ -153,95 +176,158 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
         )
         itemBinding.loginContainer.changeBgTint(item.menuBg)
         holder.itemView.setDebouncedClickListener {
-            LoginDialog.show(this@LoginFragment, viewModel, item.loginType) {
-                hideLoading()
-                when (it) {
-                    LoginResultEnum.FINGERPRINTER_VERIFY_SUCCESS -> {
-                        showToast(CommonUtils.getStr("fingerprint_login_success"))
-                        if (loginMode == LoginModeEnum.USER_MODE) {
-                            startActivity(
+            if (item.loginType in listOf(0, 3)) {
+                showLogin(item.loginType)
+            }
+        }
+    }
+
+    private fun checkLoginResult(loginResultEnum: LoginResultEnum) {
+        when (loginResultEnum) {
+            LoginResultEnum.FINGERPRINTER_VERIFY_SUCCESS -> {
+                showToast(CommonUtils.getStr("fingerprint_login_success"))
+                if (loginMode == LoginModeEnum.USER_MODE) {
+                    startActivity(
 //                                Intent(requireContext(), MaterialExchangeActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        } else {
-                            startActivity(
+                        Intent(requireContext(), MainActivity::class.java)
+                    )
+                } else {
+                    startActivity(
 //                                Intent(requireContext(), ManageActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        }
-                        requireActivity().finish()
-                    }
-
-                    LoginResultEnum.FINGERPRINTER_VERIFY_FAILED -> showToast(
-                        CommonUtils.getStr("fingerprint_login_failed")
+                        Intent(requireContext(), MainActivity::class.java)
                     )
+                }
+                requireActivity().finish()
+            }
 
-                    LoginResultEnum.FACE_VERIFY_SUCCESS -> {
-                        showToast(CommonUtils.getStr("face_login_success"))
-                        if (loginMode == LoginModeEnum.USER_MODE) {
-                            startActivity(
+            LoginResultEnum.FINGERPRINTER_VERIFY_FAILED -> showToast(
+                CommonUtils.getStr("fingerprint_login_failed")
+            )
+
+            LoginResultEnum.FACE_VERIFY_SUCCESS -> {
+                showToast(CommonUtils.getStr("face_login_success"))
+                if (loginMode == LoginModeEnum.USER_MODE) {
+                    startActivity(
 //                                Intent(requireContext(), MaterialExchangeActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        } else {
-                            startActivity(
+                        Intent(requireContext(), MainActivity::class.java)
+                    )
+                } else {
+                    startActivity(
 //                                Intent(requireContext(), ManageActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        }
-                        requireActivity().finish()
-                    }
-
-                    LoginResultEnum.FACE_VERIFY_FAILED -> showToast(
-                        CommonUtils.getStr("face_login_failed")
+                        Intent(requireContext(), MainActivity::class.java)
                     )
+                }
+                requireActivity().finish()
+            }
 
-                    LoginResultEnum.USERNAME_PASSWORD_LOGIN_SUCCESS -> {
-                        showToast(
-                            CommonUtils.getStr("username_passowrd_login_success")
-                        )
-                        if (loginMode == LoginModeEnum.USER_MODE) {
-                            startActivity(
+            LoginResultEnum.FACE_VERIFY_FAILED -> showToast(
+                CommonUtils.getStr("face_login_failed")
+            )
+
+            LoginResultEnum.USERNAME_PASSWORD_LOGIN_SUCCESS -> {
+                showToast(
+                    CommonUtils.getStr("username_passowrd_login_success")
+                )
+                if (loginMode == LoginModeEnum.USER_MODE) {
+                    startActivity(
 //                                Intent(requireContext(), MaterialExchangeActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        } else {
-                            startActivity(
+                        Intent(requireContext(), MainActivity::class.java)
+                    )
+                } else {
+                    startActivity(
 //                                Intent(requireContext(), ManageActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        }
-                        requireActivity().finish()
-                    }
-
-                    LoginResultEnum.USERNAME_OR_PASSWORD_ERROR -> showToast(
-                        CommonUtils.getStr("username_or_password_error")
+                        Intent(requireContext(), MainActivity::class.java)
                     )
+                }
+                requireActivity().finish()
+            }
 
-                    LoginResultEnum.USERNAME_PASSWORD_NOT_EXISTS -> showToast(
-                        CommonUtils.getStr("username_password_not_exists")
-                    )
+            LoginResultEnum.USERNAME_OR_PASSWORD_ERROR -> showToast(
+                CommonUtils.getStr("username_or_password_error")
+            )
 
-                    LoginResultEnum.JOB_CARD_LOGIN_SUCCESS -> {
-                        showToast(CommonUtils.getStr("job_card_login_success"))
-                        if (loginMode == LoginModeEnum.USER_MODE) {
-                            startActivity(
+            LoginResultEnum.USERNAME_PASSWORD_NOT_EXISTS -> showToast(
+                CommonUtils.getStr("username_password_not_exists")
+            )
+
+            LoginResultEnum.JOB_CARD_LOGIN_SUCCESS -> {
+                showToast(CommonUtils.getStr("job_card_login_success"))
+                if (loginMode == LoginModeEnum.USER_MODE) {
+                    startActivity(
 //                                Intent(requireContext(), MaterialExchangeActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        } else {
-                            startActivity(
+                        Intent(requireContext(), MainActivity::class.java)
+                    )
+                } else {
+                    startActivity(
 //                                Intent(requireContext(), ManageActivity::class.java)
-                                Intent(requireContext(), MainActivity::class.java)
-                            )
-                        }
-                        requireActivity().finish()
-                    }
-
-                    LoginResultEnum.JOB_CARD_LOGIN_FAILED -> showToast(
-                        CommonUtils.getStr("job_card_login_failed")
+                        Intent(requireContext(), MainActivity::class.java)
                     )
                 }
+                requireActivity().finish()
+            }
+
+            LoginResultEnum.JOB_CARD_LOGIN_FAILED -> showToast(
+                CommonUtils.getStr("job_card_login_failed")
+            )
+        }
+    }
+
+    private fun showLogin(loginType: Int) {
+        binding.loginLayout.isVisible = true
+        binding.loginMethodLayout.isVisible = false
+        binding.loginAccount.isVisible = loginType == 3
+        binding.loginFace.isVisible = loginType == 0
+        if (loginType == 3) {
+            binding.etAccount.setText("")
+            binding.etPassword.setText("")
+            binding.loginTip.text = CommonUtils.getStr("account_login_tip")
+        } else if (loginType == 0) {
+            binding.loginTip.text = CommonUtils.getStr("face_login_tip")
+            binding.ivIcon.loadSkinIcon("face-id-svgrepo-com.svg")
+            if (!ArcSoftUtil.isActivated) {
+                PopTip.tip(
+                    CommonUtils.getStr("face_can_not_process").toString()
+                )
+            } else {
+                startFace()
+            }
+        }
+    }
+
+    private fun hideLogin() {
+        binding.loginLayout.isVisible = false
+        binding.loginMethodLayout.isVisible = true
+    }
+
+    private fun startFace() {
+        inDetecting = false
+        ActivityTracker.getCurrentActivity()?.let { context ->
+            ArcSoftUtil.checkCamera(
+                context.windowManager, binding.preview
+            ) { bitmap, userId ->
+                viewModel.loginWithUserId(userId).observe(this) {
+                    if (it == LoginResultEnum.FACE_VERIFY_FAILED) {
+                        viewModel.loginWithFace(ImageConvertUtils.bitmapToBase64(bitmap).toString())
+                            .observe(this) {
+                                if (it == LoginResultEnum.FACE_VERIFY_FAILED) {
+                                    ThreadUtils.runOnMainDelayed(1000) {
+                                        inDetecting = false
+                                        inFaceChecking = false
+                                    }
+                                } else {
+                                    ArcSoftUtil.stop()
+                                    hideLoading()
+                                    checkLoginResult(it)
+                                }
+                            }
+                    } else {
+                        ArcSoftUtil.stop()
+                        checkLoginResult(it)
+                    }
+                }
+                LoadingEvent.sendLoadingEvent(
+                    CommonUtils.getStr("face_detected_do_login"), true
+                )
             }
         }
     }
@@ -341,7 +427,7 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
         super.onEvent(event)
         when (event.code) {
             EventConstants.EVENT_RFID_CARD_READ -> {
-                val cardNo = (event.data as RFIDCardReadEvent).rfidNo
+                val cardNo = (event.data as RFIDReadEvent).rfidNo
                 LoadingEvent.sendLoadingEvent(
                     CommonUtils.getStr("doing_login"), true
                 )

+ 0 - 20
app/src/main/java/com/grkj/iscs_mc/features/login/fragment/MaterialShowFragment.kt

@@ -1,20 +0,0 @@
-package com.grkj.iscs_mc.features.login.fragment
-
-import com.grkj.iscs_mc.R
-import com.grkj.iscs_mc.databinding.FragmentMaterialShowBinding
-import com.grkj.ui_base.base.BaseFragment
-import dagger.hilt.android.AndroidEntryPoint
-
-/**
- * 物资展示界面
- */
-@AndroidEntryPoint
-class MaterialShowFragment : BaseFragment<FragmentMaterialShowBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.fragment_material_show
-    }
-
-    override fun initView() {
-
-    }
-}

+ 14 - 4
app/src/main/java/com/grkj/iscs_mc/features/main/activity/MainActivity.kt

@@ -27,9 +27,8 @@ import com.grkj.shared.utils.CountdownTimer
 import com.grkj.shared.utils.extension.toByteArrays
 import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.shared.utils.i18n.I18nManager
-import com.grkj.ui_base.base.BaseActivity
 import com.grkj.ui_base.base.BaseNavActivity
-import com.grkj.ui_base.utils.event.RFIDCardReadEvent
+import com.grkj.ui_base.utils.event.RFIDReadEvent
 import com.grkj.ui_base.utils.extension.removeTint
 import com.sik.sikcore.extension.file
 import com.sik.sikcore.extension.getMMKVData
@@ -71,7 +70,7 @@ class MainActivity() : BaseNavActivity<ActivityMainBinding>() {
 
     private val bottomNavDestinations = setOf(
         R.id.homeFragment,
-        R.id.dataHomeFragment,
+        R.id.dataManageHomeFragment,
         R.id.exceptionHomeFragment
     )
 
@@ -120,9 +119,20 @@ class MainActivity() : BaseNavActivity<ActivityMainBinding>() {
             }
         }
         binding.userInfoLayout.setDebouncedClickListener {
+            if (MainDomainData.fromQuickEntry) {
+                val firstId = binding.navBar.menu[0].itemId
+                binding.navBar.selectedItemId = firstId
+                MainDomainData.fromQuickEntry = false
+            }
             replaceNavGraph(R.navigation.nav_user_info)
         }
         navController.addOnDestinationChangedListener { _, destination, _ ->
+            // 如果是我们定义的底栏图表对应的 NavGraph,就 show,否则 hide
+            if (bottomNavDestinations.contains(destination.id) && MainDomainData.fromQuickEntry) {
+                val firstId = binding.navBar.menu[0].itemId
+                binding.navBar.selectedItemId = firstId
+                MainDomainData.fromQuickEntry = false
+            }
             binding.navBar.isVisible = bottomNavDestinations.contains(destination.id)
         }
     }
@@ -149,7 +159,7 @@ class MainActivity() : BaseNavActivity<ActivityMainBinding>() {
                 try {
                     cardNo = cardNo.toLong().toByteArrays().toHexStrings(false)
                     logger.info("Swipe card login: $cardNo")
-                    RFIDCardReadEvent.sendRFIDCardReadEvent(cardNo)
+                    RFIDReadEvent.sendRFIDCardReadEvent(cardNo)
                     // 重置cardNo
                     cardNo = ""
                 } catch (e: Exception) {

+ 196 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/QuickEntranceConfigDialog.kt

@@ -0,0 +1,196 @@
+package com.grkj.iscs_mc.features.main.dialog
+
+import android.graphics.Color
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.ItemTouchHelper
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.ItemOrientation
+import com.drake.brv.listener.DefaultItemTouchCallback
+import com.drake.brv.utils.setup
+import com.google.android.flexbox.AlignItems
+import com.google.android.flexbox.FlexDirection
+import com.google.android.flexbox.FlexWrap
+import com.google.android.flexbox.FlexboxLayoutManager
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogQuickEntranceConfigBinding
+import com.grkj.iscs_mc.databinding.ItemQuickEntranceConfigBinding
+import com.grkj.iscs_mc.databinding.ItemQuickEntranceNotConfigBinding
+import com.grkj.iscs_mc.features.main.entity.QuickEntranceMenuItemEntity
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.tip
+import com.kongzue.dialogx.dialogs.FullScreenDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.kongzue.dialogx.util.views.ActivityScreenShotImageView
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 快捷入口配置弹窗
+ */
+class QuickEntranceConfigDialog(private val save: (String) -> Unit) :
+    OnBindView<FullScreenDialog>(R.layout.dialog_quick_entrance_config) {
+    private var selectedQuickEntranceConfig = mutableListOf<QuickEntranceMenuItemEntity>()
+    private val allQuickEntranceConfig: MutableList<QuickEntranceMenuItemEntity> =
+        RoleFunctionalPermissionsEnum.values().filter { it.level != 0 }.mapIndexed { index, value ->
+            QuickEntranceMenuItemEntity(
+                index,
+                QuickEntranceMenuItemEntity.getMenuIcon(value),
+                I18nManager.t(value.description),
+                value,
+                QuickEntranceMenuItemEntity.getNavGraphId(value),
+                QuickEntranceMenuItemEntity.getDestId(value)
+            )
+        }
+            .filter { MainDomainData.permissions.contains(it.permission.functionalPermission) && it.navGraph != 0 }
+            .toMutableList()
+    private val quickEntranceList: MutableList<QuickEntranceMenuItemEntity>
+        get() {
+            val quickEntryConfigJson = MainDomainData.userInfo?.quickEntranceConfig ?: ""
+            val permissions = quickEntryConfigJson.split(",").filter { it.isNotBlank() }
+                .mapNotNull { runCatching { RoleFunctionalPermissionsEnum.valueOf(it) }.getOrNull() }
+            return if (permissions.isEmpty()) {
+                mutableListOf<QuickEntranceMenuItemEntity>(
+                    QuickEntranceMenuItemEntity(
+                        0,
+                        "document.svg",
+                        I18nManager.t(RoleFunctionalPermissionsEnum.EXCHANGE_RECORD.description),
+                        RoleFunctionalPermissionsEnum.EXCHANGE_RECORD,
+                        QuickEntranceMenuItemEntity.getNavGraphId(RoleFunctionalPermissionsEnum.EXCHANGE_RECORD),
+                        QuickEntranceMenuItemEntity.getDestId(RoleFunctionalPermissionsEnum.EXCHANGE_RECORD)
+                    )
+                )
+            } else {
+                permissions.mapIndexed { index, value ->
+                    QuickEntranceMenuItemEntity(
+                        index,
+                        QuickEntranceMenuItemEntity.getMenuIcon(value),
+                        I18nManager.t(value.description),
+                        value,
+                        QuickEntranceMenuItemEntity.getNavGraphId(value),
+                        QuickEntranceMenuItemEntity.getDestId(value)
+                    )
+                }.toMutableList()
+            }
+        }
+    private lateinit var binding: DialogQuickEntranceConfigBinding
+    override fun onBind(dialog: FullScreenDialog, contentView: View) {
+        dialog.setMaskColor(Color.parseColor("#4D000000"))
+        ActivityScreenShotImageView.hideContentView = true
+        binding = DialogQuickEntranceConfigBinding.bind(contentView)
+        selectedQuickEntranceConfig = quickEntranceList.onEach {
+            it.itemOrientationDrag = ItemOrientation.ALL
+        }
+        binding.selectedRvList.apply {
+            layoutManager = FlexboxLayoutManager(context)
+        }.setup {
+            addType<QuickEntranceMenuItemEntity>(R.layout.item_quick_entrance_config)
+            itemTouchHelper = ItemTouchHelper(object : DefaultItemTouchCallback() {
+                /**
+                 * 当拖拽动作完成且松开手指时触发
+                 */
+                override fun onDrag(
+                    source: BindingAdapter.BindingViewHolder,
+                    target: BindingAdapter.BindingViewHolder
+                ) {
+
+                }
+            })
+            onBind {
+                onQuickEntranceBinding(this, false)
+            }
+        }.models = selectedQuickEntranceConfig
+        binding.allRvList.apply {
+            layoutManager = FlexboxLayoutManager(context).apply {
+                flexDirection = FlexDirection.ROW        // 横向布局
+                flexWrap = FlexWrap.WRAP
+                alignItems = AlignItems.STRETCH   // ✅ 不会被压扁
+            }
+        }.setup {
+            addType<QuickEntranceMenuItemEntity>(R.layout.item_quick_entrance_not_config)
+            onBind {
+                onQuickEntranceNotConfigBinding(this, true)
+            }
+        }.models = allQuickEntranceConfig
+        binding.save.setDebouncedClickListener {
+            save(selectedQuickEntranceConfig.map { it.permission.name }
+                .joinToString(","))
+            dialog.dismiss()
+        }
+        binding.cancel.setDebouncedClickListener {
+            dialog.dismiss()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onQuickEntranceBinding(
+        holder: BindingAdapter.BindingViewHolder, showAdd: Boolean = false
+    ) {
+        val itemBinding = holder.getBinding<ItemQuickEntranceConfigBinding>()
+        val item = holder.getModel<QuickEntranceMenuItemEntity>()
+        itemBinding.homeMenuIv.loadSkinIcon(item.menuIcon)
+        itemBinding.homeMenuTv.text = item.menuText
+        itemBinding.add.isVisible =
+            showAdd && !selectedQuickEntranceConfig.map { it.permission.functionalPermission }
+                .contains(item.permission.functionalPermission)
+        itemBinding.remove.isVisible = !showAdd
+        itemBinding.root.setDebouncedClickListener {
+            if (showAdd) {
+                if (selectedQuickEntranceConfig.size == 8) {
+                    PopTip.build().tip(CommonUtils.getStr("quick_entrance_most_set_tip"))
+                    return@setDebouncedClickListener
+                }
+                item.itemOrientationDrag = ItemOrientation.ALL
+                selectedQuickEntranceConfig.add(item)
+            } else {
+                item.itemOrientationDrag = ItemOrientation.NONE
+                selectedQuickEntranceConfig.removeIf { it.type == item.type }
+            }
+            binding.selectedRvList.adapter?.notifyDataSetChanged()
+            binding.allRvList.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onQuickEntranceNotConfigBinding(
+        holder: BindingAdapter.BindingViewHolder, showAdd: Boolean = false
+    ) {
+        val itemBinding = holder.getBinding<ItemQuickEntranceNotConfigBinding>()
+        val item = holder.getModel<QuickEntranceMenuItemEntity>()
+        itemBinding.homeMenuIv.loadSkinIcon(item.menuIcon)
+        itemBinding.homeMenuTv.text = item.menuText
+        itemBinding.add.isVisible =
+            showAdd && !selectedQuickEntranceConfig.map { it.permission.functionalPermission }
+                .contains(item.permission.functionalPermission)
+        itemBinding.remove.isVisible = !showAdd
+        itemBinding.root.setDebouncedClickListener {
+            if (!selectedQuickEntranceConfig.map { it.permission.functionalPermission }
+                    .contains(item.permission.functionalPermission)) {
+                if (selectedQuickEntranceConfig.size == 8) {
+                    PopTip.build().tip(CommonUtils.getStr("quick_entrance_most_set_tip"))
+                    return@setDebouncedClickListener
+                }
+                item.itemOrientationDrag = ItemOrientation.ALL
+                selectedQuickEntranceConfig.add(item)
+            } else {
+                item.itemOrientationDrag = ItemOrientation.NONE
+                selectedQuickEntranceConfig.removeIf { it.type == item.type }
+            }
+            binding.selectedRvList.adapter?.notifyDataSetChanged()
+            binding.allRvList.adapter?.notifyDataSetChanged()
+        }
+    }
+
+
+    companion object {
+        /**
+         * 显示
+         */
+        @JvmStatic
+        fun show(save: (String) -> Unit) {
+            FullScreenDialog.build().setCustomView(QuickEntranceConfigDialog(save)).show()
+        }
+    }
+}

+ 285 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/TextDropDownDialog.kt

@@ -0,0 +1,285 @@
+package com.grkj.iscs_mc.features.main.dialog
+
+import android.view.Gravity
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.core.widget.addTextChangedListener
+import com.drake.brv.item.ItemExpand
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogDropDownListBinding
+import com.grkj.iscs_mc.databinding.ItemHomeTextDropDownBinding
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+import me.jessyan.autosize.utils.AutoSizeUtils
+
+/**
+ * 通用下拉文本选择框,支持单选和多选
+ * 使用方式:
+ * 1. 单选:TextDropDownDialog.showSingle(dataList) { selected -> ... }
+ * 1. 单选树形:TextDropDownDialog.showSingleTree(dataList) { selected -> ... }
+ * 2. 多选:TextDropDownDialog.showMulti(dataList) { selectedList -> ... }
+ * 2. 多选树形:TextDropDownDialog.showMultiTree(dataList) { selectedList -> ... }
+ */
+class TextDropDownDialog(
+    private val dataList: List<TextDropDownEntity>,
+    private val multiSelect: Boolean,
+    private val treeSelect: Boolean,
+    private val showSearchView: Boolean = false,
+    private val canSelectedParent: Boolean = false,
+    private val onSelect: (TextDropDownEntity) -> Unit,
+    private val onMultiSelect: (List<TextDropDownEntity>?) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_drop_down_list) {
+
+    private lateinit var binding: DialogDropDownListBinding
+    private val indentPx: Int by lazy { AutoSizeUtils.dp2px(binding.root.context, 20f) }
+
+    override fun onBind(dialog: CustomDialog?, v: View) {
+        binding = DialogDropDownListBinding.bind(v)
+        binding.searchKey.isVisible = showSearchView
+        binding.searchKey.addTextChangedListener(onTextChanged = { text, _, _, _ ->
+            binding.dropDownRv.models =
+                dataList.filter { it.getShowText().contains(text.toString()) }
+        })
+        if (treeSelect) {
+            binding.dropDownRv.linear().setup {
+                addType<TextDropDownEntity>(R.layout.item_home_text_drop_down)
+                onBind {
+                    val item = getModel<TextDropDownEntity>()
+                    val itemBinding = getBinding<ItemHomeTextDropDownBinding>()
+                    itemBinding.arrowIv.isVisible = true
+                    // 缩进
+                    itemBinding.rootLayout.setPadding(indentPx * item.getLevel(), 0, 0, 0)
+                    if (item.getChildren().isEmpty()) {
+                        itemBinding.arrowIv.loadSkinIcon("icon_drop_down_tree_point.png")
+                    } else {
+                        itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                    }
+                    itemBinding.arrowIv.setDebouncedClickListener {
+                        if (item.itemExpand) collapse() else expand()
+                        itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                    }
+                    // 文本和选中
+                    itemBinding.dropDownText.text = item.getShowText()
+                    itemBinding.checkIv.isVisible = item.isSelected()
+                    itemBinding.rootLayout.setDebouncedClickListener {
+                        if (multiSelect) {
+                            if (item.getChildren().isEmpty() || canSelectedParent) {
+                                item.setSelected(!item.isSelected())
+                                itemBinding.checkIv.isVisible = item.isSelected()
+                                val selected = binding.dropDownRv.models
+                                    ?.filterIsInstance<TextDropDownEntity>()
+                                    ?.filter { it.isSelected() }
+                                onMultiSelect(selected)
+                                adapter.notifyDataSetChanged()
+                            } else {
+                                if (item.itemExpand) collapse() else expand()
+                                itemBinding.arrowIv.rotation = if (item.itemExpand) 90f else 0f
+                            }
+                        } else {
+                            onSelect(item)
+                            dialog?.dismiss()
+                        }
+                    }
+                }
+            }
+        } else {
+            binding.dropDownRv.linear().setup {
+                addType<TextDropDownEntity>(R.layout.item_home_text_drop_down)
+                onBind {
+                    val item = getModel<TextDropDownEntity>()
+                    val itemBinding = getBinding<ItemHomeTextDropDownBinding>()
+                    itemBinding.arrowIv.isVisible = false
+                    if (item.getChildren().isEmpty()) {
+                        itemBinding.arrowIv.loadSkinIcon("icon_drop_down_tree_point.png")
+                    } else {
+                        itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                    }
+                    itemBinding.arrowIv.setDebouncedClickListener {
+                        if (item.itemExpand) collapse() else expand()
+                        itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                    }
+                    itemBinding.dropDownText.text = item.getShowText()
+                    itemBinding.checkIv.isVisible = item.isSelected()
+                    itemBinding.root.setDebouncedClickListener {
+                        if (multiSelect) {
+                            item.setSelected(!item.isSelected())
+                            itemBinding.checkIv.isVisible = item.isSelected()
+                            val selected = binding.dropDownRv.models
+                                ?.filterIsInstance<TextDropDownEntity>()
+                                ?.filter { it.isSelected() }
+                            onMultiSelect(selected)
+                        } else {
+                            onSelect(item)
+                            dialog?.dismiss()
+                        }
+                    }
+                }
+            }
+        }
+        binding.dropDownRv.models = dataList
+    }
+
+    companion object {
+        /**
+         * 显示单选弹窗
+         */
+        @JvmStatic
+        fun showSingle(
+            data: List<TextDropDownEntity>,
+            view: View,
+            showSearchView: Boolean = false,
+            onSelect: (TextDropDownEntity) -> Unit
+        ) {
+            CustomDialog.show(
+                TextDropDownDialog(data, false, false, showSearchView, false, onSelect) { }
+            ).setAlignBaseViewGravity(view, Gravity.BOTTOM or Gravity.CENTER).setWidth(view.width)
+        }
+
+        /**
+         * 显示多选弹窗
+         */
+        @JvmStatic
+        fun showMulti(
+            data: List<TextDropDownEntity>,
+            view: View,
+            showSearchView: Boolean = false,
+            onMultiSelect: (List<TextDropDownEntity>?) -> Unit
+        ) {
+            CustomDialog.show(
+                TextDropDownDialog(data, true, false, showSearchView, false, {}) { selected ->
+                    onMultiSelect(
+                        selected
+                    )
+                }
+            ).setAlignBaseViewGravity(view, Gravity.BOTTOM or Gravity.CENTER).setWidth(view.width)
+        }
+
+        /**
+         * 显示单选树形弹窗
+         */
+        @JvmStatic
+        fun showSingleTree(
+            data: List<TextDropDownEntity>,
+            view: View,
+            showSearchView: Boolean = false,
+            canSelectedParent: Boolean = true,
+            onSelect: (TextDropDownEntity) -> Unit
+        ) {
+            CustomDialog.show(
+                TextDropDownDialog(
+                    data,
+                    false,
+                    true,
+                    showSearchView,
+                    canSelectedParent,
+                    onSelect
+                ) { }
+            ).setAlignBaseViewGravity(view, Gravity.BOTTOM or Gravity.CENTER).setWidth(view.width)
+        }
+
+        /**
+         * 显示多选树形弹窗
+         */
+        @JvmStatic
+        fun showMultiTree(
+            data: List<TextDropDownEntity>,
+            view: View,
+            showSearchView: Boolean = false,
+            canSelectedParent: Boolean = false,
+            onMultiSelect: (List<TextDropDownEntity>?) -> Unit
+        ) {
+            CustomDialog.show(
+                TextDropDownDialog(
+                    data,
+                    true,
+                    true,
+                    showSearchView,
+                    canSelectedParent,
+                    {}) { selected ->
+                    onMultiSelect(
+                        selected
+                    )
+                }
+            ).setAlignBaseViewGravity(view, Gravity.BOTTOM or Gravity.CENTER).setWidth(view.width)
+        }
+    }
+
+    /**
+     * 下拉弹窗数据实体接口
+     */
+    interface TextDropDownEntity : ItemExpand {
+        fun getId(): Long?
+        fun getTag(): String
+        fun getShowText(): String
+        fun getData(): Any?
+        fun isSelected(): Boolean
+        fun setSelected(isSelect: Boolean)
+        fun getChildren(): List<TextDropDownEntity>
+        fun setChildren(children: List<TextDropDownEntity>)
+        fun getLevel(): Int
+
+        fun checkItemSelected(action: (TextDropDownEntity) -> Boolean)
+
+        fun findTreeData(action: (TextDropDownEntity) -> Boolean): List<TextDropDownEntity>
+    }
+
+    /**
+     * 简易实现
+     */
+    class SimpleTextDropDownEntity(
+        var dataId: Long? = null,
+        var dataObject: Any? = null,
+        val dataTag: String = "",
+        var dataText: String = "",
+        var dataLevel: Int = 0,
+        override var itemExpand: Boolean = false,
+        override var itemGroupPosition: Int = 0
+    ) : TextDropDownEntity {
+        private var selected = false
+        private var children: List<TextDropDownEntity> = emptyList<TextDropDownEntity>()
+        override fun getId() = dataId
+        override fun getTag() = dataTag
+        override fun getShowText() = dataText
+        override fun getData() = dataObject
+        override fun isSelected() = selected
+        override fun setSelected(isSelect: Boolean) {
+            selected = isSelect
+        }
+
+        override fun getChildren(): List<TextDropDownEntity> {
+            return children
+        }
+
+        override fun setChildren(children: List<TextDropDownEntity>) {
+            this.children = children
+        }
+
+        override fun getItemSublist(): List<Any?>? {
+            return children
+        }
+
+        override fun getLevel() = dataLevel
+
+        override fun checkItemSelected(action: (TextDropDownEntity) -> Boolean) {
+            selected = action(this)
+            children.forEach {
+                it.checkItemSelected(action)
+            }
+        }
+
+        override fun findTreeData(action: (TextDropDownEntity) -> Boolean): List<TextDropDownEntity> {
+            val result = mutableListOf<TextDropDownEntity>()
+            if (action(this)) {
+                result.add(this)
+            }
+            result += children.flatMap { it.findTreeData(action) }
+            return result
+        }
+
+    }
+}

+ 186 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/AddRoleDialog.kt

@@ -0,0 +1,186 @@
+package com.grkj.iscs_mc.features.main.dialog.data_manage
+
+import android.view.View
+import androidx.core.view.isVisible
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.google.android.gms.common.internal.service.Common
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogAddRoleBinding
+import com.grkj.iscs_mc.databinding.ItemRoleBinding
+import com.grkj.iscs_mc.features.main.entity.AddRoleDataEntity
+import com.grkj.iscs_mc.features.main.entity.RoleManageFunctionalPermissionsEntity
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+import me.jessyan.autosize.utils.AutoSizeUtils
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.tip
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 添加角色对话框,基于 DialogX
+ * 使用:AddRoleDialog.show { addRoleData -> ... }
+ */
+class AddRoleDialog(
+    private val onConfirm: (AddRoleDataEntity, dialog: CustomDialog) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_add_role) {
+
+    private lateinit var binding: DialogAddRoleBinding
+    private val indentPx: Int by lazy { AutoSizeUtils.dp2px(binding.root.context, 20f) }
+    private var roleData = RoleManageFunctionalPermissionsEntity
+        .getFunctionalPermissions()
+        .reversed()
+        .toMutableList()
+    private val selectedPermission = mutableListOf<String>()
+
+    override fun onBind(dialog: CustomDialog, v: View) {
+        binding = DialogAddRoleBinding.bind(v)
+        dialog?.isCancelable = false
+        // 遮罩色
+        dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+
+        // 关闭
+        binding.closeIv.setDebouncedClickListener {
+            dialog?.dismiss()
+        }
+        // 确认
+        binding.confirm.setDebouncedClickListener {
+            if (binding.roleNameEt.text.isNullOrBlank()) {
+                PopTip.build().tip(CommonUtils.getStr("please_input_role_name"))
+                return@setDebouncedClickListener
+            }
+            if (binding.roleKeyEt.text.isNullOrBlank()) {
+                PopTip.build().tip(CommonUtils.getStr("please_input_permission_characters"))
+                return@setDebouncedClickListener
+            }
+            val data = AddRoleDataEntity().apply {
+                roleName = binding.roleNameEt.text.toString()
+                roleKeys = binding.roleKeyEt.text.toString()
+                status = when (binding.statusRg.checkedRadioButtonId) {
+                    binding.activateRb.id -> true
+                    binding.deactivateRb.id -> false
+                    else -> null
+                }
+                functionalPermissions = collectSelected(roleData)
+            }
+            onConfirm(data, dialog)
+        }
+        // 取消
+        binding.cancel.setDebouncedClickListener {
+            dialog?.dismiss()
+        }
+
+        // 列表
+        binding.roleListRv.linear().setup {
+            addType<RoleManageFunctionalPermissionsEntity>(R.layout.item_role)
+            onBind {
+                val item = getModel<RoleManageFunctionalPermissionsEntity>()
+                val itemBinding = getBinding<ItemRoleBinding>()
+                // 缩进
+                itemBinding.rootLayout.setPadding(indentPx * item.level, 0, 0, 0)
+                // 展开/收起
+                itemBinding.rootLayout.setDebouncedClickListener {
+                    if (item.itemExpand) collapse() else expand()
+                    itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                }
+                if (item.children.isEmpty()) {
+                    itemBinding.arrowIv.loadSkinIcon("icon_drop_down_tree_point.png")
+                } else {
+                    itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                }
+                // 文本和选中
+                itemBinding.roleTv.text = item.description
+                itemBinding.roleCb.setOnCheckedChangeListener(null)
+                itemBinding.roleCb.isChecked = item.isSelected
+                itemBinding.roleCb.setOnCheckedChangeListener { _, checked ->
+                    item.isSelected = checked
+                    // 子节点同步
+                    if (item.children.isNotEmpty()) syncChildren(item, checked)
+                    // 收集权限
+                    if (checked) selectedPermission += item.functionalPermission
+                    else selectedPermission.remove(item.functionalPermission)
+                    binding.allSelected.isChecked = roleData.all { it.allSelectedRecursively() }
+                    binding.roleListRv.adapter?.notifyDataSetChanged()
+                }
+            }
+        }.models = roleData
+
+        // 全部展开/收起
+        binding.expandCollapse.setOnCheckedChangeListener { _, expand ->
+            val expandData = roleData.toList()
+            toggleExpand(expandData, expand)
+            binding.allSelected.isChecked = roleData.all { it.allSelectedRecursively() }
+            binding.roleListRv.models = expandData
+        }
+
+        // 全选/全不选
+        binding.allSelected.setOnCheckedChangeListener { _, allSel ->
+            syncAll(roleData, allSel)
+            binding.roleListRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    // 子节点同步选中状态
+    private fun syncChildren(item: RoleManageFunctionalPermissionsEntity, checked: Boolean) {
+        item.children.forEach {
+            it.isSelected = checked
+            syncChildren(it, checked)
+        }
+    }
+
+    // 全局展开/收起
+    private fun toggleExpand(list: List<RoleManageFunctionalPermissionsEntity>, expand: Boolean) {
+        list.forEach {
+            if (it.children.isNotEmpty()) {
+                it.itemExpand = expand
+                toggleExpand(it.children, expand)
+            }
+        }
+    }
+
+    // 全局同步选中
+    private fun syncAll(list: List<RoleManageFunctionalPermissionsEntity>, checked: Boolean) {
+        list.forEach {
+            it.isSelected = checked
+            if (checked) selectedPermission += it.functionalPermission
+            else selectedPermission.remove(it.functionalPermission)
+            syncAll(it.children, checked)
+        }
+    }
+
+    // 收集所有选中权限实体
+    private fun collectSelected(
+        list: List<RoleManageFunctionalPermissionsEntity>
+    ): MutableList<RoleManageFunctionalPermissionsEntity> {
+        val result = mutableListOf<RoleManageFunctionalPermissionsEntity>()
+        list.forEach {
+            if (it.isSelected) {
+                result += it
+                result += collectSelected(it.children)
+            }
+        }
+        return result
+    }
+
+    // 递归全选判断
+    private fun RoleManageFunctionalPermissionsEntity.allSelectedRecursively(): Boolean {
+        if (!isSelected) return false
+        return children.all { it.allSelectedRecursively() }
+    }
+
+    companion object {
+        /**
+         * 显示对话框并设置确认回调
+         */
+        @JvmStatic
+        fun show(onConfirm: (AddRoleDataEntity, dialog: CustomDialog) -> Unit) {
+            CustomDialog.show(
+                AddRoleDialog(onConfirm),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

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

@@ -0,0 +1,96 @@
+package com.grkj.iscs_mc.features.main.dialog.data_manage
+
+import android.view.View
+import com.grkj.data.model.vo.AddUserDataVo
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogAddUserBinding
+import com.grkj.iscs_mc.features.main.dialog.TextDropDownDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.tip
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 新增用户对话框,基于 DialogX,支持多选角色和岗位
+ * 使用:
+ * AddUserDialog.show(
+ *     roleData, workstationData
+ * ) { addUserVo -> /* handle */ }
+ */
+class AddUserDialog(
+    private val roleData: List<TextDropDownDialog.TextDropDownEntity>,
+    private val onConfirm: (AddUserDataVo, CustomDialog) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_add_user) {
+
+    private lateinit var binding: DialogAddUserBinding
+    private var selectedRoles: List<TextDropDownDialog.TextDropDownEntity> = emptyList()
+
+    override fun onBind(dialog: CustomDialog, v: View) {
+        binding = DialogAddUserBinding.bind(v)
+        dialog?.isCancelable = false
+        dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+
+        // 角色多选
+        binding.roleTv.setDebouncedClickListener {
+            TextDropDownDialog.showMulti(roleData, binding.roleTv) { list ->
+                selectedRoles = list.orEmpty()
+                binding.roleTv.text = selectedRoles.joinToString(",") { it.getShowText() }
+            }
+        }
+
+        // 取消/关闭
+        binding.cancel.setDebouncedClickListener { dialog?.dismiss() }
+        binding.closeIv.setDebouncedClickListener { dialog?.dismiss() }
+
+        // 确认
+        binding.confirm.setDebouncedClickListener {
+            if (!checkData()) return@setDebouncedClickListener
+            val vo = AddUserDataVo(
+                binding.usernameEt.text.trim().toString(),
+                binding.nicknameEt.text.trim().toString(),
+                "",
+                selectedRoles.mapNotNull { it.getId() },
+                binding.statusRg.checkedRadioButtonId == binding.activateRb.id
+            )
+            onConfirm(vo, dialog)
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.usernameEt.text.isNullOrBlank()) {
+            PopTip.build().tip(CommonUtils.getStr("please_input_username"))
+            return false
+        }
+        if (binding.nicknameEt.text.isNullOrBlank()) {
+            PopTip.build().tip(CommonUtils.getStr("please_input_nickname"))
+            return false
+        }
+        if (selectedRoles.isEmpty()) {
+            PopTip.build().tip(CommonUtils.getStr("please_select_role"))
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tip(CommonUtils.getStr("please_select_status"))
+            return false
+        }
+        return true
+    }
+
+    companion object {
+        /**
+         * 显示新增用户对话框并设置确认回调
+         */
+        @JvmStatic
+        fun show(
+            roleData: List<TextDropDownDialog.TextDropDownEntity>,
+            onConfirm: (AddUserDataVo, CustomDialog) -> Unit
+        ) {
+            CustomDialog.show(
+                AddUserDialog(roleData, onConfirm),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

+ 66 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/FilterRoleDialog.kt

@@ -0,0 +1,66 @@
+package com.grkj.iscs_mc.features.main.dialog.data_manage
+
+import android.view.View
+import com.grkj.data.model.vo.RoleManageFilterVo
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogFilterRoleBinding
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 筛选角色对话框,基于 DialogX
+ * 使用:
+ * FilterRoleDialog.show { filterVo -> /* handle */ }
+ */
+class FilterRoleDialog(
+    private val onConfirm: (RoleManageFilterVo) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_filter_role) {
+
+    private lateinit var binding: DialogFilterRoleBinding
+
+    override fun onBind(dialog: CustomDialog?, v: View) {
+        binding = DialogFilterRoleBinding.bind(v)
+        dialog?.isCancelable = false
+        dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+
+        // 关闭/取消
+        binding.closeIv.setDebouncedClickListener { dialog?.dismiss() }
+        binding.cancel.setDebouncedClickListener { dialog?.dismiss() }
+
+        // 确认筛选
+        binding.confirm.setDebouncedClickListener {
+            val name = binding.roleNameTv.text.trim().toString()
+            val key = binding.roleKeyEt.text.trim().toString()
+            val status = when (binding.statusRg.checkedRadioButtonId) {
+                binding.activateRb.id -> true
+                binding.deactivateRb.id -> false
+                else -> null
+            }
+            onConfirm(RoleManageFilterVo(name, key, status))
+            dialog?.dismiss()
+            clearFields()
+        }
+    }
+
+    private fun clearFields() {
+        binding.roleNameTv.text?.clear()
+        binding.roleKeyEt.text?.clear()
+        binding.statusRg.clearCheck()
+    }
+
+    companion object {
+        /**
+         * 显示筛选角色对话框并设置回调
+         */
+        @JvmStatic
+        fun show(onConfirm: (RoleManageFilterVo) -> Unit) {
+            CustomDialog.show(
+                FilterRoleDialog(onConfirm),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

+ 60 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/FilterUserDialog.kt

@@ -0,0 +1,60 @@
+package com.grkj.iscs_mc.features.main.dialog.data_manage
+
+import android.view.View
+import com.grkj.data.model.vo.UserManageFilterVo
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogFilterUserBinding
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 筛选用户对话框,基于 DialogX
+ * 使用:
+ * FilterUserDialog.show { filterVo -> /* handle */ }
+ */
+class FilterUserDialog(
+    private val onConfirm: (UserManageFilterVo) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_filter_user) {
+
+    private lateinit var binding: DialogFilterUserBinding
+
+    override fun onBind(dialog: CustomDialog?, v: View) {
+        binding = DialogFilterUserBinding.bind(v)
+        dialog?.isCancelable = false
+        // 设置遮罩色
+        dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+
+        // 关闭/取消
+        binding.closeIv.setDebouncedClickListener { dialog?.dismiss() }
+        binding.cancel.setDebouncedClickListener { dialog?.dismiss() }
+
+        // 确认
+        binding.confirm.setDebouncedClickListener {
+            val name = binding.nicknameEt.text.trim().toString()
+            val card = binding.cardcodeEt.text.trim().toString()
+            val status = when (binding.statusRg.checkedRadioButtonId) {
+                binding.activateRb.id -> true
+                binding.deactivateRb.id -> false
+                else -> null
+            }
+            onConfirm(UserManageFilterVo(name, card, status))
+            dialog?.dismiss()
+            clearFields()
+        }
+    }
+
+    private fun clearFields() {
+        binding.nicknameEt.text?.clear()
+        binding.cardcodeEt.text?.clear()
+        binding.statusRg.clearCheck()
+    }
+
+    companion object {
+        @JvmStatic
+        fun show(onConfirm: (UserManageFilterVo) -> Unit) {
+            CustomDialog.show(FilterUserDialog(onConfirm), CustomDialog.ALIGN.CENTER)
+        }
+    }
+}

+ 198 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/data_manage/UpdateRoleDialog.kt

@@ -0,0 +1,198 @@
+package com.grkj.iscs_mc.features.main.dialog.data_manage
+
+import android.view.View
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogUpdateRoleBinding
+import com.grkj.iscs_mc.databinding.ItemRoleBinding
+import com.grkj.iscs_mc.features.main.entity.RoleManageFunctionalPermissionsEntity
+import com.grkj.iscs_mc.features.main.entity.UpdateRoleDataEntity
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.tip
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+import me.jessyan.autosize.utils.AutoSizeUtils
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * 修改角色对话框,基于 DialogX
+ * 使用:
+ * UpdateRoleDialog.show(updateEntity) { updated -> /* handle */ }
+ */
+class UpdateRoleDialog(
+    private val updateEntity: UpdateRoleDataEntity,
+    private val onConfirm: (UpdateRoleDataEntity, CustomDialog) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_update_role) {
+    private val logger: Logger = LoggerFactory.getLogger(UpdateRoleDialog::class.java)
+    private lateinit var binding: DialogUpdateRoleBinding
+    private val indentPx: Int by lazy { AutoSizeUtils.dp2px(binding.root.context, 20f) }
+    private var selectedList = mutableListOf<String>()
+    private val roleData = RoleManageFunctionalPermissionsEntity
+        .getFunctionalPermissions()
+        .reversed()
+        .toMutableList()
+
+    override fun onBind(dialog: CustomDialog, v: View) {
+        binding = DialogUpdateRoleBinding.bind(v)
+        dialog?.isCancelable = false
+        dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+
+        // 预填数据
+        binding.roleNameEt.setText(updateEntity.roleName)
+        binding.roleKeyEt.setText(updateEntity.roleKeys)
+        binding.statusRg.clearCheck()
+        updateEntity.status?.let { binding.activateRb.isChecked = it }
+        selectedList.clear()
+        selectedList.addAll(updateEntity.functionalPermissions.flatMap { it.getFunctionalPermissionWithChildren() })
+        logger.info("selectedList: $selectedList")
+        markSelected(roleData)
+        binding.roleListRv.linear().setup {
+            addType<RoleManageFunctionalPermissionsEntity>(R.layout.item_role)
+            onBind {
+                val item = getModel<RoleManageFunctionalPermissionsEntity>()
+                val itemBinding = getBinding<ItemRoleBinding>()
+                // 缩进
+                itemBinding.rootLayout.setPadding(indentPx * item.level, 0, 0, 0)
+                // 展开收起
+                itemBinding.rootLayout.setDebouncedClickListener {
+                    if (item.itemExpand) collapse() else expand()
+                    itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                }
+                if (item.children.isEmpty()) {
+                    itemBinding.arrowIv.loadSkinIcon("icon_drop_down_tree_point.png")
+                } else {
+                    itemBinding.arrowIv.loadSkinIcon(if (item.itemExpand) "icon_drop_down_tree_expand.png" else "icon_drop_down_tree_collapse.png")
+                }
+                // 选中
+                itemBinding.roleTv.text = item.description
+                itemBinding.roleCb.setOnCheckedChangeListener(null)
+                itemBinding.roleCb.isChecked = item.isSelected
+                itemBinding.roleCb.setOnCheckedChangeListener { _, checked ->
+                    item.isSelected = checked
+                    // 子节点同步
+                    if (item.children.isNotEmpty()) syncChildren(item, checked)
+                    // 收集权限
+                    if (checked) selectedList += item.functionalPermission
+                    else selectedList.remove(item.functionalPermission)
+                    binding.allSelected.isChecked = roleData.all { it.allSelectedRecursively() }
+                    binding.roleListRv.adapter?.notifyDataSetChanged()
+                }
+            }
+        }.models = roleData
+
+        // 全选展开控件
+        binding.expandCollapse.setOnCheckedChangeListener { _, expanded ->
+            val expandData = roleData.toList()
+            toggleExpand(expandData, expanded)
+            binding.allSelected.isChecked = roleData.all { it.allSelectedRecursively() }
+            binding.roleListRv.models = expandData
+        }
+        binding.allSelected.setOnCheckedChangeListener(null)
+        binding.allSelected.isChecked = roleData.all { it.allSelectedRecursively() }
+        binding.allSelected.setOnCheckedChangeListener { _, selectAll ->
+            syncAll(roleData, selectAll)
+            binding.roleListRv.adapter?.notifyDataSetChanged()
+        }
+
+        // 取消
+        binding.closeIv.setDebouncedClickListener { dialog?.dismiss() }
+        binding.cancel.setDebouncedClickListener { dialog?.dismiss() }
+
+        // 确认
+        binding.confirm.setDebouncedClickListener {
+            if (binding.roleNameEt.text.isNullOrBlank()) {
+                PopTip.build().tip(CommonUtils.getStr("please_input_role_name"))
+                return@setDebouncedClickListener
+            }
+            if (binding.roleKeyEt.text.isNullOrBlank()) {
+                PopTip.build().tip(CommonUtils.getStr("please_input_permission_characters"))
+                return@setDebouncedClickListener
+            }
+            updateEntity.roleName = binding.roleNameEt.text.toString()
+            updateEntity.roleKeys = binding.roleKeyEt.text.toString()
+            updateEntity.status = binding.activateRb.isChecked
+            // 收集权限
+            updateEntity.functionalPermissions = collectSelected(roleData)
+            onConfirm(updateEntity, dialog)
+        }
+    }
+
+    private fun markSelected(list: List<RoleManageFunctionalPermissionsEntity>) {
+        list.forEach { item ->
+            // step1: 根据传入的 functionalPermissions 标记自己
+            item.isSelected = selectedList.contains(item.functionalPermission)
+            // step2: 先递归标记孩子
+            if (item.children.isNotEmpty()) {
+                markSelected(item.children)
+                // step3: 如果所有孩子都被选了,那自己也打勾
+                if (item.children.all { it.isSelected }) {
+                    item.isSelected = true
+                }
+            }
+        }
+    }
+
+    private fun syncChildren(item: RoleManageFunctionalPermissionsEntity, checked: Boolean) {
+        item.children.forEach {
+            it.isSelected = checked
+            syncChildren(it, checked)
+        }
+    }
+
+    private fun toggleExpand(data: List<RoleManageFunctionalPermissionsEntity>, expand: Boolean) {
+        data.forEach {
+            it.itemExpand = expand
+            toggleExpand(it.children, expand)
+        }
+    }
+
+    // 全局同步选中
+    private fun syncAll(list: List<RoleManageFunctionalPermissionsEntity>, checked: Boolean) {
+        list.forEach {
+            it.isSelected = checked
+            if (checked) selectedList += it.functionalPermission
+            else selectedList.remove(it.functionalPermission)
+            syncAll(it.children, checked)
+        }
+    }
+
+    private fun collectSelected(
+        data: List<RoleManageFunctionalPermissionsEntity>
+    ): MutableList<RoleManageFunctionalPermissionsEntity> {
+        val res = mutableListOf<RoleManageFunctionalPermissionsEntity>()
+        data.forEach {
+            if (it.isSelected) {
+                res += it
+                res += collectSelected(it.children)
+            }
+        }
+        return res
+    }
+
+    private fun RoleManageFunctionalPermissionsEntity.allSelectedRecursively(): Boolean {
+        if (!isSelected) return false
+        return children.all { it.allSelectedRecursively() }
+    }
+
+    companion object {
+        /**
+         * 展示修改角色对话框
+         */
+        @JvmStatic
+        fun show(
+            updateEntity: UpdateRoleDataEntity,
+            onConfirm: (UpdateRoleDataEntity, CustomDialog) -> Unit
+        ) {
+            CustomDialog.show(
+                UpdateRoleDialog(updateEntity, onConfirm),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

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

@@ -0,0 +1,107 @@
+package com.grkj.iscs_mc.features.main.dialog.data_manage
+
+import android.view.View
+import com.grkj.data.model.vo.UpdateUserDataVo
+import com.grkj.data.model.vo.UserManageVo
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogUpdateUserBinding
+import com.grkj.iscs_mc.features.main.dialog.TextDropDownDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.tip
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 更新用户对话框,基于 DialogX,支持多选角色和岗位
+ * 使用:
+ * UpdateUserDialog.show(
+ *     userVo, roleData, workstationData
+ * ) { updateVo -> /* handle */ }
+ */
+class UpdateUserDialog(
+    private val userVo: UserManageVo,
+    private val roleData: List<TextDropDownDialog.TextDropDownEntity>,
+    private val onConfirm: (UpdateUserDataVo, CustomDialog) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_update_user) {
+
+    private lateinit var binding: DialogUpdateUserBinding
+    private var selectedRoles = mutableListOf<TextDropDownDialog.TextDropDownEntity>()
+
+    override fun onBind(dialog: CustomDialog, v: View) {
+        binding = DialogUpdateUserBinding.bind(v)
+        dialog?.isCancelable = false
+        dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+
+        binding.cardcodeEt.isEnabled = false
+        binding.usernameEt.isEnabled = false
+        binding.roleTv.isEnabled = userVo.userId != 1L
+
+        // 预填数据
+        binding.usernameEt.setText(userVo.userName)
+        binding.nicknameEt.setText(userVo.nickName)
+        binding.cardcodeEt.setText(userVo.cardCodes.joinToString(","))
+        binding.roleTv.text = userVo.roleNames.filterNotNull().joinToString(",")
+        binding.activateRb.isChecked = userVo.getStatus()
+        binding.deactivateRb.isChecked = !userVo.getStatus()
+
+        // 标记已选
+        selectedRoles = roleData.filter { it.getShowText() in userVo.roleNames }.toMutableList()
+
+        // 角色多选
+        binding.roleTv.setOnClickListener {
+            TextDropDownDialog.showMulti(roleData.apply {
+                forEach {
+                    it.setSelected(selectedRoles.map { it.getShowText() }
+                        .contains(it.getShowText()))
+                }
+            }, binding.roleTv) { list ->
+                selectedRoles = list.orEmpty().toMutableList()
+                binding.roleTv.text = selectedRoles.joinToString(",") { it.getShowText() }
+            }
+        }
+
+        // 取消/关闭
+        binding.cancel.setDebouncedClickListener { dialog?.dismiss() }
+        binding.closeIv.setDebouncedClickListener { dialog?.dismiss() }
+
+        // 确认
+        binding.confirm.setDebouncedClickListener {
+            val username = binding.usernameEt.text.trim().toString()
+            val name = binding.nicknameEt.text.trim().toString()
+            val card = binding.cardcodeEt.text.trim().toString()
+            if (name.isBlank()) return@setDebouncedClickListener PopTip.build()
+                .tip(CommonUtils.getStr("please_input_nickname"))
+            if (selectedRoles.isEmpty()) return@setDebouncedClickListener PopTip.build()
+                .tip(CommonUtils.getStr("please_select_role"))
+            val isActive = binding.statusRg.checkedRadioButtonId == binding.activateRb.id
+            val updateVo = UpdateUserDataVo(
+                userVo.userId,
+                username,
+                name,
+                card,
+                selectedRoles.mapNotNull { it.getId() },
+                isActive
+            )
+            onConfirm(updateVo, dialog)
+        }
+    }
+
+    companion object {
+        /**
+         * 显示更新用户对话框并设置回调
+         */
+        @JvmStatic
+        fun show(
+            userVo: UserManageVo,
+            roleData: List<TextDropDownDialog.TextDropDownEntity>,
+            onConfirm: (UpdateUserDataVo, CustomDialog) -> Unit
+        ) {
+            CustomDialog.show(
+                UpdateUserDialog(userVo, roleData, onConfirm),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

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

@@ -0,0 +1,41 @@
+package com.grkj.iscs_mc.features.main.dialog.user_info
+
+import android.view.View
+import android.widget.TextView
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogAddFingerprintBinding
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 添加指纹弹窗
+ */
+class AddFingerprintDialog(
+    val onCancel: (CustomDialog) -> Unit, val updateTip: (TextView) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_add_fingerprint) {
+    private lateinit var binding: DialogAddFingerprintBinding
+    override fun onBind(dialog: CustomDialog, p1: View) {
+        binding = DialogAddFingerprintBinding.bind(p1)
+        dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+        dialog.isCancelable = false
+        updateTip(binding.pressTip)
+        binding.pressTip.text = CommonUtils.getStr("fingerprint_scan_tip", 3)
+        binding.cancel.setDebouncedClickListener {
+            onCancel(dialog)
+        }
+    }
+
+    companion object {
+        /**
+         * 显示弹窗
+         */
+        @JvmStatic
+        fun show(onCancel: (CustomDialog) -> Unit, updateTip: (TextView) -> Unit): CustomDialog {
+            return CustomDialog.show(
+                AddFingerprintDialog(onCancel, updateTip), CustomDialog.ALIGN.CENTER
+            ).setCancelable(false)
+        }
+    }
+}

+ 11 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/AddRoleDataEntity.kt

@@ -0,0 +1,11 @@
+package com.grkj.iscs_mc.features.main.entity
+
+/**
+ * 添加角色数据实体
+ */
+class AddRoleDataEntity {
+    var roleName: String = ""
+    var roleKeys: String = ""
+    var status: Boolean? = null
+    var functionalPermissions: List<RoleManageFunctionalPermissionsEntity> = listOf()
+}

+ 40 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/ExceptionSourceDataEntity.kt

@@ -0,0 +1,40 @@
+package com.grkj.iscs_mc.features.main.entity
+
+import com.drake.brv.item.ItemExpand
+
+/**
+ * 异常数据源实体
+ */
+class ExceptionSourceDataEntity(
+    override var itemExpand: Boolean = false,
+    override var itemGroupPosition: Int = 0
+) : ItemExpand {
+    /**
+     * 数据源id
+     */
+    var sourceDataId: Long = 0L
+
+    /**
+     * 数据源类型
+     */
+    var sourceDataType: Int = 0
+
+    /**
+     * 异常源文本
+     */
+    var sourceDataText: String = ""
+
+    /**
+     * 子类
+     */
+    var children: MutableList<ExceptionSourceDataEntity> = mutableListOf()
+
+    /**
+     * 是否选中
+     */
+    var isSelected: Boolean = false
+
+    override fun getItemSublist(): List<Any?>? {
+        return children
+    }
+}

+ 16 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/MenuItemEntity.kt

@@ -0,0 +1,16 @@
+package com.grkj.iscs_mc.features.main.entity
+
+import com.grkj.ui_base.utils.CommonUtils
+
+/**
+ * 菜单实体
+ * type 菜单类型 0-用户管理 1-角色管理 2-区域管理 3-点位管理
+ */
+data class MenuItemEntity(
+    val type: Int,
+    val menuIcon: String,
+    val menuText: String,
+    val permission: String,
+    var badgeNum: Int = 0,
+    val menuBgTint: Int = CommonUtils.getColor(com.grkj.ui_base.R.attr.colorHomeMenuBgTint),
+)

+ 69 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/QuickEntranceMenuItemEntity.kt

@@ -0,0 +1,69 @@
+package com.grkj.iscs_mc.features.main.entity
+
+import com.drake.brv.annotaion.ItemOrientation
+import com.drake.brv.item.ItemDrag
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.iscs_mc.R
+
+/**
+ * 快捷入口菜单实体
+ * type 菜单类型 0-用户管理 1-角色管理 2-区域管理 3-点位管理
+ */
+data class QuickEntranceMenuItemEntity(
+    val type: Int,
+    val menuIcon: String,
+    val menuText: String,
+    val permission: RoleFunctionalPermissionsEnum,
+    val navGraph: Int,
+    val destId: Int,
+    var badgeNum: Int = 0,
+    override var itemOrientationDrag: Int = ItemOrientation.NONE
+) : ItemDrag {
+
+    companion object {
+        /**
+         * 获取图标id
+         */
+        @JvmStatic
+        fun getMenuIcon(permission: RoleFunctionalPermissionsEnum): String {
+            return when (permission) {
+                RoleFunctionalPermissionsEnum.USER_MANAGE -> "user.svg"
+                RoleFunctionalPermissionsEnum.ROLE_MANAGE -> "users-alt.svg"
+                RoleFunctionalPermissionsEnum.USER_INFO -> "chalkboard-user.svg"
+                RoleFunctionalPermissionsEnum.RESET_PASSWORD -> "password-lock.svg"
+                RoleFunctionalPermissionsEnum.FINGERPRINT_SETTING -> "fingerprint.svg"
+                RoleFunctionalPermissionsEnum.FACE_SETTING -> "face-id-svgrepo-com.svg"
+                RoleFunctionalPermissionsEnum.CARD_SETTING -> "cards-blank.png"
+                else -> ""
+            }
+        }
+
+        /**
+         * 获取导航
+         */
+        @JvmStatic
+        fun getNavGraphId(permission: RoleFunctionalPermissionsEnum): Int {
+            return when (permission) {
+                RoleFunctionalPermissionsEnum.USER_MANAGE, RoleFunctionalPermissionsEnum.ROLE_MANAGE,
+
+                RoleFunctionalPermissionsEnum.USER_INFO,
+                RoleFunctionalPermissionsEnum.RESET_PASSWORD,
+                RoleFunctionalPermissionsEnum.FINGERPRINT_SETTING,
+                RoleFunctionalPermissionsEnum.FACE_SETTING,
+                RoleFunctionalPermissionsEnum.CARD_SETTING -> R.navigation.nav_user_info
+
+                else -> 0
+            }
+        }
+
+        /**
+         * 获取目标
+         */
+        @JvmStatic
+        fun getDestId(permission: RoleFunctionalPermissionsEnum): Int {
+            return when (permission) {
+                else -> 0
+            }
+        }
+    }
+}

+ 98 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/RoleManageFunctionalPermissionsEntity.kt

@@ -0,0 +1,98 @@
+package com.grkj.iscs_mc.features.main.entity
+
+import com.drake.brv.item.ItemExpand
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.shared.utils.i18n.I18nManager
+
+/**
+ *  角色功能权限实体
+ */
+class RoleManageFunctionalPermissionsEntity(
+    override var itemExpand: Boolean = false,
+    override var itemGroupPosition: Int
+) : ItemExpand {
+
+    var functionalPermission: String = ""
+    var description: String = ""
+    var level: Int = 0
+    var children: MutableList<RoleManageFunctionalPermissionsEntity> = mutableListOf()
+
+    var isSelected: Boolean = false
+
+    override fun getItemSublist(): List<Any?>? {
+        return children
+    }
+
+    /**
+     * 获取权限包括子节点
+     */
+    fun getFunctionalPermissionWithChildren(): List<String> {
+        fun flatten(list: List<RoleManageFunctionalPermissionsEntity>): List<String> {
+            val res = mutableListOf<String>()
+            list.forEach {
+                if (it.isSelected) {
+                    res += it.functionalPermission
+                }
+                res += flatten(it.children)
+            }
+            return res
+        }
+        return if (isSelected){
+            listOf(functionalPermission) + flatten(children)
+        }else{
+            listOf()
+        }
+    }
+
+    companion object {
+        /**
+         * 获取功能权限数据
+         */
+        @JvmStatic
+        fun getFunctionalPermissions(level: Int = 0): List<RoleManageFunctionalPermissionsEntity> {
+            return mutableListOf<RoleManageFunctionalPermissionsEntity>().apply {
+                RoleFunctionalPermissionsEnum.values().filter { it.level == level }
+                    .forEachIndexed { index, item ->
+                        val roleManageFunctionalPermissionsEntity =
+                            RoleManageFunctionalPermissionsEntity(false, index)
+                        roleManageFunctionalPermissionsEntity.functionalPermission =
+                            item.functionalPermission
+                        roleManageFunctionalPermissionsEntity.description = I18nManager.t(item.description)
+                        roleManageFunctionalPermissionsEntity.level = item.level
+                        item.children.forEachIndexed { index, child ->
+                            roleManageFunctionalPermissionsEntity.children.add(
+                                getFunctionPermission(
+                                    index, child
+                                )
+                            )
+                        }
+                        add(roleManageFunctionalPermissionsEntity)
+                    }
+            }
+        }
+
+        /**
+         * 根据权限获取
+         */
+        @JvmStatic
+        private fun getFunctionPermission(
+            index: Int,
+            item: RoleFunctionalPermissionsEnum
+        ): RoleManageFunctionalPermissionsEntity {
+            val roleManageFunctionalPermissionsEntity =
+                RoleManageFunctionalPermissionsEntity(false, index)
+            roleManageFunctionalPermissionsEntity.functionalPermission =
+                item.functionalPermission
+            roleManageFunctionalPermissionsEntity.description = I18nManager.t(item.description)
+            roleManageFunctionalPermissionsEntity.level = item.level
+            item.children.forEachIndexed { index, child ->
+                roleManageFunctionalPermissionsEntity.children.add(
+                    getFunctionPermission(
+                        index, child
+                    )
+                )
+            }
+            return roleManageFunctionalPermissionsEntity;
+        }
+    }
+}

+ 13 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/UpdateRoleDataEntity.kt

@@ -0,0 +1,13 @@
+package com.grkj.iscs_mc.features.main.entity
+
+/**
+ * 添加角色数据实体
+ */
+class UpdateRoleDataEntity {
+    var roleId: Long = 0
+    var roleName: String = ""
+    var roleKeys: String = ""
+    var status: Boolean? = null
+    var functionalPermissions: List<RoleManageFunctionalPermissionsEntity> = listOf()
+    var isPreset: Boolean = false
+}

+ 0 - 18
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/DataHomeFragment.kt

@@ -1,18 +0,0 @@
-package com.grkj.iscs_mc.features.main.fragment
-
-import com.grkj.iscs_mc.R
-import com.grkj.iscs_mc.databinding.FragmentDataHomeBinding
-import com.grkj.ui_base.base.BaseFragment
-
-/**
- * 数据管理首页
- */
-class DataHomeFragment: BaseFragment<FragmentDataHomeBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.fragment_data_home
-    }
-
-    override fun initView() {
-        TODO("Not yet implemented")
-    }
-}

+ 0 - 19
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/ExceptionHomeFragment.kt

@@ -1,19 +0,0 @@
-package com.grkj.iscs_mc.features.main.fragment
-
-import com.grkj.iscs_mc.R
-import com.grkj.iscs_mc.databinding.FragmentDataHomeBinding
-import com.grkj.iscs_mc.databinding.FragmentExceptionHomeBinding
-import com.grkj.ui_base.base.BaseFragment
-
-/**
- * 异常首页
- */
-class ExceptionHomeFragment : BaseFragment<FragmentExceptionHomeBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.fragment_exception_home
-    }
-
-    override fun initView() {
-        TODO("Not yet implemented")
-    }
-}

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

@@ -1,18 +0,0 @@
-package com.grkj.iscs_mc.features.main.fragment
-
-import com.grkj.iscs_mc.R
-import com.grkj.iscs_mc.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() {
-        TODO("Not yet implemented")
-    }
-}

+ 271 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/BackupAndRestoreFragment.kt

@@ -0,0 +1,271 @@
+package com.grkj.iscs_mc.features.main.fragment.data_manage
+
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.common.EventConstants
+import com.grkj.data.enums.BackupFrequencyWeekEnum
+import com.grkj.data.local.database.BackupScheduler
+import com.grkj.data.local.database.RoomBackupManager
+import com.grkj.data.utils.event.BackupCompleteEvent
+import com.grkj.data.utils.event.LoadingEvent
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentBackupAndRestoreBinding
+import com.grkj.iscs_mc.databinding.ItemBackupBinding
+import com.grkj.iscs_mc.features.main.dialog.TextDropDownDialog
+import com.grkj.iscs_mc.features.main.viewmodel.data_manage.BackupAndRestoreViewModel
+import com.grkj.shared.model.EventBean
+import com.grkj.shared.utils.FilePickerUtils
+import com.grkj.shared.utils.SAFHelper.copyFileToDir
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.dialog.WheelTimePickerDialog
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 备份/还原界面
+ */
+@AndroidEntryPoint
+class BackupAndRestoreFragment : BaseFragment<FragmentBackupAndRestoreBinding>() {
+    private val viewModel: BackupAndRestoreViewModel by viewModels()
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_backup_and_restore
+    }
+
+    /**
+     * 文件选择器
+     */
+    private lateinit var filePickerUtils: FilePickerUtils
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.maximumNumberOfBackups.setText("${viewModel.scheduleData.keep}")
+        binding.statusRg.setOnCheckedChangeListener(null)
+        binding.enableRb.isChecked = viewModel.scheduleData.enabled
+        binding.disableRb.isChecked = !viewModel.scheduleData.enabled
+        filePickerUtils = FilePickerUtils(this)
+        binding.statusRg.setOnCheckedChangeListener { _, checkedId ->
+            viewModel.scheduleData.enabled = checkedId == binding.enableRb.id
+        }
+        viewModel.selectedBackupFrequencyData =
+            BackupFrequencyWeekEnum.getSelectedDaysText(viewModel.scheduleData.daysMask)
+        binding.backupFrequency.text =
+            if (viewModel.selectedBackupFrequencyData.size == 7) I18nManager.t("backup_frequency_every_day")
+            else viewModel.selectedBackupFrequencyData.joinToString(",") { it.showText }
+        binding.backupFrequency.setDebouncedClickListener {
+            TextDropDownDialog.showMulti(
+                viewModel.backupFrequencyData,
+                binding.backupFrequency
+            ) { selectedData ->
+                viewModel.selectedBackupFrequencyData = BackupFrequencyWeekEnum.values()
+                    .filter {
+                        selectedData?.map { it.getShowText() }
+                            ?.contains(I18nManager.t(it.showText)) == true
+                    }
+                binding.backupFrequency.text =
+                    if (viewModel.selectedBackupFrequencyData.size == 7) I18nManager.t("backup_frequency_every_day")
+                    else viewModel.selectedBackupFrequencyData.joinToString(",") { I18nManager.t(it.showText) }
+            }
+        }
+        binding.save.setDebouncedClickListener {
+            if (checkSchedule()) {
+                viewModel.scheduleData.keep = binding.maximumNumberOfBackups.text.toString().toInt()
+                viewModel.selectedBackupFrequencyData.map {
+                    it.type
+                }.let {
+                    viewModel.scheduleData.daysMask =
+                        BackupFrequencyWeekEnum.getMaskFromTypes(it)
+                }
+                viewModel.setAndApply().observe(this) {
+                    showToast(I18nManager.t("save_success"))
+                }
+            }
+        }
+        binding.backupPath.text = RoomBackupManager.backupDir.absolutePath
+        binding.backupPath.isEnabled = false
+        binding.backupTime.text = "${viewModel.scheduleData.hour}:${viewModel.scheduleData.minute}"
+        binding.backupTime.setDebouncedClickListener {
+            val selectedTime = "${viewModel.scheduleData.hour}:${viewModel.scheduleData.minute}"
+            WheelTimePickerDialog.show(selectedTime) {
+                val split = it.split(":")
+                viewModel.scheduleData.hour = split[0].toInt()
+                viewModel.scheduleData.minute = split[1].toInt()
+                binding.backupTime.text = it
+            }
+        }
+        binding.backupNow.setDebouncedClickListener {
+            if (viewModel.backupItemDatas.size == viewModel.scheduleData.keep) {
+                TipDialog.showInfo(I18nManager.t("max_backup_tip"), onConfirmClick = {
+                    LoadingEvent.sendLoadingEvent(I18nManager.t("backup_now_please_wait"))
+                    BackupScheduler.backupNow(requireContext())
+                })
+            } else {
+                LoadingEvent.sendLoadingEvent(I18nManager.t("backup_now_please_wait"))
+                BackupScheduler.backupNow(requireContext())
+            }
+        }
+        binding.batchDelete.setDebouncedClickListener {
+            if (viewModel.backupItemDatas.none { it.isSelected }) {
+                showToast(I18nManager.t("please_select_backup_file"))
+                return@setDebouncedClickListener
+            }
+            TipDialog.showInfo(
+                I18nManager.t("delete_selected_backup_file_confirm"),
+                onConfirmClick = {
+                    viewModel.deleteBackupFiles(viewModel.backupItemDatas.filter { it.isSelected })
+                        .observe(this) {
+                            getData()
+                            showToast(I18nManager.t("delete_success"))
+                            viewModel.backupItemDatas.forEach {
+                                it.isSelected = false
+                            }
+                            checkAndSetSelectAllListener()
+                            binding.listRv.adapter?.notifyDataSetChanged()
+                        }
+                })
+        }
+        binding.batchExport.setDebouncedClickListener {
+            if (viewModel.backupItemDatas.none { it.isSelected }) {
+                showToast(I18nManager.t("please_select_backup_file"))
+                return@setDebouncedClickListener
+            }
+            TipDialog.showInfo(
+                I18nManager.t("export_selected_backup_file_confirm"),
+                onConfirmClick = {
+                    filePickerUtils.pickDirectory { treeUri ->
+                        viewModel.backupItemDatas.filter { it.isSelected }.forEach {
+                            treeUri?.let { treeUri ->
+                                requireContext().copyFileToDir(treeUri, it.file)
+                            }
+                        }
+                        viewModel.backupItemDatas.forEach {
+                            it.isSelected = false
+                        }
+                        checkAndSetSelectAllListener()
+                        binding.listRv.adapter?.notifyDataSetChanged()
+                        showToast(I18nManager.t("export_success"))
+                    }
+                })
+        }
+        binding.state.emptyLayout = com.grkj.ui_base.R.layout.layout_no_backup
+        binding.listRv.linear().setup {
+            addType<RoomBackupManager.BackupItem>(R.layout.item_backup)
+            onBind {
+                onRVListBinding(this)
+            }
+        }
+    }
+
+    /**
+     * 检查并设置是否全选
+     */
+    private fun checkAndSetSelectAllListener() {
+        binding.selectAll.setOnCheckedChangeListener(null)
+        binding.selectAll.isChecked =
+            viewModel.backupItemDatas.isNotEmpty() && viewModel.backupItemDatas.all { it.isSelected }
+        binding.selectAll.setOnCheckedChangeListener { v, isChecked ->
+            viewModel.backupItemDatas.forEach { it.isSelected = isChecked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    override fun onEvent(event: EventBean<Any>) {
+        super.onEvent(event)
+        when (event.code) {
+            EventConstants.EVENT_BACKUP_COMPLETE_CODE -> {
+                (event.data as BackupCompleteEvent).let {
+                    LoadingEvent.sendLoadingEvent()
+                    if (it.backupResult) {
+                        showToast(I18nManager.t("backup_success"))
+                    } else {
+                        showToast(I18nManager.t("backup_failed"))
+                    }
+                    getData()
+                }
+            }
+        }
+    }
+
+    /**
+     * 检查计划
+     */
+    private fun checkSchedule(): Boolean {
+        if (viewModel.selectedBackupFrequencyData.isEmpty()) {
+            showToast(I18nManager.t("please_select_backup_frequency"))
+            return false
+        }
+        val keep = binding.maximumNumberOfBackups.text.toString().toInt()
+        if (keep !in 5..20) {
+            showToast(I18nManager.t("maximumNumberOfBackupsNotCorrect"))
+            return false
+        }
+        return true
+    }
+
+    override fun initData() {
+        super.initData()
+        getData()
+    }
+
+    private fun getData() {
+        LoadingEvent.sendLoadingEvent(I18nManager.t("loading_backup"))
+        viewModel.getBackupList().observe(this) {
+            LoadingEvent.sendLoadingEvent()
+            if (it.isEmpty()) {
+                binding.state.showEmpty()
+            } else {
+                binding.state.showContent()
+            }
+            binding.listRv.models = it
+            checkAndSetSelectAllListener()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onRVListBinding(holder: BindingAdapter.BindingViewHolder) {
+        val item = getModel<RoomBackupManager.BackupItem>()
+        val itemBinding = getBinding<ItemBackupBinding>()
+        itemBinding.backupName.text = item.name
+        itemBinding.select.setOnCheckedChangeListener(null)
+        itemBinding.select.isChecked = item.isSelected
+        itemBinding.select.setOnCheckedChangeListener { v, isChecked ->
+            item.isSelected = isChecked
+            checkAndSetSelectAllListener()
+        }
+        itemBinding.delete.setDebouncedClickListener {
+            TipDialog.showInfo(I18nManager.t("delete_backup_file_confirm"), onConfirmClick = {
+                viewModel.deleteBackupFile(item).observe(this@BackupAndRestoreFragment) {
+                    showToast(I18nManager.t("delete_success"))
+                    getData()
+                }
+            })
+        }
+        itemBinding.export.setDebouncedClickListener {
+            TipDialog.showInfo(
+                I18nManager.t("export_selected_backup_file_confirm"),
+                onConfirmClick = {
+                    filePickerUtils.pickDirectory { treeUri ->
+                        treeUri?.let { treeUri ->
+                            requireContext().copyFileToDir(treeUri, item.file)
+                        }
+                        showToast(I18nManager.t("export_success"))
+                    }
+                })
+        }
+        itemBinding.restore.setDebouncedClickListener {
+            TipDialog.showInfo(I18nManager.t("restore_backup_confirm"), onConfirmClick = {
+                LoadingEvent.sendLoadingEvent(I18nManager.t("backup_restoring"))
+                viewModel.restoreBackUp(item).observe(this@BackupAndRestoreFragment) {
+                    LoadingEvent.sendLoadingEvent()
+                    showToast(I18nManager.t("restore_backup_success"))
+                }
+            })
+        }
+    }
+}

+ 122 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/DataExportFragment.kt

@@ -0,0 +1,122 @@
+package com.grkj.iscs_mc.features.main.fragment.data_manage
+
+import androidx.fragment.app.viewModels
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.domain.vo.DataExportVo
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentDataExportBinding
+import com.grkj.iscs_mc.databinding.ItemDataExportBinding
+import com.grkj.iscs_mc.features.main.viewmodel.data_manage.DataExportViewModel
+import com.grkj.shared.utils.FilePickerUtils
+import com.grkj.shared.utils.SAFHelper.copyFileToDir
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 数据导出
+ */
+@AndroidEntryPoint
+class DataExportFragment : BaseFragment<FragmentDataExportBinding>() {
+    private val viewModel: DataExportViewModel by viewModels()
+    private lateinit var filePickerUtils: FilePickerUtils
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_data_export
+    }
+
+    override fun initView() {
+        filePickerUtils = FilePickerUtils(this)
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.export.setDebouncedClickListener {
+            if (checkSelected()) {
+                exportData()
+            }
+        }
+        binding.listRv.linear().setup {
+            addType<DataExportVo>(R.layout.item_data_export)
+            onBind {
+                val item = getModel<DataExportVo>()
+                val itemBinding = getBinding<ItemDataExportBinding>()
+                itemBinding.select.setOnCheckedChangeListener(null)
+                itemBinding.select.isChecked = item.isSelected
+                itemBinding.select.setOnCheckedChangeListener { _, isChecked ->
+                    item.isSelected = isChecked
+                    checkSelectedAll()
+                }
+                itemBinding.tableName.text = item.tableName
+                itemBinding.lastExportTime.text = item.lastUpdateTime
+            }
+        }
+        checkSelectedAll()
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.getExportTableData().observe(this) {
+            binding.listRv.models = viewModel.dataExportTableData
+        }
+    }
+
+    /**
+     * 导出数据
+     */
+    private fun exportData() {
+        showLoading(CommonUtils.getStr("exporting"))
+        viewModel.exportData().observe(this) {
+            hideLoading()
+            if (it != null) {
+                TipDialog.showSuccess(
+                    CommonUtils.getStr("data_export_success_tip"),
+                    showCancel = false,
+                    onConfirmClick = {
+                        filePickerUtils.pickDirectory { treeUri ->
+                            treeUri?.let { treeUri ->
+                                requireContext().copyFileToDir(treeUri, it)
+                                it.delete()
+                                viewModel.updateExportTime()
+                                binding.listRv.adapter?.notifyDataSetChanged()
+                                showToast(CommonUtils.getStr("save_success"))
+                            }
+                        }
+                    }, onCancelClick = {
+                        it.delete()
+                    })
+            } else {
+                TipDialog.showError(CommonUtils.getStr("data_export_error"))
+            }
+
+        }
+    }
+
+    /**
+     * 检查是否选中
+     */
+    private fun checkSelected(): Boolean {
+        if (viewModel.dataExportTableData.none { it.isSelected }) {
+            showToast(CommonUtils.getStr("please_select_data_you_want_to_export"))
+            return false
+        }
+        return true
+    }
+
+    /**
+     * 检查是否全选
+     */
+    private fun checkSelectedAll() {
+        binding.selectAll.setOnCheckedChangeListener(null)
+        binding.selectAll.isSelected == viewModel.dataExportTableData.all { it.isSelected }
+        binding.selectAll.setOnCheckedChangeListener { _, isChecked ->
+            viewModel.dataExportTableData.forEach {
+                it.isSelected = isChecked
+            }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+}

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

@@ -0,0 +1,103 @@
+package com.grkj.iscs_mc.features.main.fragment.data_manage
+
+import androidx.annotation.OptIn
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.dividerSpace
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.google.android.flexbox.AlignItems
+import com.google.android.flexbox.FlexDirection
+import com.google.android.flexbox.FlexWrap
+import com.google.android.flexbox.FlexboxLayoutManager
+import com.google.android.material.badge.ExperimentalBadgeUtils
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentDataManageHomeBinding
+import com.grkj.iscs_mc.databinding.ItemHomeMenuBinding
+import com.grkj.iscs_mc.features.main.entity.MenuItemEntity
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.changeBgTint
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 数据管理首页
+ */
+@AndroidEntryPoint
+class DataManageHomeFragment : BaseFragment<FragmentDataManageHomeBinding>() {
+    private var menuData: MutableList<MenuItemEntity> = mutableListOf(
+        MenuItemEntity(
+            0,
+            "user.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.USER_MANAGE.description),
+            RoleFunctionalPermissionsEnum.USER_MANAGE.functionalPermission
+        ),
+        MenuItemEntity(
+            1,
+            "users-alt.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.ROLE_MANAGE.description),
+            RoleFunctionalPermissionsEnum.ROLE_MANAGE.functionalPermission
+        ),
+        MenuItemEntity(
+            4,
+            "back-up.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.BACKUP_AND_RESTORE.description),
+            RoleFunctionalPermissionsEnum.BACKUP_AND_RESTORE.functionalPermission
+        ),
+        MenuItemEntity(
+            5,
+            "icon_data_export.png",
+            I18nManager.t(RoleFunctionalPermissionsEnum.DATA_EXPORT.description),
+            RoleFunctionalPermissionsEnum.DATA_EXPORT.functionalPermission
+        ),
+    )
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_data_manage_home
+    }
+
+    override fun initView() {
+        binding.homeMenuRv.apply {
+            layoutManager = FlexboxLayoutManager(requireContext()).apply {
+                flexDirection = FlexDirection.ROW        // 横向布局
+                flexWrap = FlexWrap.WRAP
+                alignItems = AlignItems.STRETCH   // ✅ 不会被压扁
+            }
+            dividerSpace(40, DividerOrientation.GRID)
+        }.setup {
+            addType<MenuItemEntity>(R.layout.item_home_menu)
+            onBind {
+                onHomeMenuBinding(this)
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        menuData =
+            menuData.filter { MainDomainData.permissions.contains(it.permission) }.toMutableList()
+        binding.homeMenuRv.models = menuData
+    }
+
+    @OptIn(ExperimentalBadgeUtils::class)
+    private fun BindingAdapter.BindingViewHolder.onHomeMenuBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemHomeMenuBinding>()
+        val item = holder.getModel<MenuItemEntity>()
+        itemBinding.homeMenuIv.loadSkinIcon(item.menuIcon)
+        itemBinding.homeMenuTv.text = item.menuText
+        itemBinding.homeMenuLayout.changeBgTint(item.menuBgTint)
+        itemBinding.root.setDebouncedClickListener {
+            onMenuClick(item.type)
+        }
+    }
+
+    private fun onMenuClick(menuType: Int) {
+        when (menuType) {
+
+        }
+    }
+}

+ 301 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/RoleManageFragment.kt

@@ -0,0 +1,301 @@
+package com.grkj.iscs_mc.features.main.fragment.data_manage
+
+import android.graphics.Color
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.divider
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.enums.RoleEnum
+import com.grkj.data.model.vo.RoleManageVo
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentRoleManageBinding
+import com.grkj.iscs_mc.databinding.ItemRoleManageRoleBinding
+import com.grkj.iscs_mc.features.main.dialog.data_manage.AddRoleDialog
+import com.grkj.iscs_mc.features.main.dialog.data_manage.FilterRoleDialog
+import com.grkj.iscs_mc.features.main.dialog.data_manage.UpdateRoleDialog
+import com.grkj.iscs_mc.features.main.entity.UpdateRoleDataEntity
+import com.grkj.iscs_mc.features.main.viewmodel.data_manage.RoleManageViewModel
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 角色管理
+ */
+@AndroidEntryPoint
+class RoleManageFragment : BaseFragment<FragmentRoleManageBinding>() {
+    private val viewModel: RoleManageViewModel by viewModels()
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_role_manage
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.delete.setDebouncedClickListener {
+            deleteSelect()
+        }
+        binding.reset.setDebouncedClickListener {
+            viewModel.roleFilterData = null
+            getRoleData(false)
+        }
+        binding.add.setDebouncedClickListener {
+            AddRoleDialog.show { data, dialog ->
+                if (data.roleKeys in RoleEnum.values().map { it.roleKey }) {
+                    TipDialog.show(
+                        title = CommonUtils.getStr("action_failed"),
+                        dialogType = TipDialog.DialogType.ERROR,
+                        msg = CommonUtils.getStr("role_key_already_exists"),
+                        countDownTime = 10,
+                        showCancel = false,
+                        onConfirmClick = {
+                            getRoleData(nextPage = false)
+                        },
+                        onCancelClick = {
+                            getRoleData(nextPage = false)
+                        }
+                    )
+                    return@show
+                }
+                viewModel.validateRoleData(data.roleKeys).observe(this) {
+                    viewModel.addRoleData(data).observe(this) {
+                        dialog.dismiss()
+                        if (it) {
+                            TipDialog.show(
+                                title = CommonUtils.getStr("action_succeed"),
+                                dialogType = TipDialog.DialogType.SUCCESS,
+                                msg = CommonUtils.getStr("add_role_succeed"),
+                                countDownTime = 10,
+                                showCancel = false,
+                                onConfirmClick = {
+                                    getRoleData(nextPage = false)
+                                },
+                                onCancelClick = {
+                                    getRoleData(nextPage = false)
+                                }
+                            )
+                        } else {
+                            TipDialog.show(
+                                title = CommonUtils.getStr("action_failed"),
+                                dialogType = TipDialog.DialogType.ERROR,
+                                msg = CommonUtils.getStr("add_role_failed"),
+                                countDownTime = 10,
+                                showCancel = false,
+                                onConfirmClick = {
+                                    getRoleData(nextPage = false)
+                                },
+                                onCancelClick = {
+                                    getRoleData(nextPage = false)
+                                }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        binding.filter.setDebouncedClickListener {
+            FilterRoleDialog.show {
+                viewModel.roleFilterData = it
+                getRoleData(nextPage = false)
+            }
+        }
+        binding.refreshLayout.setOnRefreshListener {
+            viewModel.roleFilterData = null
+            getRoleData(nextPage = false)
+        }
+        binding.refreshLayout.setOnLoadMoreListener {
+            getRoleData()
+        }
+        binding.roleListRv.linear().divider {
+            this.setColor(Color.BLACK)
+            this.startVisible = false
+            this.endVisible = true
+            this.orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<RoleManageVo>(R.layout.item_role_manage_role)
+            onBind {
+                onRoleDataBinding(this)
+            }
+        }
+        setSelectAllListener()
+    }
+
+    private fun setSelectAllListener() {
+        binding.selectAll.setOnCheckedChangeListener { v, checked ->
+            viewModel.roleManageDataList.forEach { it.isSelected = checked }
+            binding.roleListRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onRoleDataBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemRoleManageRoleBinding>()
+        val item = holder.getModel<RoleManageVo>()
+        itemBinding.roleNum.text = item.roleId.toString()
+        val i18NRoleName = I18nManager.t(item.roleKey ?: "")
+        if (i18NRoleName == item.roleKey || i18NRoleName.isEmpty()) {
+            itemBinding.roleName.text = item.roleName
+        } else {
+            itemBinding.roleName.text = i18NRoleName
+        }
+        itemBinding.roleKeyTv.text = item.roleKey
+        itemBinding.select.setOnCheckedChangeListener(null)
+        itemBinding.select.isChecked = item.isSelected
+        itemBinding.select.setOnCheckedChangeListener { _, checked ->
+            item.isSelected = checked
+            binding.selectAll.setOnCheckedChangeListener(null)
+            binding.selectAll.isChecked = viewModel.roleManageDataList.all { it.isSelected }
+            setSelectAllListener()
+        }
+        itemBinding.root.setDebouncedClickListener {
+            if (item.roleKey == RoleEnum.ADMIN.roleKey) {
+                showToast(CommonUtils.getStr("admin_role_can_not_edit"))
+                return@setDebouncedClickListener
+            }
+            viewModel.getFunctionalPermissionsByRoleId(item.roleId)
+                .observe(this@RoleManageFragment) {
+                    UpdateRoleDialog.show(UpdateRoleDataEntity().apply {
+                        roleId = item.roleId
+                        roleName = item.roleName
+                        roleKeys = item.roleKey.toString()
+                        status = item.status == "0"
+                        functionalPermissions = it
+                        isPreset = item.roleKey in RoleEnum.values().map { it.roleKey }
+                    }) { data, dialog ->
+                        if (data.roleKeys in RoleEnum.values()
+                                .map { it.roleKey } && !data.isPreset
+                        ) {
+                            TipDialog.show(
+                                title = CommonUtils.getStr("action_failed")
+                                    .toString(),
+                                dialogType = TipDialog.DialogType.ERROR,
+                                msg = CommonUtils.getStr("role_key_already_exists")
+                                    .toString(),
+                                countDownTime = 10,
+                                showCancel = false,
+                                onConfirmClick = {
+                                    getRoleData(nextPage = false)
+                                },
+                                onCancelClick = {
+                                    getRoleData(nextPage = false)
+                                }
+                            )
+                            return@show
+                        }
+                        viewModel.updateRoleData(data).observe(this@RoleManageFragment) {
+                            dialog.dismiss()
+                            if (it) {
+                                TipDialog.show(
+                                    title = CommonUtils.getStr("action_succeed"),
+                                    dialogType = TipDialog.DialogType.SUCCESS,
+                                    msg = CommonUtils.getStr("update_role_succeed"),
+                                    countDownTime = 10,
+                                    showCancel = false,
+                                    onConfirmClick = {
+                                        getRoleData(nextPage = false)
+                                    },
+                                    onCancelClick = {
+                                        getRoleData(nextPage = false)
+                                    }
+                                )
+                            } else {
+                                TipDialog.show(
+                                    title = CommonUtils.getStr("action_failed"),
+                                    dialogType = TipDialog.DialogType.ERROR,
+                                    msg = CommonUtils.getStr("update_role_failed"),
+                                    countDownTime = 10,
+                                    showCancel = false,
+                                    onConfirmClick = {
+                                        getRoleData(nextPage = false)
+                                    },
+                                    onCancelClick = {
+                                        getRoleData(nextPage = false)
+                                    }
+                                )
+                            }
+                        }
+                    }
+                }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.roleFilterData = null
+        getRoleData(nextPage = false)
+    }
+
+    private fun getRoleData(nextPage: Boolean = true) {
+        if (!nextPage) {
+            showLoading()
+        }
+        viewModel.getRoleData(viewModel.roleFilterData, nextPage).observe(this) {
+            hideLoading()
+            if (!nextPage) {
+                viewModel.roleFilterData = null
+                binding.selectAll.setOnCheckedChangeListener(null)
+                binding.selectAll.isChecked = false
+                setSelectAllListener()
+            }
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+
+            if (viewModel.roleManageDataList.isEmpty()) {
+                binding.state.showEmpty()
+            } else {
+                binding.state.showContent()
+            }
+            binding.roleListRv.models = viewModel.roleManageDataList
+        }
+    }
+
+    private fun deleteSelect() {
+        if (viewModel.roleManageDataList.none { it.isSelected }) {
+            showToast(CommonUtils.getStr("please_select_role"))
+            return
+        }
+        if (viewModel.roleManageDataList.any {
+                it.isSelected && it.roleKey in RoleEnum.values().map { it.roleKey }
+            }) {
+            showToast(CommonUtils.getStr("role_in_preset_tip"))
+            return
+        }
+        viewModel.checkRoleInUse().observe(this) {
+            if (!it) {
+                TipDialog.show(
+                    msg = CommonUtils.getStr("check_delete_role"),
+                    countDownTime = 10,
+                    onConfirmClick = {
+                        viewModel.deleteSelectedRoles(viewModel.roleManageDataList.filter { it.isSelected }
+                            .map { it.roleId }).observe(this) {
+                            if (it) {
+                                TipDialog.show(
+                                    dialogType = TipDialog.DialogType.SUCCESS,
+                                    msg = CommonUtils.getStr("role_manage_delete_succeed"),
+                                    showCancel = false,
+                                    countDownTime = 10
+                                )
+                                getRoleData(false)
+                            } else {
+                                TipDialog.show(
+                                    dialogType = TipDialog.DialogType.ERROR,
+                                    msg = CommonUtils.getStr("role_manage_delete_failed"),
+                                    showCancel = false,
+                                    countDownTime = 10
+                                )
+                            }
+                        }
+                    })
+            } else {
+                TipDialog.showError(CommonUtils.getStr("role_in_use").toString())
+            }
+        }
+
+    }
+}

+ 249 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/data_manage/UserManageFragment.kt

@@ -0,0 +1,249 @@
+package com.grkj.iscs_mc.features.main.fragment.data_manage
+
+import android.graphics.Color
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.divider
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.model.vo.UserManageVo
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentUserManageBinding
+import com.grkj.iscs_mc.databinding.ItemUserManageUserBinding
+import com.grkj.iscs_mc.features.main.dialog.TextDropDownDialog
+import com.grkj.iscs_mc.features.main.dialog.data_manage.AddUserDialog
+import com.grkj.iscs_mc.features.main.dialog.data_manage.FilterUserDialog
+import com.grkj.iscs_mc.features.main.dialog.data_manage.UpdateUserDialog
+import com.grkj.iscs_mc.features.main.viewmodel.data_manage.UserManageViewModel
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 用户管理
+ */
+@AndroidEntryPoint
+class UserManageFragment : BaseFragment<FragmentUserManageBinding>() {
+    private val viewModel: UserManageViewModel by viewModels()
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_user_manage
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.deleteUser.setDebouncedClickListener {
+            deleteSelectUser()
+        }
+        binding.reset.setDebouncedClickListener {
+            viewModel.userFilterData = null
+            getUserData(false)
+        }
+        binding.addUser.setDebouncedClickListener {
+            viewModel.getRoleData().observe(this) {
+                AddUserDialog.show(viewModel.roleData.map {
+                    val i18NRoleName = I18nManager.t(it.roleKey ?: "")
+                    val roleName = if (i18NRoleName == it.roleKey || i18NRoleName.isEmpty()) {
+                        it.roleName
+                    } else {
+                        i18NRoleName
+                    }
+                    TextDropDownDialog.SimpleTextDropDownEntity(
+                        dataId = it.roleId,
+                        dataText = roleName
+                    )
+                }) { data, dialog ->
+                    viewModel.validateUserData(data.username).observe(this) {
+                        viewModel.addUser(data).observe(this) {
+                            dialog.dismiss()
+                            if (it) {
+                                TipDialog.show(
+                                    title = CommonUtils.getStr("action_succeed"),
+                                    dialogType = TipDialog.DialogType.SUCCESS,
+                                    msg = CommonUtils.getStr("add_user_succeed"),
+                                    countDownTime = 10,
+                                    showCancel = false,
+                                    onConfirmClick = {
+                                        getUserData(false)
+                                    },
+                                    onCancelClick = {
+                                        getUserData(false)
+                                    }
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        binding.filterUser.setDebouncedClickListener {
+            FilterUserDialog.show {
+                viewModel.userFilterData = it
+                getUserData(nextPage = false)
+            }
+        }
+        binding.refreshLayout.setOnRefreshListener {
+            viewModel.userFilterData = null
+            getUserData(nextPage = false)
+        }
+        binding.refreshLayout.setOnLoadMoreListener {
+            getUserData()
+        }
+        binding.listRv.linear().divider {
+            this.setColor(Color.BLACK)
+            this.startVisible = false
+            this.endVisible = true
+            this.orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<UserManageVo>(R.layout.item_user_manage_user)
+            onBind {
+                onUserDataBinding(this)
+            }
+        }
+        setSelectAllListener()
+    }
+
+    private fun setSelectAllListener() {
+        binding.selectAll.setOnCheckedChangeListener { v, checked ->
+            viewModel.userManageDataList.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onUserDataBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemUserManageUserBinding>()
+        val item = holder.getModel<UserManageVo>()
+        itemBinding.nickname.text = item.nickName
+        itemBinding.cardCode.text = item.cardCodes.joinToString(",")
+        itemBinding.role.text = item.roleNames.joinToString(",")
+        itemBinding.select.setOnCheckedChangeListener(null)
+        itemBinding.select.isChecked = item.isSelected
+        itemBinding.select.setOnCheckedChangeListener { _, checked ->
+            item.isSelected = checked
+            binding.selectAll.setOnCheckedChangeListener(null)
+            binding.selectAll.isChecked = viewModel.userManageDataList.all { it.isSelected }
+            setSelectAllListener()
+        }
+        itemBinding.root.setDebouncedClickListener {
+            viewModel.getRoleData().observe(this@UserManageFragment) {
+                UpdateUserDialog.show(item, viewModel.roleData.map {
+                    val i18NRoleName = I18nManager.t(it.roleKey ?: "")
+                    val roleName = if (i18NRoleName == it.roleKey || i18NRoleName.isEmpty()) {
+                        it.roleName
+                    } else {
+                        i18NRoleName
+                    }
+                    TextDropDownDialog.SimpleTextDropDownEntity(
+                        dataId = it.roleId,
+                        dataText = roleName
+                    )
+                }) { data, dialog ->
+                    viewModel.updateUser(data).observe(this@UserManageFragment) {
+                        dialog.dismiss()
+                        if (it) {
+                            TipDialog.show(
+                                title = CommonUtils.getStr("action_succeed"),
+                                dialogType = TipDialog.DialogType.SUCCESS,
+                                msg = CommonUtils.getStr("update_user_succeed"),
+                                countDownTime = 10,
+                                showCancel = false,
+                                onConfirmClick = {
+                                    getUserData(false)
+                                },
+                                onCancelClick = {
+                                    getUserData(false)
+                                }
+                            )
+                        } else {
+                            TipDialog.show(
+                                title = CommonUtils.getStr("action_failed"),
+                                dialogType = TipDialog.DialogType.ERROR,
+                                msg = CommonUtils.getStr("update_user_failed"),
+                                countDownTime = 10,
+                                showCancel = false,
+                                onConfirmClick = {
+                                    getUserData(false)
+                                },
+                                onCancelClick = {
+                                    getUserData(false)
+                                }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.userFilterData = null
+        getUserData(nextPage = false)
+    }
+
+    private fun getUserData(nextPage: Boolean = true) {
+        if (!nextPage) {
+            showLoading()
+        }
+        viewModel.getUserData(viewModel.userFilterData, nextPage).observe(this) {
+            hideLoading()
+            if (!nextPage) {
+                viewModel.userFilterData = null
+                binding.selectAll.setOnCheckedChangeListener(null)
+                binding.selectAll.isChecked = false
+                setSelectAllListener()
+            }
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+            if (viewModel.userManageDataList.isEmpty()) {
+                binding.state.showEmpty()
+            } else {
+                binding.state.showContent()
+            }
+            binding.listRv.models = viewModel.userManageDataList
+        }
+    }
+
+    private fun deleteSelectUser() {
+        if (viewModel.userManageDataList.none { it.isSelected }) {
+            showToast(CommonUtils.getStr("please_select_user"))
+            return
+        }
+        viewModel.userInProgressJob().observe(this) {
+            if (!it) {
+                TipDialog.show(
+                    msg = CommonUtils.getStr("check_delete_user"),
+                    countDownTime = 10,
+                    onConfirmClick = {
+                        viewModel.deleteSelectedUsers(viewModel.userManageDataList.filter { it.isSelected }
+                            .map { it.userId }).observe(this) {
+                            if (it) {
+                                TipDialog.show(
+                                    dialogType = TipDialog.DialogType.SUCCESS,
+                                    msg = CommonUtils.getStr("user_manage_delete_succeed"),
+                                    showCancel = false,
+                                    countDownTime = 10
+                                )
+                                getUserData(false)
+                            } else {
+                                TipDialog.show(
+                                    dialogType = TipDialog.DialogType.ERROR,
+                                    msg = CommonUtils.getStr("user_manage_delete_failed"),
+                                    showCancel = false,
+                                    countDownTime = 10
+                                )
+                            }
+                        }
+                    })
+            } else {
+                TipDialog.showError(CommonUtils.getStr("has_user_in_progress_job"))
+            }
+        }
+    }
+}

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

@@ -0,0 +1,125 @@
+package com.grkj.iscs_mc.features.main.fragment.home
+
+import android.widget.LinearLayout
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentHomeBinding
+import com.grkj.iscs_mc.databinding.ItemHomeQuickEntranceBinding
+import com.grkj.iscs_mc.features.main.dialog.QuickEntranceConfigDialog
+import com.grkj.iscs_mc.features.main.entity.QuickEntranceMenuItemEntity
+import com.grkj.iscs_mc.features.main.viewmodel.home.HomeViewModel
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.event.JumpViewEvent
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 首页
+ */
+@AndroidEntryPoint
+class HomeFragment : BaseFragment<FragmentHomeBinding>() {
+    private val viewModel: HomeViewModel by viewModels()
+    private val quickEntranceList: MutableList<QuickEntranceMenuItemEntity>
+        get() {
+            val quickEntryConfigJson = MainDomainData.userInfo?.quickEntranceConfig ?: ""
+            val permissions = quickEntryConfigJson.split(",").filter { it.isNotBlank() }
+                .mapNotNull { runCatching { RoleFunctionalPermissionsEnum.valueOf(it) }.getOrNull() }
+            return if (permissions.isEmpty()) {
+                mutableListOf<QuickEntranceMenuItemEntity>(
+                    QuickEntranceMenuItemEntity(
+                        0,
+                        "document.svg",
+                        I18nManager.t(RoleFunctionalPermissionsEnum.EXCHANGE_RECORD.description),
+                        RoleFunctionalPermissionsEnum.EXCHANGE_RECORD,
+                        QuickEntranceMenuItemEntity.getNavGraphId(RoleFunctionalPermissionsEnum.EXCHANGE_RECORD),
+                        QuickEntranceMenuItemEntity.getDestId(RoleFunctionalPermissionsEnum.EXCHANGE_RECORD)
+                    )
+                )
+            } else {
+                permissions.mapIndexed { index, value ->
+                    QuickEntranceMenuItemEntity(
+                        index,
+                        QuickEntranceMenuItemEntity.getMenuIcon(value),
+                        I18nManager.t(value.description),
+                        value,
+                        QuickEntranceMenuItemEntity.getNavGraphId(value),
+                        QuickEntranceMenuItemEntity.getDestId(value)
+                    )
+                }.toMutableList()
+            }
+        }
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_home
+    }
+
+    override fun initView() {
+        binding.quickEntranceRv.apply {
+            linear(orientation = LinearLayout.HORIZONTAL)
+        }.setup {
+            addType<QuickEntranceMenuItemEntity>(R.layout.item_home_quick_entrance)
+            onBind {
+                onQuickEntranceBinding(this)
+            }
+        }
+        binding.quickEntranceConfig.setDebouncedClickListener {
+            quickEntranceConfig()
+        }
+    }
+
+    /**
+     * 快捷入口配置
+     */
+    private fun quickEntranceConfig() {
+        QuickEntranceConfigDialog.show {
+            MainDomainData.userInfo?.quickEntranceConfig = it
+            binding.quickEntranceRv.models = quickEntranceList
+            viewModel.saveQuickEntranceData().observe(this) {}
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        binding.quickEntranceRv.models = quickEntranceList
+        getHomeData()
+    }
+
+    private fun getHomeData() {
+        viewModel.getHomeData().observe(this) {
+            binding.totalMaterialNum.text = viewModel.allMaterial.toString()
+            binding.borrowMaterialNum.text = viewModel.borrowMaterial.toString()
+            binding.unborrowedMaterialNum.text = viewModel.unborrowedMaterial.toString()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onQuickEntranceBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemHomeQuickEntranceBinding>()
+        val item = holder.getModel<QuickEntranceMenuItemEntity>()
+        itemBinding.homeMenuIv.loadSkinIcon(item.menuIcon)
+        itemBinding.homeMenuTv.text = item.menuText
+        if (item.badgeNum == 0) {
+            itemBinding.quickEntranceLayout.hiddenBadge()
+        } else {
+            itemBinding.quickEntranceLayout.showTextBadge(item.badgeNum.toString())
+        }
+        itemBinding.root.setDebouncedClickListener {
+            onMenuClick(item)
+        }
+    }
+
+    private fun onMenuClick(menu: QuickEntranceMenuItemEntity) {
+        JumpViewEvent.sendJumpViewEvent(
+            menu.navGraph,
+            menu.destId,
+            true
+        )
+    }
+}

+ 98 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/ResetPasswordFragment.kt

@@ -0,0 +1,98 @@
+package com.grkj.iscs_mc.features.main.fragment.user_info
+
+import androidx.fragment.app.viewModels
+import com.grkj.data.common.CommonConstants
+import com.grkj.data.common.MainDomainData
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentResetPasswordBinding
+import com.grkj.iscs_mc.features.main.viewmodel.user_info.UserInfoViewModel
+import com.grkj.shared.utils.BCryptUtils
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.event.LogoutEvent
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.string.RegexUtils
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 重置密码
+ */
+@AndroidEntryPoint
+class ResetPasswordFragment : BaseFragment<FragmentResetPasswordBinding>() {
+    private val viewModel: UserInfoViewModel by viewModels()
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_reset_password
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.cancel.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.confirm.setDebouncedClickListener {
+            if (checkData()) {
+                viewModel.updatePassword(
+                    binding.newPasswordEt.text.toString()
+                ).observe(this) {
+                    if (it) {
+                        TipDialog.showSuccess(
+                            CommonUtils.getStr("reset_user_password_succeed").toString(),
+                            onConfirmClick = {
+                                LogoutEvent.sendLogoutEvent()
+                            }, onCancelClick = {
+                                LogoutEvent.sendLogoutEvent()
+                            })
+                    } else {
+                        TipDialog.showError(
+                            CommonUtils.getStr("reset_user_password_failed").toString()
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (!BCryptUtils.matchPassword(
+                binding.oldPasswordEt.text.toString(),
+                MainDomainData.userInfo?.password ?: ""
+            )
+        ) {
+            showToast(CommonUtils.getStr("old_password_error"))
+            return false
+        }
+        if (binding.oldPasswordEt.text.toString().isEmpty()) {
+            showToast(CommonUtils.getStr("please_input_old_password"))
+            return false
+        }
+        if (binding.newPasswordEt.text.toString().isEmpty()) {
+            showToast(CommonUtils.getStr("please_input_new_password"))
+            return false
+        }
+        if (!RegexUtils.isMatch(
+                binding.newPasswordEt.text.toString(),
+                CommonConstants.REGEX_PASSWORD
+            )
+        ) {
+            showToast(CommonUtils.getStr("password_regex_tip"))
+            return false
+        }
+        if (binding.repeatNewPasswordEt.text.toString().isEmpty()) {
+            showToast(CommonUtils.getStr("please_input_repeat_new_password"))
+            return false
+        }
+        if (binding.newPasswordEt.text.toString() != binding.repeatNewPasswordEt.text.toString()) {
+            showToast(CommonUtils.getStr("new_password_and_repeat_new_password_not_same"))
+            return false
+        }
+        if (binding.newPasswordEt.text.toString() == binding.oldPasswordEt.text.toString()) {
+            showToast(CommonUtils.getStr("new_password_cannot_be_the_same_as_the_old_password"))
+            return false
+        }
+        return true
+    }
+}

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

@@ -0,0 +1,199 @@
+package com.grkj.iscs_mc.features.main.fragment.user_info
+
+import android.graphics.Bitmap
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.fragment.app.viewModels
+import coil.load
+import com.grkj.data.common.CommonConstants
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.utils.FileStorageUtils
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentSetFaceBinding
+import com.grkj.iscs_mc.features.main.viewmodel.user_info.UserInfoViewModel
+import com.grkj.shared.utils.ArcSoftUtil
+import com.grkj.shared.utils.CancellableTimer
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.removeTint
+import com.sik.sikcore.date.TimeUtils
+import com.sik.sikcore.extension.file
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.extension.toJson
+import com.sik.sikimage.ImageConvertUtils
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 设置人脸
+ */
+@AndroidEntryPoint
+class SetFaceFragment : BaseFragment<FragmentSetFaceBinding>() {
+    private val viewModel: UserInfoViewModel by viewModels()
+    private var mCapturedBitmap: Bitmap? = null
+    private var isFaceDetect: Boolean = false
+    private var isInCountDown: Boolean = false
+    private val captureTimer = CancellableTimer(4000, 1000, {
+        binding.countDownTip.text = "${(3000 - it) / 1000}"
+    }) {
+        ArcSoftUtil.inDetecting = true
+        isFaceDetect = true
+        binding.previewLayout.visibility = View.INVISIBLE
+        binding.image.visibility = View.VISIBLE
+        binding.recapture.visibility = View.VISIBLE
+        binding.confirm.visibility = View.VISIBLE
+        binding.countDownTip.text = CommonUtils.getStr("detect_face_tip")
+        binding.countDownTip.isVisible = false
+    }
+    private val reCaptureTimer = CancellableTimer(2000, 1000, {}) {
+        isFaceDetect = false
+        ArcSoftUtil.inDetecting = false
+        isInCountDown = false
+    }
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_set_face
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            releaseFace()
+            navController.popBackStack()
+        }
+        binding.setOrResetFace.setDebouncedClickListener {
+            binding.faceViewLayout.isVisible = false
+            binding.faceSetLayout.isVisible = true
+            binding.image.isVisible = false
+            binding.previewLayout.isVisible = true
+            binding.confirm.isVisible = false
+            binding.recapture.isVisible = false
+            startFace()
+        }
+        binding.cancel.setDebouncedClickListener {
+            releaseFace()
+            binding.faceViewLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            binding.image.isVisible = false
+            binding.previewLayout.isVisible = true
+            binding.confirm.isVisible = false
+            binding.recapture.isVisible = false
+        }
+        binding.confirm.setDebouncedClickListener {
+            releaseFace()
+            binding.faceViewLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            binding.confirm.isVisible = false
+            binding.recapture.isVisible = false
+            val saveFileName =
+                "${MainDomainData.userInfo?.userId}_face_${TimeUtils.nowString("yyyyMMddHHmmss")}"
+            val imageData =
+                ImageConvertUtils.bitmapToBase64(mCapturedBitmap).toString()
+            FileStorageUtils.writeText(
+                CommonConstants.FACE_FOLDER,
+                saveFileName,
+                imageData
+            )
+            val savePath = FileStorageUtils.getFilePath(
+                CommonConstants.FACE_FOLDER,
+                saveFileName
+            )
+            viewModel.registerFaceFeature(imageData).observe(this) {}
+            viewModel.saveUserFace(savePath).observe(this) {
+                getData()
+            }
+        }
+        binding.recapture.setDebouncedClickListener {
+            reCaptureTimer.start()
+            binding.countDownTip.isVisible = false
+            binding.image.isVisible = false
+            binding.previewLayout.isVisible = true
+            binding.confirm.isVisible = false
+            binding.recapture.isVisible = false
+            binding.faceOverlayView.setFaceRect(null)
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getData()
+    }
+
+    private fun getData() {
+        viewModel.getFaceData().observe(this) {
+            binding.faceViewLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            binding.faceNotSetIv.isVisible = viewModel.faceData.isEmpty()
+            binding.faceSetIv.isVisible = viewModel.faceData.isNotEmpty()
+            if (viewModel.faceData.isEmpty()) {
+                binding.setOrResetFace.text = CommonUtils.getStr("set_data_tv")
+                binding.faceSetTipTv.text = CommonUtils.getStr("face_not_set_tip")
+            } else {
+                logger.info("人脸图片地址:${viewModel.faceData.toJson()}")
+                val faceFile = viewModel.faceData[0].content.file()
+                if (faceFile.exists()) {
+                    val faceBase64 = faceFile.readText()
+                    val faceBitmap = ImageConvertUtils.base64ToBitmap(faceBase64)
+                    binding.faceSetIv.removeTint()
+                    binding.faceSetIv.load(faceBitmap)
+                }
+                binding.setOrResetFace.text = CommonUtils.getStr("reset_data_tv")
+                binding.faceSetTipTv.text = CommonUtils.getStr("face_set_tip")
+            }
+        }
+    }
+
+    private fun startFace() {
+        binding.previewLayout.isVisible = true
+        binding.image.isVisible = false
+        ArcSoftUtil.inDetecting = false
+        ArcSoftUtil.initCamera(
+            requireContext(),
+            requireActivity().windowManager,
+            binding.preview,
+            binding.faceOverlayView,
+            true,
+        ) { bitmap, faceSize, alive ->
+            binding.tipTv.isVisible = faceSize > 1 || alive == false
+            logger.info("人脸检测结果: ${bitmap == null},$faceSize,$alive")
+            if (faceSize > 1) {
+                binding.tipTv.text = CommonUtils.getStr("only_one_person_allowed")
+                ArcSoftUtil.inDetecting = false
+                stopCountDown()
+                return@initCamera
+            }
+            if (alive == false) {
+                binding.tipTv.text =
+                    CommonUtils.getStr("real_person_verification_required")
+                ArcSoftUtil.inDetecting = false
+                stopCountDown()
+                return@initCamera
+            }
+            if (!isInCountDown) {
+                startCountDown()
+            }
+            if (!isFaceDetect) {
+                mCapturedBitmap = bitmap
+                binding.image.setImageBitmap(bitmap)
+            }
+            ArcSoftUtil.inDetecting = false
+        }
+    }
+
+    private fun startCountDown() {
+        isInCountDown = true
+        binding.countDownTip.text = CommonUtils.getStr("detect_face_tip")
+        binding.countDownTip.isVisible = true
+        captureTimer.start()
+    }
+
+    private fun stopCountDown() {
+        isInCountDown = false
+        binding.countDownTip.text = CommonUtils.getStr("detect_face_tip")
+        binding.countDownTip.isVisible = false
+        captureTimer.cancel()
+    }
+
+    private fun releaseFace() {
+        ArcSoftUtil.stop()
+    }
+
+}

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

@@ -0,0 +1,235 @@
+package com.grkj.iscs_mc.features.main.fragment.user_info
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.widget.TextView
+import androidx.fragment.app.viewModels
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.divider
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.common.CommonConstants
+import com.grkj.data.common.MMKVConstants
+import com.grkj.data.domain.vo.FingerprintDataVo
+import com.grkj.data.hardware.fingerprint.FingerprintUtil
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentSetFingerprintBinding
+import com.grkj.iscs_mc.databinding.ItemSetFingerprintBinding
+import com.grkj.iscs_mc.features.main.dialog.user_info.AddFingerprintDialog
+import com.grkj.iscs_mc.features.main.viewmodel.user_info.UserInfoViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.sik.sikcore.extension.deleteIfExists
+import com.sik.sikcore.extension.getMMKVData
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikimage.ImageConvertUtils
+import dagger.hilt.android.AndroidEntryPoint
+import java.util.UUID
+
+/**
+ * 设置指纹
+ */
+@AndroidEntryPoint
+class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
+    private val viewModel: UserInfoViewModel by viewModels()
+    private var fingerprintTempData = mutableListOf<ByteArray>()
+
+    private var mFingerprintLimit: Int = 5
+    private var mFingerprintPressTimes: Int = 0
+    private var mFingerprintInputErrorTimes: Int = 0
+    private var mFingerprintGroupName: String = ""
+    private val maxPressTimes = 3
+    private val inputFingerprintErrorTimes = 3
+    private var pressTip: TextView? = null
+    private val inputFingerprintIds: MutableList<Long> = mutableListOf()
+
+    /**
+     * 最大指纹录入数
+     */
+    private val maxFingerprintInsertSize =
+        MMKVConstants.KEY_MAX_FINGERPRINT_INSERT.getMMKVData(CommonConstants.DEFAULT_MAX_FINGERPRINT_INSERT_SIZE)
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_set_fingerprint
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.maxFingerprintInsertTip.text = CommonUtils.getStr(
+            "max_fingerprint_insert_tip",
+            maxFingerprintInsertSize
+        )
+        binding.add.setDebouncedClickListener {
+            if (viewModel.fingerprintData.size >= maxFingerprintInsertSize) {
+                showToast(CommonUtils.getStr("fingerprint_limit_tip"))
+                return@setDebouncedClickListener
+            }
+            mFingerprintPressTimes = 0
+            mFingerprintInputErrorTimes = 0
+            fingerprintTempData.clear()
+            AddFingerprintDialog.show({
+                FingerprintUtil.stop()
+                it.dismiss()
+            }) {
+                pressTip = it
+                it.text = CommonUtils.getStr(
+                    "fingerprint_scan_tip",
+                    maxPressTimes - mFingerprintPressTimes
+                )
+            }.apply {
+                startCaptureFingerprint(this)
+            }
+        }
+        binding.delete.setDebouncedClickListener {
+            TipDialog.showInfo(
+                CommonUtils.getStr(
+                    "fingerprint_delete_selected_confirm_tip"
+                ).toString(),
+                countDownTime = 10,
+                onConfirmClick = {
+                    viewModel.deleteFingerprintByIds(viewModel.fingerprintData.filter { it.isSelected }
+                        .flatMap { it.fingerprintData }
+                        .map { it.recordId })
+                        .observe(this) {
+                            if (it) {
+                                TipDialog.showSuccess(
+                                    CommonUtils.getStr("delete_success"),
+                                    onConfirmClick = {
+                                        getData()
+                                    })
+                            }
+                        }
+                }
+            )
+        }
+        binding.listRv.linear().divider {
+            this.setColor(Color.BLACK)
+            this.startVisible = false
+            this.endVisible = true
+            this.orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<FingerprintDataVo>(R.layout.item_set_fingerprint)
+            onBind {
+                val itemBinding = getBinding<ItemSetFingerprintBinding>()
+                val item = getModel<FingerprintDataVo>()
+                itemBinding.fingerprintCode.text =
+                    CommonUtils.getStr("fingerprint_code_str", item.group?.take(6))
+                itemBinding.delete.setDebouncedClickListener {
+                    TipDialog.showInfo(
+                        CommonUtils.getStr(
+                            "fingerprint_delete_confirm_tip",
+                            CommonUtils.getStr("fingerprint_code_str", item.group?.take(6))
+                        ).toString(),
+                        countDownTime = 10,
+                        onConfirmClick = {
+                            viewModel.deleteFingerprintByIds(item.fingerprintData.map { it.recordId })
+                                .observe(this@SetFingerprintFragment) {
+                                    if (it) {
+                                        TipDialog.showSuccess(CommonUtils.getStr("delete_success"))
+                                        getData()
+                                    }
+                                }
+                        }
+                    )
+                }
+                itemBinding.select.setOnCheckedChangeListener(null)
+                itemBinding.select.isChecked = item.isSelected
+                itemBinding.select.setOnCheckedChangeListener { _, checked ->
+                    item.isSelected = checked
+                    binding.selectAll.setOnCheckedChangeListener(null)
+                    binding.selectAll.isChecked = viewModel.fingerprintData.all { it.isSelected }
+                    setSelectAllListener()
+                }
+            }
+        }
+        setSelectAllListener()
+    }
+
+    private fun startCaptureFingerprint(dialog: CustomDialog) {
+        FingerprintUtil.init(requireContext())
+        FingerprintUtil.start()
+        mFingerprintGroupName = UUID.randomUUID().toString()
+        FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {
+            override fun onScan(bitmap: Bitmap) {
+                viewModel.saveUserFingerprint(
+                    ImageConvertUtils.bitmapToBase64(bitmap) ?: "",
+                    mFingerprintGroupName
+                ).observe(this@SetFingerprintFragment) {
+                    if (it != null) {
+                        logger.info("添加指纹:${it}")
+                        inputFingerprintIds.add(it)
+                        mFingerprintPressTimes++
+                        if (mFingerprintPressTimes == maxPressTimes) {
+                            dialog?.dismiss()
+                            showToast(CommonUtils.getStr("fingerprint_add_success_tip"))
+                            getData()
+                        } else if (mFingerprintInputErrorTimes == inputFingerprintErrorTimes) {
+                            mFingerprintGroupName = UUID.randomUUID().toString()
+                            mFingerprintPressTimes = 0
+                            mFingerprintInputErrorTimes = 0
+                            pressTip?.text = CommonUtils.getStr(
+                                "fingerprint_scan_tip",
+                                maxPressTimes - mFingerprintPressTimes
+                            )
+                            viewModel.deleteFingerprintByIds(inputFingerprintIds)
+                                .observe(this@SetFingerprintFragment) {
+                                    getData()
+                                }
+                            showToast(CommonUtils.getStr("please_re_press_fingerprint_again"))
+                        } else {
+                            pressTip?.text = CommonUtils.getStr(
+                                "fingerprint_scan_tip",
+                                maxPressTimes - mFingerprintPressTimes
+                            )
+                            showToast(CommonUtils.getStr("please_press_fingerprint_again"))
+                        }
+                    } else {
+                        mFingerprintInputErrorTimes++
+                        if (mFingerprintInputErrorTimes == inputFingerprintErrorTimes) {
+                            mFingerprintGroupName = UUID.randomUUID().toString()
+                            mFingerprintPressTimes = 0
+                            mFingerprintInputErrorTimes = 0
+                            pressTip?.text = CommonUtils.getStr(
+                                "fingerprint_scan_tip",
+                                maxPressTimes - mFingerprintPressTimes
+                            )
+                            viewModel.deleteFingerprintByIds(inputFingerprintIds)
+                                .observe(this@SetFingerprintFragment) {
+                                    getData()
+                                }
+                            showToast(CommonUtils.getStr("please_re_press_fingerprint_again"))
+                        }
+                    }
+                }
+            }
+        })
+    }
+
+    private fun setSelectAllListener() {
+        binding.selectAll.setOnCheckedChangeListener { v, checked ->
+            viewModel.fingerprintData.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getData()
+    }
+
+    private fun getData() {
+        viewModel.getFingerprintData().observe(this) {
+            if (viewModel.fingerprintData.isEmpty()) {
+                binding.state.showEmpty()
+            } else {
+                binding.state.showContent()
+            }
+            binding.listRv.models = viewModel.fingerprintData
+        }
+    }
+}

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

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

+ 99 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/SettingsFragment.kt

@@ -0,0 +1,99 @@
+package com.grkj.iscs_mc.features.main.fragment.user_info
+
+import com.grkj.data.common.CommonConstants
+import com.grkj.data.common.MMKVConstants
+import com.grkj.data.enums.HardwareMode
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentSettingsBinding
+import com.grkj.iscs_mc.features.main.dialog.TextDropDownDialog
+import com.grkj.shared.utils.CountdownTimer
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.data.utils.event.RestartAppEvent
+import com.sik.sikcore.extension.getMMKVData
+import com.sik.sikcore.extension.saveMMKVData
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 设置界面
+ */
+@AndroidEntryPoint
+class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
+    /**
+     * 硬件模式修改
+     */
+    private var hardwareModeChanged: Boolean = false
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_settings
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.maxFingerprintInsert.setText(
+            "${
+                MMKVConstants.KEY_MAX_FINGERPRINT_INSERT.getMMKVData(
+                    CommonConstants.DEFAULT_MAX_FINGERPRINT_INSERT_SIZE
+                )
+            }"
+        )
+        binding.autoLogoutTime.setText(
+            "${
+                MMKVConstants.KEY_AUTO_LOGOUT_TIME.getMMKVData(
+                    CommonConstants.DEFAULT_AUTO_LOGOUT_TIME
+                ) / 1000
+            }"
+        )
+        binding.hardwareMode.text =
+            MMKVConstants.KEY_HARDWARE_MODE.getMMKVData(HardwareMode.CAN.name)
+        binding.hardwareMode.setDebouncedClickListener {
+            val hardwareModeData = HardwareMode.values()
+                .map { TextDropDownDialog.SimpleTextDropDownEntity(dataText = it.name) }
+            TextDropDownDialog.showSingle(hardwareModeData, binding.hardwareMode) {
+                binding.hardwareMode.text = it.getShowText()
+                MMKVConstants.KEY_HARDWARE_MODE.saveMMKVData(it.getShowText())
+                hardwareModeChanged = true
+            }
+        }
+        binding.confirm.setDebouncedClickListener {
+            if (checkData()) {
+                MMKVConstants.KEY_MAX_FINGERPRINT_INSERT.saveMMKVData(
+                    binding.maxFingerprintInsert.text.toString().toInt()
+                )
+                val autoLogoutTime =
+                    binding.autoLogoutTime.text.toString().toLong() * 1000
+                MMKVConstants.KEY_AUTO_LOGOUT_TIME.saveMMKVData(
+                    autoLogoutTime
+                )
+                CountdownTimer.reset(autoLogoutTime)
+                if (hardwareModeChanged) {
+                    showToast(CommonUtils.getStr("save_success"))
+                    RestartAppEvent.sendRestartAppEvent()
+                } else {
+                    showToast(CommonUtils.getStr("save_success"))
+                }
+            }
+        }
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (binding.maxFingerprintInsert.text.toString().isEmpty()) {
+            showToast(CommonUtils.getStr("please_input_max_fingerprint_entries_size"))
+            return false
+        }
+        if (binding.autoLogoutTime.text.toString().isEmpty()) {
+            showToast(CommonUtils.getStr("please_input_auto_logout_time"))
+            return false
+        }
+        if (binding.autoLogoutTime.text.toString().toLong() !in 60..1800) {
+            showToast(CommonUtils.getStr("please_input_auto_logout_time_correct"))
+            return false
+        }
+        return true
+    }
+}

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

@@ -0,0 +1,235 @@
+package com.grkj.iscs_mc.features.main.fragment.user_info
+
+import android.graphics.Bitmap
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.fragment.app.viewModels
+import coil.load
+import coil.transform.CircleCropTransformation
+import com.grkj.data.common.CommonConstants
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.utils.FileStorageUtils
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentUserInfoBinding
+import com.grkj.iscs_mc.features.main.viewmodel.user_info.UserInfoViewModel
+import com.grkj.shared.utils.ArcSoftUtil
+import com.grkj.shared.utils.CancellableTimer
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.event.LogoutEvent
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.date.TimeUtils
+import com.sik.sikcore.extension.file
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.string.RegexUtils
+import com.sik.sikimage.ImageConvertUtils
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 个人信息
+ */
+@AndroidEntryPoint
+class UserInfoFragment : BaseFragment<FragmentUserInfoBinding>() {
+    private val viewModel: UserInfoViewModel by viewModels()
+    private var mCapturedBitmap: Bitmap? = null
+    private var isFaceDetect: Boolean = false
+    private var isInCountDown: Boolean = false
+    private val captureTimer = CancellableTimer(4000, 1000, {
+        binding.countDownTip.text = "${(3000 - it) / 1000}"
+    }) {
+        ArcSoftUtil.inDetecting = true
+        isFaceDetect = true
+        binding.previewLayout.visibility = View.INVISIBLE
+        binding.image.visibility = View.VISIBLE
+        binding.recapture.visibility = View.VISIBLE
+        binding.setAvatarConfirm.visibility = View.VISIBLE
+        binding.countDownTip.text = CommonUtils.getStr("detect_face_tip")
+        binding.countDownTip.isVisible = false
+    }
+    private val reCaptureTimer = CancellableTimer(2000, 1000, {}) {
+        isFaceDetect = false
+        ArcSoftUtil.inDetecting = false
+        isInCountDown = false
+    }
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_user_info
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            releaseFace()
+            navController.popBackStack()
+        }
+        binding.cancel.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.avatar.setDebouncedClickListener {
+            binding.showUserInfoLayout.isVisible = false
+            binding.faceSetLayout.isVisible = true
+            binding.image.isVisible = false
+            binding.previewLayout.isVisible = true
+            binding.setAvatarConfirm.isVisible = false
+            binding.recapture.isVisible = false
+            startFace()
+        }
+        binding.setAvatarCancel.setDebouncedClickListener {
+            releaseFace()
+            binding.showUserInfoLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            binding.image.isVisible = false
+            binding.previewLayout.isVisible = true
+            binding.setAvatarConfirm.isVisible = false
+            binding.recapture.isVisible = false
+        }
+        binding.deleteAvatar.setDebouncedClickListener {
+            viewModel.saveUserAvatar(null).observe(this) {
+                binding.avatar.setImageResource(com.grkj.ui_base.R.drawable.icon_avatar)
+                getData()
+            }
+        }
+        binding.setAvatarConfirm.setDebouncedClickListener {
+            releaseFace()
+            binding.showUserInfoLayout.isVisible = true
+            binding.faceSetLayout.isVisible = false
+            binding.setAvatarConfirm.isVisible = false
+            binding.recapture.isVisible = false
+            val saveFileName =
+                "${MainDomainData.userInfo?.userId}_avatar_${TimeUtils.nowString("yyyyMMddHHmmss")}"
+            FileStorageUtils.writeText(
+                CommonConstants.AVATAR_FOLDER,
+                saveFileName,
+                ImageConvertUtils.bitmapToBase64(mCapturedBitmap).toString()
+            )
+            val savePath = FileStorageUtils.getFilePath(
+                CommonConstants.AVATAR_FOLDER,
+                saveFileName
+            )
+            viewModel.saveUserAvatar(savePath).observe(this) {
+                getData()
+            }
+        }
+        binding.recapture.setDebouncedClickListener {
+            reCaptureTimer.start()
+            binding.countDownTip.isVisible = false
+            binding.image.isVisible = false
+            binding.previewLayout.isVisible = true
+            binding.setAvatarConfirm.isVisible = false
+            binding.recapture.isVisible = false
+            binding.faceOverlayView.setFaceRect(null)
+        }
+        binding.confirm.setDebouncedClickListener {
+            if (checkData()) {
+                viewModel.saveUserInfo(
+                    binding.nicknameEt.text.toString(),
+                    binding.phoneEt.text.toString()
+                ).observe(this) {
+                    if (it) {
+                        TipDialog.showSuccess(
+                            CommonUtils.getStr("update_user_succeed").toString()
+                        )
+                    } else {
+                        TipDialog.showError(
+                            CommonUtils.getStr("update_user_failed").toString()
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getData()
+    }
+
+    private fun getData() {
+        binding.username.text = MainDomainData.userInfo?.userName
+        binding.nicknameEt.setText(MainDomainData.userInfo?.nickName)
+        binding.phoneEt.setText(MainDomainData.userInfo?.phoneNumber)
+        binding.deleteAvatar.isVisible = MainDomainData.userInfo?.avatar?.isNotEmpty() == true
+        MainDomainData.userInfo?.avatar?.let {
+            if (it.isEmpty()) {
+                return
+            }
+            if (it.file().exists()) {
+                val avatar = ImageConvertUtils.base64ToBitmap(it.file().readText())
+                binding.avatar.load(avatar) {
+                    transformations(CircleCropTransformation())
+                }
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.nicknameEt.text.toString().isEmpty()) {
+            showToast(CommonUtils.getStr("please_input_nickname"))
+            return false
+        }
+        if (binding.phoneEt.text.toString().isEmpty()) {
+            showToast(CommonUtils.getStr("please_input_phone"))
+            return false
+        }
+        if (!RegexUtils.isMatch(binding.phoneEt.text.toString(), CommonConstants.REGEX_MOBILE)) {
+            showToast(CommonUtils.getStr("please_input_correct_phone"))
+            return false
+        }
+        return true
+    }
+
+    private fun startFace() {
+        binding.previewLayout.isVisible = true
+        binding.image.isVisible = false
+        ArcSoftUtil.inDetecting = false
+        ArcSoftUtil.initCamera(
+            requireContext(),
+            requireActivity().windowManager,
+            binding.preview,
+            binding.faceOverlayView,
+            true,
+        ) { bitmap, faceSize, alive ->
+            binding.tipTv.isVisible = faceSize > 1 || alive == false
+            logger.info("人脸检测结果: ${bitmap == null},$faceSize,$alive")
+            if (faceSize > 1) {
+                binding.tipTv.text = CommonUtils.getStr("only_one_person_allowed")
+                ArcSoftUtil.inDetecting = false
+                stopCountDown()
+                return@initCamera
+            }
+            if (alive == false) {
+                binding.tipTv.text =
+                    CommonUtils.getStr("real_person_verification_required")
+                ArcSoftUtil.inDetecting = false
+                stopCountDown()
+                return@initCamera
+            }
+            if (!isInCountDown) {
+                startCountDown()
+            }
+            if (!isFaceDetect) {
+                mCapturedBitmap = bitmap
+                binding.image.setImageBitmap(bitmap)
+            }
+            ArcSoftUtil.inDetecting = false
+        }
+    }
+
+    private fun startCountDown() {
+        isInCountDown = true
+        binding.countDownTip.text = CommonUtils.getStr("detect_face_tip")
+        binding.countDownTip.isVisible = true
+        captureTimer.start()
+    }
+
+    private fun stopCountDown() {
+        isInCountDown = false
+        binding.countDownTip.text = CommonUtils.getStr("detect_face_tip")
+        binding.countDownTip.isVisible = false
+        captureTimer.cancel()
+    }
+
+    private fun releaseFace() {
+        ArcSoftUtil.stop()
+    }
+}

+ 145 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/user_info/UserInfoHomeFragment.kt

@@ -0,0 +1,145 @@
+package com.grkj.iscs_mc.features.main.fragment.user_info
+
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.dividerSpace
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.google.android.flexbox.AlignItems
+import com.google.android.flexbox.FlexDirection
+import com.google.android.flexbox.FlexWrap
+import com.google.android.flexbox.FlexboxLayoutManager
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentUserInfoHomeBinding
+import com.grkj.iscs_mc.databinding.ItemHomeMenuBinding
+import com.grkj.iscs_mc.features.main.entity.MenuItemEntity
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.changeBgTint
+import com.grkj.ui_base.utils.event.LogoutEvent
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 用户信息菜单
+ */
+@AndroidEntryPoint
+class UserInfoHomeFragment : BaseFragment<FragmentUserInfoHomeBinding>() {
+    private var menuData: MutableList<MenuItemEntity> = mutableListOf(
+        MenuItemEntity(
+            0,
+            "chalkboard-user.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.USER_INFO.description),
+            RoleFunctionalPermissionsEnum.USER_INFO.functionalPermission
+        ),
+        MenuItemEntity(
+            1,
+            "password-lock.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.RESET_PASSWORD.description),
+            RoleFunctionalPermissionsEnum.RESET_PASSWORD.functionalPermission
+        ),
+        MenuItemEntity(
+            2,
+            "fingerprint.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.FINGERPRINT_SETTING.description),
+            RoleFunctionalPermissionsEnum.FINGERPRINT_SETTING.functionalPermission
+        ),
+        MenuItemEntity(
+            3,
+            "face-id-svgrepo-com.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.FACE_SETTING.description),
+            RoleFunctionalPermissionsEnum.FACE_SETTING.functionalPermission
+        ),
+        MenuItemEntity(
+            4,
+            "cards-blank.png",
+            I18nManager.t(RoleFunctionalPermissionsEnum.CARD_SETTING.description),
+            RoleFunctionalPermissionsEnum.CARD_SETTING.functionalPermission
+        ),
+        MenuItemEntity(
+            5,
+            "icon_settings.png",
+            I18nManager.t(RoleFunctionalPermissionsEnum.SETTINGS.description),
+            RoleFunctionalPermissionsEnum.SETTINGS.functionalPermission
+        ),
+        MenuItemEntity(
+            6,
+            "leave.svg",
+            I18nManager.t(RoleFunctionalPermissionsEnum.LOGOUT.description),
+            RoleFunctionalPermissionsEnum.LOGOUT.functionalPermission
+        ),
+    )
+
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_user_info_home
+    }
+
+    override fun initView() {
+        binding.homeMenuRv.apply {
+            layoutManager = FlexboxLayoutManager(requireContext()).apply {
+                flexDirection = FlexDirection.ROW        // 横向布局
+                flexWrap = FlexWrap.WRAP
+                alignItems = AlignItems.STRETCH   // ✅ 不会被压扁
+            }
+            dividerSpace(40, DividerOrientation.GRID)
+        }.setup {
+            addType<MenuItemEntity>(R.layout.item_home_menu)
+            onBind {
+                onHomeMenuBinding(this)
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        menuData =
+            menuData.filter { MainDomainData.permissions.contains(it.permission) }.toMutableList()
+        binding.homeMenuRv.models = menuData
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onHomeMenuBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemHomeMenuBinding>()
+        val item = holder.getModel<MenuItemEntity>()
+        itemBinding.homeMenuIv.loadSkinIcon(item.menuIcon)
+        itemBinding.homeMenuTv.text = item.menuText
+        itemBinding.homeMenuLayout.changeBgTint(item.menuBgTint)
+        itemBinding.root.setDebouncedClickListener {
+            onMenuClick(item.type)
+        }
+    }
+
+    private fun onMenuClick(menuType: Int) {
+        when (menuType) {
+            0 -> {
+                navController.navigate(R.id.action_userInfoHomeFragment_to_userInfoFragment)
+            }
+
+            1 -> {
+                navController.navigate(R.id.action_userInfoHomeFragment_to_resetPasswordFragment)
+            }
+
+            2 -> {
+                navController.navigate(R.id.action_userInfoHomeFragment_to_setFingerprintFragment)
+            }
+
+            3 -> {
+                navController.navigate(R.id.action_userInfoHomeFragment_to_setFaceFragment)
+            }
+
+            4 -> {
+                navController.navigate(R.id.action_userInfoHomeFragment_to_setJobCardFragment)
+            }
+
+            5 -> {
+                navController.navigate(R.id.action_userInfoHomeFragment_to_settingsFragment)
+            }
+
+            6 -> {
+                LogoutEvent.sendLogoutEvent()
+            }
+        }
+    }
+}

+ 1 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/MainViewModel.kt

@@ -9,4 +9,5 @@ import javax.inject.Inject
  */
 @HiltViewModel
 class MainViewModel @Inject constructor() : BaseViewModel() {
+
 }

+ 111 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/BackupAndRestoreViewModel.kt

@@ -0,0 +1,111 @@
+package com.grkj.iscs_mc.features.main.viewmodel.data_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.enums.BackupFrequencyWeekEnum
+import com.grkj.data.local.database.BackupScheduler
+import com.grkj.data.local.database.RoomBackupManager
+import com.grkj.data.local.database.SimpleBackupConfig
+import com.grkj.data.local.database.SimpleBackupPrefs
+import com.grkj.iscs_mc.features.main.dialog.TextDropDownDialog
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.ui_base.base.BaseViewModel
+import com.sik.sikcore.SIKCore
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 备份/还原数据结构
+ */
+@HiltViewModel
+class BackupAndRestoreViewModel @Inject constructor() : BaseViewModel() {
+    /**
+     * 计划数据
+     */
+    var scheduleData: SimpleBackupConfig = SimpleBackupPrefs.get(SIKCore.getApplication())
+
+    /**
+     * 备份数据
+     */
+    var backupItemDatas: MutableList<RoomBackupManager.BackupItem> = mutableListOf()
+
+    /**
+     * 备份频率数据
+     */
+    val backupFrequencyData: List<TextDropDownDialog.SimpleTextDropDownEntity>
+        get() = BackupFrequencyWeekEnum.values().map {
+            TextDropDownDialog.SimpleTextDropDownEntity(
+                dataId = it.type.toLong(),
+                dataText = I18nManager.t(it.showText),
+            ).apply {
+                setSelected(selectedBackupFrequencyData.map { I18nManager.t(it.showText) }
+                    .contains(getShowText()))
+            }
+        }
+
+    /**
+     * 选择的备份频率数据
+     */
+    var selectedBackupFrequencyData: List<BackupFrequencyWeekEnum> = listOf()
+
+    /**
+     * 设置并应用
+     */
+    fun setAndApply(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            BackupScheduler.setAndApply(
+                SIKCore.getApplication(),
+                scheduleData.enabled,
+                scheduleData.hour,
+                scheduleData.minute,
+                scheduleData.daysMask,
+                scheduleData.keep
+            )
+            emit(true)
+        }
+    }
+
+    /**
+     * 备份还原
+     */
+    fun restoreBackUp(item: RoomBackupManager.BackupItem): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            RoomBackupManager.restore(SIKCore.getApplication(), item.file)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取备份文件
+     */
+    fun getBackupList(): LiveData<List<RoomBackupManager.BackupItem>> {
+        return liveData(Dispatchers.IO) {
+            backupItemDatas =
+                RoomBackupManager.listBackups(SIKCore.getApplication()).toMutableList()
+            emit(backupItemDatas)
+        }
+    }
+
+    /**
+     * 删除备份文件
+     */
+    fun deleteBackupFile(item: RoomBackupManager.BackupItem): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(RoomBackupManager.deleteBackup(item.file))
+        }
+    }
+
+    /**
+     * 删除多备份文件
+     */
+    fun deleteBackupFiles(items: List<RoomBackupManager.BackupItem>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            for (item in items) {
+                RoomBackupManager.deleteBackup(item.file)
+            }
+            emit(true)
+        }
+    }
+
+}

+ 61 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/DataExportViewModel.kt

@@ -0,0 +1,61 @@
+package com.grkj.iscs_mc.features.main.viewmodel.data_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.domain.logic.IDataExportLogic
+import com.grkj.data.domain.vo.DataExportVo
+import com.grkj.data.enums.DataExportTableEnum
+import com.grkj.data.enums.getLastExportTime
+import com.grkj.data.enums.getTableName
+import com.grkj.data.enums.setLastExportTime
+import com.grkj.ui_base.base.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import java.io.File
+import javax.inject.Inject
+
+/**
+ * 数据导出
+ */
+@HiltViewModel
+class DataExportViewModel @Inject constructor(val dataExportLogic: IDataExportLogic) :
+    BaseViewModel() {
+    var dataExportTableData: List<DataExportVo> = mutableListOf()
+
+    /**
+     * 获取导出表数据
+     */
+    fun getExportTableData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            dataExportTableData =
+                DataExportTableEnum.values().filter { it != DataExportTableEnum.NONE }.map {
+                    DataExportVo().apply {
+                        dataExportTableEnum = it
+                        tableName = it.getTableName()
+                        lastUpdateTime = it.getLastExportTime()
+                    }
+                }
+            emit(true)
+        }
+    }
+
+    /**
+     * 导出数据
+     */
+    fun exportData(): LiveData<File?> {
+        return liveData(Dispatchers.IO) {
+            emit(dataExportLogic.dataExport(dataExportTableData.filter { it.isSelected }
+                .map { it.dataExportTableEnum }))
+        }
+    }
+
+    /**
+     * 更新导出时间
+     */
+    fun updateExportTime() {
+        dataExportTableData.forEach {
+            it.dataExportTableEnum.setLastExportTime()
+            it.lastUpdateTime = it.dataExportTableEnum.getLastExportTime()
+        }
+    }
+}

+ 144 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/RoleManageViewModel.kt

@@ -0,0 +1,144 @@
+package com.grkj.iscs_mc.features.main.viewmodel.data_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.domain.logic.ISysLogic
+import com.grkj.data.domain.vo.AddRoleDo
+import com.grkj.data.model.vo.RoleManageFilterVo
+import com.grkj.data.model.vo.RoleManageVo
+import com.grkj.data.domain.vo.UpdateRoleDo
+import com.grkj.iscs_mc.features.main.entity.AddRoleDataEntity
+import com.grkj.iscs_mc.features.main.entity.RoleManageFunctionalPermissionsEntity
+import com.grkj.iscs_mc.features.main.entity.UpdateRoleDataEntity
+import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.utils.CommonUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 角色管理界面模型
+ */
+@HiltViewModel
+class RoleManageViewModel @Inject constructor(
+    val sysLogic: ISysLogic
+) :
+    BaseViewModel() {
+    private var current: Int = 0
+    private var size: Int = 50
+    var roleManageDataList: MutableList<RoleManageVo> = mutableListOf()
+    var roleFilterData: RoleManageFilterVo? = null
+
+    /**
+     * 删除选中角色
+     */
+    fun deleteSelectedRoles(roleIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            sysLogic.deleteRoleByRoleIds(roleIds)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取角色数据
+     */
+    fun getRoleData(
+        filterData: RoleManageFilterVo? = null,
+        nextPage: Boolean = true
+    ): LiveData<Boolean> {
+        if (nextPage) {
+            current += 1
+        } else {
+            current = 0
+            roleManageDataList.clear()
+        }
+        return liveData(Dispatchers.IO) {
+            val roleManageDataPage = sysLogic.getRoleManagerData(filterData, current, size)
+            roleManageDataList.addAll(roleManageDataPage)
+            emit(true)
+        }
+    }
+
+    /**
+     * 添加角色
+     */
+    fun addRoleData(roleDataEntity: AddRoleDataEntity): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val sysMenus = sysLogic.getSysMenus()
+            sysLogic.addRoleData(AddRoleDo().apply {
+                roleName = roleDataEntity.roleName
+                roleKey = roleDataEntity.roleKeys
+                status = roleDataEntity.status
+                menuData =
+                    sysMenus.filter { it.perms in roleDataEntity.functionalPermissions.flatMap { it.getFunctionalPermissionWithChildren() } }
+            })
+            emit(true)
+        }
+    }
+
+    /**
+     * 更新角色
+     */
+    fun updateRoleData(roleDataEntity: UpdateRoleDataEntity): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val sysMenus = sysLogic.getSysMenus()
+            sysLogic.updateRoleData(UpdateRoleDo().apply {
+                roleId = roleDataEntity.roleId
+                roleName = roleDataEntity.roleName
+                roleKey = roleDataEntity.roleKeys
+                status = roleDataEntity.status
+                menuData =
+                    sysMenus.filter { it.perms in roleDataEntity.functionalPermissions.flatMap { it.getFunctionalPermissionWithChildren() } }
+            })
+            emit(true)
+        }
+    }
+
+    /**
+     * 根据角色id获取功能权限列表
+     */
+    fun getFunctionalPermissionsByRoleId(roleId: Long): LiveData<List<RoleManageFunctionalPermissionsEntity>> {
+        return liveData(Dispatchers.IO) {
+            val functionalPermissions =
+                RoleManageFunctionalPermissionsEntity.getFunctionalPermissions().reversed()
+            var selectedPermissions =
+                sysLogic.getSysMenusByRoleId(roleId).map { it.perms }
+
+            fun setSelectedFunctionalPermissions(dataList: List<RoleManageFunctionalPermissionsEntity>) {
+                dataList.forEach {
+                    it.isSelected = it.functionalPermission in selectedPermissions
+                    setSelectedFunctionalPermissions(it.children)
+                }
+            }
+            setSelectedFunctionalPermissions(functionalPermissions)
+            emit(functionalPermissions)
+        }
+    }
+
+    /**
+     * 校验角色数据
+     */
+    fun validateRoleData(roleKey: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val hasRoleExists = sysLogic.getRoleByRoleKey(roleKey)
+            if (hasRoleExists == null) {
+                emit(true)
+            } else {
+                showTip(
+                    CommonUtils.getStr("role_key_already_exists").toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * 检查角色是否在使用
+     */
+    fun checkRoleInUse(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val userIds = sysLogic.getUserIdsByRole(roleManageDataList.filter { it.isSelected }
+                .map { it.roleId })
+            emit(userIds.isNotEmpty())
+        }
+    }
+}

+ 118 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/data_manage/UserManageViewModel.kt

@@ -0,0 +1,118 @@
+package com.grkj.iscs_mc.features.main.viewmodel.data_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.domain.logic.ISysLogic
+import com.grkj.data.domain.logic.IUserLogic
+import com.grkj.data.local.dos.SysRole
+import com.grkj.data.model.vo.AddUserDataVo
+import com.grkj.data.model.vo.UpdateUserDataVo
+import com.grkj.data.model.vo.UserManageFilterVo
+import com.grkj.data.model.vo.UserManageVo
+import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.utils.CommonUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 用户管理界面模型
+ */
+@HiltViewModel
+class UserManageViewModel @Inject constructor(
+    val userLogic: IUserLogic,
+    val sysLogic: ISysLogic,
+) : BaseViewModel() {
+    private var current: Int = 0
+    private var size: Int = 50
+    var userManageDataList: MutableList<UserManageVo> = mutableListOf()
+    var userFilterData: UserManageFilterVo? = null
+    var roleData: List<SysRole> = listOf()
+
+    /**
+     * 删除选中用户
+     */
+    fun deleteSelectedUsers(userIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userLogic.deleteUserById(userIds)
+            sysLogic.deleteUserRoleByUserId(userIds)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取用户数据
+     */
+    fun getUserData(
+        filterData: UserManageFilterVo? = null,
+        nextPage: Boolean = true
+    ): LiveData<Boolean> {
+        if (nextPage) {
+            current += 1
+        } else {
+            current = 0
+            userManageDataList.clear()
+        }
+        return liveData(Dispatchers.IO) {
+            val userManageDataPage = userLogic.getUserManagerData(filterData, current, size)
+            userManageDataList.addAll(userManageDataPage)
+            emit(true)
+        }
+    }
+
+    /**
+     * 添加用户
+     */
+    fun addUser(userData: AddUserDataVo): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val userId = userLogic.addUserData(userData)
+            sysLogic.addUserRoleData(userId, userData.roleId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取角色
+     */
+    fun getRoleData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            roleData = sysLogic.getAllRoleData()
+            emit(true)
+        }
+    }
+
+    /**
+     * 更新用户
+     */
+    fun updateUser(updateUserDataVo: UpdateUserDataVo): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userLogic.updateUserData(updateUserDataVo)
+            sysLogic.deleteUserRoleByUserId(listOf(updateUserDataVo.userId))
+            sysLogic.addUserRoleData(updateUserDataVo.userId, updateUserDataVo.roleId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 校验用户数据
+     */
+    fun validateUserData(username: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val user = userLogic.getUserByUserName(username)
+            if (user != null) {
+                showTip(CommonUtils.getStr("user_already_exists").toString())
+            } else {
+                emit(true)
+            }
+        }
+    }
+
+    /**
+     * 用户是否在正在进行中的作业
+     */
+    fun userInProgressJob(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(false)
+        }
+    }
+}

+ 45 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/home/HomeViewModel.kt

@@ -0,0 +1,45 @@
+package com.grkj.iscs_mc.features.main.viewmodel.home
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.domain.logic.IHardwareLogic
+import com.grkj.data.domain.logic.IUserLogic
+import com.grkj.ui_base.base.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 主界面界面模型
+ */
+@HiltViewModel
+class HomeViewModel @Inject constructor(
+    val hardwareLogic: IHardwareLogic,
+    val userLogic: IUserLogic
+) : BaseViewModel() {
+    var allMaterial = 0
+    var borrowMaterial = 0
+    var unborrowedMaterial = 0
+
+    /**
+     * 获取首页数据
+     */
+    fun getHomeData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(true)
+        }
+    }
+
+    /**
+     * 保存快捷入口
+     */
+    fun saveQuickEntranceData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            MainDomainData.userInfo?.let {
+                userLogic.updateUser(it)
+                emit(true)
+            }
+        }
+    }
+}

+ 176 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/user_info/UserInfoViewModel.kt

@@ -0,0 +1,176 @@
+package com.grkj.iscs_mc.features.main.viewmodel.user_info
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.common.MainDomainData
+import com.grkj.data.domain.logic.IHardwareLogic
+import com.grkj.data.domain.logic.IUserLogic
+import com.grkj.data.domain.vo.FingerprintDataVo
+import com.grkj.data.domain.vo.SysBiometricDataVo
+import com.grkj.data.local.dos.IsJobCardDo
+import com.grkj.data.local.dos.SysUserCharacteristicDo
+import com.grkj.shared.utils.ArcSoftUtil
+import com.grkj.shared.utils.BCryptUtils
+import com.grkj.ui_base.base.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+@HiltViewModel
+class UserInfoViewModel @Inject constructor(
+    val userLogic: IUserLogic,
+    val hardwareRepository: IHardwareLogic
+) : BaseViewModel() {
+    /**
+     * 生物数据
+     */
+    var fingerprintData: List<FingerprintDataVo> =
+        mutableListOf()
+
+    /**
+     * 人脸数据
+     */
+    var faceData: List<SysBiometricDataVo> = mutableListOf()
+
+    /**
+     * 工卡数据
+     */
+    var jobCardDataVo: List<IsJobCardDo> = mutableListOf()
+
+    /**
+     * 保存用户信息
+     */
+    fun saveUserInfo(nickName: String, phone: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            MainDomainData.userInfo?.userId?.let {
+                userLogic.updateUserInfo(it, nickName, phone)
+                MainDomainData.userInfo = userLogic.getUserByUserId(it)
+                emit(true)
+            } ?: emit(false)
+        }
+    }
+
+    /**
+     * 更新密码
+     */
+    fun updatePassword(newPassword: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            MainDomainData.userInfo?.userId?.let {
+                val password = BCryptUtils.encryptPassword(newPassword)
+                logger.info("用户id:${it}")
+                userLogic.updatePassword(it, password)
+                emit(true)
+            } ?: emit(false)
+        }
+    }
+
+    /**
+     * 获取指纹数据
+     */
+    fun getFingerprintData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            fingerprintData =
+                userLogic.getFingerprintDataByUserId(MainDomainData.userInfo?.userId)
+                    .groupBy { it.group }.map {
+                        FingerprintDataVo().apply {
+                            group = it.key
+                            fingerprintData = it.value
+                        }
+                    }
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取人脸数据
+     */
+    fun getFaceData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            faceData =
+                userLogic.getFaceDataByUserId(MainDomainData.userInfo?.userId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 根据id删除指纹数据
+     */
+    fun deleteFingerprintByIds(fingerprintIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userLogic.deleteFingerprintByIds(fingerprintIds)
+            emit(true)
+        }
+    }
+
+    /**
+     * 保存用户指纹
+     */
+    fun saveUserFingerprint(b64: String, group: String): LiveData<Long> {
+        return liveData(Dispatchers.IO) {
+            val sysUserCharacteristicDo = SysUserCharacteristicDo()
+            sysUserCharacteristicDo.userId = MainDomainData.userInfo?.userId!!
+            sysUserCharacteristicDo.content = b64
+            sysUserCharacteristicDo.type = "1"
+            sysUserCharacteristicDo.group = group
+            val fingerprintId = userLogic.saveUserCharacteristic(sysUserCharacteristicDo)
+            emit(fingerprintId)
+        }
+    }
+
+    /**
+     * 保存人脸数据
+     */
+    fun saveUserFace(savePath: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            userLogic.deleteFaceDataByUserId(MainDomainData.userInfo?.userId!!)
+            val sysUserCharacteristicDo = SysUserCharacteristicDo()
+            sysUserCharacteristicDo.userId = MainDomainData.userInfo?.userId!!
+            sysUserCharacteristicDo.content = savePath
+            sysUserCharacteristicDo.type = "2"
+            logger.info("保存的人脸数据:${sysUserCharacteristicDo}")
+            userLogic.saveUserCharacteristic(sysUserCharacteristicDo)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取工卡数据
+     */
+    fun getJobCardData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            jobCardDataVo =
+                hardwareRepository.getJobCardDataByUserId(MainDomainData.userInfo?.userId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 保存用户工卡
+     */
+    fun saveUserJobCard(rfidNo: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            hardwareRepository.updateUserJobCard(rfidNo, MainDomainData.userInfo?.userId!!)
+            emit(true)
+        }
+    }
+
+    /**
+     * 保存用户头像
+     */
+    fun saveUserAvatar(avatarSavePath: String?): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            MainDomainData.userInfo?.let {
+                it.avatar = avatarSavePath
+                userLogic.updateUser(it)
+            }
+            emit(true)
+        }
+    }
+
+    fun registerFaceFeature(imageData: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            ArcSoftUtil.registerFace(listOf((MainDomainData.userInfo?.userId ?: 0) to imageData))
+            emit(true)
+        }
+    }
+}

+ 0 - 20
app/src/main/java/com/grkj/iscs_mc/features/manage/activity/ManageActivity.kt

@@ -1,20 +0,0 @@
-package com.grkj.iscs_mc.features.manage.activity
-
-import com.grkj.iscs_mc.R
-import com.grkj.iscs_mc.databinding.ActivityManageBinding
-import com.grkj.ui_base.base.BaseActivity
-import dagger.hilt.android.AndroidEntryPoint
-
-/**
- * 管理界面
- */
-@AndroidEntryPoint
-class ManageActivity : BaseActivity<ActivityManageBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.activity_manage
-    }
-
-    override fun initView() {
-
-    }
-}

+ 0 - 20
app/src/main/java/com/grkj/iscs_mc/features/material_exchange/activity/MaterialExchangeActivity.kt

@@ -1,20 +0,0 @@
-package com.grkj.iscs_mc.features.material_exchange.activity
-
-import com.grkj.iscs_mc.R
-import com.grkj.iscs_mc.databinding.ActivityMaterialExchangeBinding
-import com.grkj.ui_base.base.BaseActivity
-import dagger.hilt.android.AndroidEntryPoint
-
-/**
- * 物资取还
- */
-@AndroidEntryPoint
-class MaterialExchangeActivity : BaseActivity<ActivityMaterialExchangeBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.activity_material_exchange
-    }
-
-    override fun initView() {
-        
-    }
-}

+ 5 - 0
app/src/main/res/drawable/bg_card_color_menu_bg_radius_md.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="?attr/colorWhite" />
+    <corners android:radius="@dimen/iscs_radius_md" />
+</shape>

+ 6 - 0
app/src/main/res/drawable/bg_home_card_num.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/iscs_radius_md" />
+    <solid android:color="?attr/colorBg" />
+</shape>

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

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

+ 6 - 0
app/src/main/res/drawable/common_divider_normal_space_horizontal.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size
+        android:width="@dimen/iscs_space_2"
+        android:height="@dimen/iscs_stroke_sm" />
+</shape>

+ 6 - 0
app/src/main/res/drawable/common_divider_normal_space_vertical.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size
+        android:width="@dimen/iscs_stroke_sm"
+        android:height="@dimen/iscs_space_2" />
+</shape>

+ 6 - 0
app/src/main/res/drawable/common_divider_small_space_horizontal.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size
+        android:width="@dimen/iscs_space_1"
+        android:height="@dimen/iscs_stroke_sm" />
+</shape>

+ 6 - 0
app/src/main/res/drawable/common_divider_small_space_vertical.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size
+        android:width="@dimen/iscs_stroke_sm"
+        android:height="@dimen/iscs_space_1" />
+</shape>

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

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/icon_add_box_disable" android:state_selected="true" />
+    <item android:drawable="@drawable/icon_add_box_enable" />
+</selector>

+ 13 - 0
app/src/main/res/drawable/icon_add_box_disable.xml

@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="?attr/colorBlack50"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="?attr/colorDisable"
+        android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14zM11,17h2v-4h4v-2h-4L13,7h-2v4L7,11v2h4z" />
+
+</vector>

+ 13 - 0
app/src/main/res/drawable/icon_add_box_enable.xml

@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="?attr/colorBlack80"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14zM11,17h2v-4h4v-2h-4L13,7h-2v4L7,11v2h4z" />
+
+</vector>

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

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

+ 446 - 0
app/src/main/res/layout-land/fragment_backup_and_restore.xml

@@ -0,0 +1,446 @@
+<?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">
+
+    <data>
+
+        <import type="kotlin.collections.CollectionsKt" />
+    </data>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/home_card_bg"
+        android:layout_margin="@dimen/iscs_space_2"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2">
+
+            <ImageView
+                android:layout_width="@dimen/title_icon_size"
+                android:layout_height="@dimen/title_icon_size"
+                app:skinSrc='@{"back-up.svg"}'
+                android:tint="?attr/colorPrimary" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_weight="1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                android:textStyle="bold"
+                app:i18nKey='@{"backup_title"}' />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="5dp"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:drawableLeft="@mipmap/icon_back"
+                android:drawableTint="?attr/colorPrimary"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"back"}' />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorBlack" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_margin="@dimen/iscs_space_4"
+                android:layout_weight="1"
+                android:background="@drawable/home_card_bg"
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:textStyle="bold"
+                        app:i18nKey='@{"backup"}' />
+
+                    <View
+                        android:layout_width="0dp"
+                        android:layout_height="1dp"
+                        android:layout_weight="1" />
+
+
+                    <TextView
+                        android:id="@+id/backup_now"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginVertical="5dp"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:layout_marginRight="@dimen/iscs_space_4"
+                        android:background="@drawable/common_btn_secondary"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"backup_now"}' />
+                </LinearLayout>
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/divider_line_space"
+                    android:background="?attr/colorBlack" />
+
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+                    <TextView
+                        android:id="@+id/backup_path_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"backup_path"}'
+                        app:layout_constraintEnd_toEndOf="@+id/end_line"
+                        app:layout_constraintTop_toTopOf="parent" />
+
+                    <TextView
+                        android:id="@+id/backup_path"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_common_input"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintBottom_toBottomOf="@+id/backup_path_tv"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/end_line" />
+
+                    <TextView
+                        android:id="@+id/maximum_number_of_backups_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"maximum_number_of_backups"}'
+                        app:layout_constraintEnd_toEndOf="@+id/end_line"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toBottomOf="@+id/backup_path_tv" />
+
+                    <EditText
+                        android:id="@+id/maximum_number_of_backups"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:layout_marginRight="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_common_input"
+                        android:inputType="number"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintBottom_toBottomOf="@+id/maximum_number_of_backups_tv"
+                        app:layout_constraintEnd_toStartOf="@+id/maximum_number_of_backups_range"
+                        app:layout_constraintStart_toEndOf="@+id/end_line" />
+
+                    <TextView
+                        android:id="@+id/maximum_number_of_backups_range"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nArg0='@{"5-20"}'
+                        app:i18nKey='@{"backup_range"}'
+                        app:layout_constraintEnd_toEndOf="@+id/backup_path"
+                        app:layout_constraintTop_toBottomOf="@+id/backup_path_tv"
+                        app:layout_constraintTop_toTopOf="@+id/maximum_number_of_backups_tv" />
+
+                    <TextView
+                        android:id="@+id/status_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"auto_backup"}'
+                        app:layout_constraintEnd_toEndOf="@+id/end_line"
+                        app:layout_constraintTop_toBottomOf="@+id/maximum_number_of_backups_tv" />
+
+                    <RadioGroup
+                        android:id="@+id/status_rg"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                        app:layout_constraintStart_toEndOf="@+id/end_line"
+                        app:useMaterialThemeColors="true"
+                        app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                        <com.google.android.material.radiobutton.MaterialRadioButton
+                            android:id="@+id/enable_rb"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            app:useMaterialThemeColors="true"
+                            android:layout_marginStart="@dimen/iscs_space_2"
+                            android:textSize="@dimen/iscs_text_md"
+                            android:textColor="?attr/colorTextPrimary"
+                            app:i18nKey='@{"common_enable"}' />
+
+                        <com.google.android.material.radiobutton.MaterialRadioButton
+                            android:id="@+id/disable_rb"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginStart="@dimen/iscs_space_2"
+                            android:textSize="@dimen/iscs_text_md"
+                            android:textColor="?attr/colorTextPrimary"
+                            app:i18nKey='@{"common_disable"}' />
+                    </RadioGroup>
+
+                    <com.grkj.ui_base.widget.RequiredTextView
+                        android:id="@+id/backup_frequency_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"backup_frequency"}'
+                        app:layout_constraintEnd_toEndOf="@+id/end_line"
+                        app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+                    <TextView
+                        android:id="@+id/backup_frequency"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_common_input"
+                        android:drawableRight="@mipmap/icon_drop_down"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintBottom_toBottomOf="@+id/backup_frequency_tv"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/end_line"
+                        app:layout_constraintTop_toTopOf="@+id/backup_frequency_tv" />
+
+                    <com.grkj.ui_base.widget.RequiredTextView
+                        android:id="@+id/backup_time_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"backup_frequency"}'
+                        app:layout_constraintEnd_toEndOf="@+id/end_line"
+                        app:layout_constraintTop_toBottomOf="@+id/backup_frequency_tv" />
+
+                    <TextView
+                        android:id="@+id/backup_time"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_common_input"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintBottom_toBottomOf="@+id/backup_time_tv"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/end_line"
+                        app:layout_constraintTop_toTopOf="@+id/backup_time_tv" />
+
+                    <androidx.constraintlayout.widget.Barrier
+                        android:id="@+id/end_line"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        app:barrierDirection="right"
+                        app:constraint_referenced_ids="maximum_number_of_backups_tv,backup_path_tv" />
+
+                    <TextView
+                        android:id="@+id/backup_tip"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_tip_red"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"backup_tip"}'
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toBottomOf="@+id/backup_time" />
+
+                    <TextView
+                        android:id="@+id/save"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginRight="@dimen/iscs_space_4"
+                        android:layout_marginBottom="@dimen/iscs_space_4"
+                        android:background="@drawable/common_btn_secondary"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"save"}'
+                        app:layout_constraintBottom_toBottomOf="parent"
+                        app:layout_constraintEnd_toEndOf="parent" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_margin="@dimen/iscs_space_4"
+                android:layout_weight="1"
+                android:background="@drawable/home_card_bg"
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:textStyle="bold"
+                        app:i18nKey='@{"restore"}' />
+
+                    <View
+                        android:layout_width="0dp"
+                        android:layout_height="1dp"
+                        android:layout_weight="1" />
+
+                    <TextView
+                        android:id="@+id/batch_export"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginVertical="5dp"
+                        android:background="@drawable/common_btn_secondary"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"common_batch_export"}' />
+
+                    <TextView
+                        android:id="@+id/batch_delete"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginVertical="5dp"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:layout_marginRight="@dimen/iscs_space_4"
+                        android:background="@drawable/common_btn_secondary"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:i18nKey='@{"common_batch_delete"}' />
+                </LinearLayout>
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/divider_line_space"
+                    android:background="?attr/colorBlack" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="@dimen/iscs_space_4"
+                    android:layout_marginTop="@dimen/iscs_space_2"
+                    android:background="@drawable/common_card_header_bg"
+                    android:divider="@drawable/divider_table"
+                    android:showDividers="middle">
+
+                    <com.google.android.material.checkbox.MaterialCheckBox
+                        android:id="@+id/select_all"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center"
+                        app:useMaterialThemeColors="true" />
+
+                    <TextView
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="2"
+                        android:gravity="center"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:textColor="?attr/colorTextPrimary"
+                        app:i18nKey='@{"backup"}' />
+
+                    <TextView
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:gravity="center"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:textColor="?attr/colorTextPrimary"
+                        app:i18nKey='@{"operation"}' />
+                </LinearLayout>
+
+                <com.drake.statelayout.StateLayout
+                    android:id="@+id/state"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_marginHorizontal="@dimen/iscs_space_4"
+                    android:layout_marginBottom="@dimen/iscs_space_2"
+                    android:background="@drawable/common_card_bg">
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/list_rv"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:background="@drawable/common_card_bg"
+                        tools:listitem="@layout/item_backup" />
+                </com.drake.statelayout.StateLayout>
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+</layout>

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

@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/iscs_space_4"
+            android:layout_marginTop="@dimen/iscs_space_2"
+            android:background="@drawable/home_card_bg"
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/quick_entrance_rv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_margin="@dimen/iscs_space_2"
+                android:layout_weight="1"
+                tools:itemCount="1"
+                tools:listitem="@layout/item_home_quick_entrance" />
+
+            <TextView
+                android:id="@+id/quick_entrance_config"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_blue_bg"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="@dimen/iscs_space_1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"edit"}' />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/iscs_space_4"
+            android:layout_marginVertical="@dimen/iscs_space_2"
+            android:divider="@drawable/common_divider_small_space_vertical"
+            android:orientation="vertical"
+            android:showDividers="middle">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingVertical="@dimen/iscs_space_1"
+                android:paddingLeft="@dimen/iscs_space_2">
+
+                <ImageView
+                    android:layout_width="@dimen/title_icon_size"
+                    android:layout_height="@dimen/title_icon_size"
+                    android:tint="?attr/colorPrimary"
+                    app:skinSrc='@{"icon_realtime_data.png"}' />
+
+                <TextView
+                    android:id="@+id/realtime_data_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/iscs_space_2"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"material_data_title"}' />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_card_bg"
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_marginTop="@dimen/iscs_space_2"
+                    android:layout_marginBottom="@dimen/iscs_space_2"
+                    android:divider="@drawable/common_divider_normal_space_horizontal"
+                    android:orientation="horizontal"
+                    android:paddingHorizontal="@dimen/iscs_space_2"
+                    android:showDividers="middle">
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:background="@drawable/bg_home_card_num"
+                        android:backgroundTint="?attr/colorHomeBlockOngoing"
+                        android:divider="@drawable/common_divider_small_space_horizontal"
+                        android:gravity="center_vertical"
+                        android:orientation="horizontal"
+                        android:showDividers="middle">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginHorizontal="@dimen/iscs_space_2"
+                            android:gravity="center"
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_md"
+                            app:i18nKey='@{"total_material"}'
+                            tools:text="进行中\n的作业" />
+
+                        <TextView
+                            android:id="@+id/total_material_num"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_margin="@dimen/iscs_space_2"
+                            android:gravity="center"
+                            android:padding="@dimen/iscs_space_2"
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_h3"
+                            android:textStyle="bold"
+                            tools:text="80" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:background="@drawable/bg_home_card_num"
+                        android:backgroundTint="?attr/colorHomeBlockLocked"
+                        android:divider="@drawable/common_divider_small_space_horizontal"
+                        android:gravity="center_vertical"
+                        android:orientation="horizontal"
+                        android:showDividers="middle">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginHorizontal="@dimen/iscs_space_2"
+                            android:gravity="center"
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_md"
+                            app:i18nKey='@{"borrow_material"}' />
+
+                        <TextView
+                            android:id="@+id/borrow_material_num"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_margin="@dimen/iscs_space_2"
+                            android:gravity="center"
+                            android:padding="@dimen/iscs_space_2"
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_h3"
+                            android:textStyle="bold" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:background="@drawable/bg_home_card_num"
+                        android:backgroundTint="?attr/colorHomeBlockUseHardware"
+                        android:divider="@drawable/common_divider_small_space_horizontal"
+                        android:gravity="center_vertical"
+                        android:orientation="horizontal"
+                        android:showDividers="middle">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginHorizontal="@dimen/iscs_space_2"
+                            android:gravity="center"
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_md"
+                            app:i18nKey='@{"unborrowed_material"}' />
+
+                        <TextView
+                            android:id="@+id/unborrowed_material_num"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_margin="@dimen/iscs_space_2"
+                            android:gravity="center"
+                            android:padding="@dimen/iscs_space_2"
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_h3"
+                            android:textStyle="bold" />
+                    </LinearLayout>
+                </LinearLayout>
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingVertical="@dimen/iscs_space_1"
+                android:paddingLeft="@dimen/iscs_space_2">
+
+                <ImageView
+                    android:layout_width="@dimen/title_icon_size"
+                    android:layout_height="@dimen/title_icon_size"
+                    android:tint="?attr/colorPrimary"
+                    app:skinSrc='@{"icon_overview_data.png"}' />
+
+                <TextView
+                    android:id="@+id/borrowed_material_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/iscs_space_2"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"you_have_borrowed_materials"}' />
+            </LinearLayout>
+
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_card_bg">
+
+                <com.drake.statelayout.StateLayout
+                    android:id="@+id/state_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/borrowed_material_rv"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent" />
+                </com.drake.statelayout.StateLayout>
+            </RelativeLayout>
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 128 - 27
app/src/main/res/layout-land/fragment_login.xml

@@ -65,45 +65,146 @@
                 android:textSize="@dimen/iscs_text_xs"
                 app:i18nKey='@{"administrator_mode"}' />
 
-            <LinearLayout
+            <FrameLayout
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_marginTop="@dimen/iscs_space_2"
-                android:gravity="center"
-                android:orientation="vertical">
+                android:layout_height="match_parent">
 
-                <androidx.recyclerview.widget.RecyclerView
-                    android:id="@+id/login_type_rv"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    tools:listitem="@layout/item_login_method" />
+                <LinearLayout
+                    android:id="@+id/login_method_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:orientation="vertical">
+
+                    <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="@dimen/iscs_space_2" />
+
+                    <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="@dimen/iscs_space_2"
+                            android:layout_height="@dimen/iscs_space_2"
+                            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:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_md"
+                            app:i18nKey='@{"login_tip"}' />
+                    </LinearLayout>
+
+                </LinearLayout>
 
                 <LinearLayout
+                    android:id="@+id/login_layout"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_horizontal"
-                    android:layout_marginTop="@dimen/iscs_space_2"
-                    android:divider="@drawable/common_divider_normal_space_horizontal"
-                    android:gravity="center_vertical"
-                    android:orientation="horizontal"
-                    android:showDividers="middle">
-
-                    <View
-                        android:layout_width="@dimen/iscs_space_2"
-                        android:layout_height="@dimen/iscs_space_2"
-                        android:background="@drawable/login_tip_circle" />
+                    android:gravity="center_horizontal"
+                    android:orientation="vertical"
+                    android:padding="@dimen/iscs_space_2"
+                    android:visibility="gone">
 
                     <TextView
-                        android:id="@+id/login_tip_tv"
-                        android:layout_width="match_parent"
+                        android:id="@+id/login_tip"
+                        android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:layout_alignParentBottom="true"
-                        android:gravity="center_horizontal"
+                        android:layout_marginBottom="@dimen/iscs_space_2"
+                        android:drawableLeft="@mipmap/tip"
+                        android:drawablePadding="@dimen/iscs_space_2"
                         android:textColor="?attr/colorTextPrimary"
-                        android:textSize="@dimen/iscs_text_md"
-                        app:i18nKey='@{"login_tip"}' />
+                        android:textSize="@dimen/iscs_text_md" />
+
+                    <LinearLayout
+                        android:id="@+id/login_account"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:orientation="vertical">
+
+                        <EditText
+                            android:id="@+id/et_account"
+                            style="@style/CommonEdit"
+                            android:layout_width="match_parent"
+                            android:layout_height="@dimen/login_dialog_input_height"
+                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:minWidth="@dimen/iscs_input_min_width"
+                            android:textSize="@dimen/iscs_text_sm"
+                            app:i18nHint='@{"please_input_account"}' />
+
+                        <EditText
+                            android:id="@+id/et_password"
+                            style="@style/CommonEdit"
+                            android:layout_width="match_parent"
+                            android:layout_height="@dimen/login_dialog_input_height"
+                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:inputType="textPassword"
+                            android:minWidth="@dimen/iscs_input_min_width"
+                            android:textSize="@dimen/iscs_text_sm"
+                            app:i18nHint='@{"please_input_password"}' />
+
+                        <TextView
+                            android:id="@+id/tv_login"
+                            style="@style/CommonBtn"
+                            android:layout_width="match_parent"
+                            android:layout_height="@dimen/login_dialog_btn_height"
+                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:textSize="@dimen/iscs_text_sm"
+                            app:i18nKey='@{"login"}' />
+                    </LinearLayout>
+
+                    <RelativeLayout
+                        android:id="@+id/login_face"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:visibility="gone">
+
+                        <FrameLayout
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content">
+
+                            <TextureView
+                                android:id="@+id/preview"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:visibility="invisible" />
+                        </FrameLayout>
+
+                        <ImageView
+                            android:id="@+id/iv_icon"
+                            android:layout_width="@dimen/dialog_common_root_height_login_and_check"
+                            android:layout_height="@dimen/dialog_common_root_height_login_and_check"
+                            android:layout_centerHorizontal="true"
+                            android:layout_gravity="center_horizontal"
+                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:tint="?attr/colorPrimary" />
+                    </RelativeLayout>
+
+                    <TextView
+                        android:id="@+id/tv_cancel"
+                        style="@style/CommonBtn"
+                        android:layout_width="@dimen/iscs_input_min_width"
+                        android:layout_height="@dimen/login_dialog_btn_height"
+                        android:background="@drawable/white_stroke_bg"
+                        android:textSize="@dimen/iscs_text_sm"
+                        app:i18nKey='@{"back"}' />
                 </LinearLayout>
-            </LinearLayout>
+            </FrameLayout>
         </LinearLayout>
 
         <TextView

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

@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/iscs_space_2"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2">
+
+            <ImageView
+                android:layout_width="@dimen/title_icon_size"
+                android:layout_height="@dimen/title_icon_size"
+                android:tint="?attr/colorPrimary"
+                app:skinSrc='@{"face-id-svgrepo-com.svg"}' />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_weight="1"
+                app:i18nKey='@{"set_face_title"}'
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="5dp"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:drawableLeft="@mipmap/icon_back"
+                android:drawableTint="?attr/colorPrimary"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                app:i18nKey='@{"back"}'
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorBlack" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <LinearLayout
+                android:id="@+id/face_view_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/face_not_set_iv"
+                    android:layout_width="@dimen/login_method_item_iv_size"
+                    android:layout_height="@dimen/login_method_item_iv_size"
+                    android:tint="?attr/colorPrimary"
+                    android:src="@drawable/icon_add_box" />
+
+                <ImageView
+                    android:id="@+id/face_set_iv"
+                    android:layout_width="400dp"
+                    android:layout_height="300dp"
+                    android:scaleType="fitXY"
+                    android:tint="?attr/colorPrimary"
+                    android:src="@drawable/icon_add_box"
+                    android:visibility="gone" />
+
+                <TextView
+                    android:id="@+id/face_set_tip_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/iscs_space_4"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    tools:text="您尚未设置人脸数据" />
+
+                <TextView
+                    android:id="@+id/set_or_reset_face"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/iscs_space_4"
+                    android:background="@drawable/common_btn_secondary"
+                    android:paddingHorizontal="@dimen/iscs_space_4"
+                    android:paddingVertical="@dimen/iscs_space_2"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    tools:text="点击设置" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/face_set_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:visibility="gone">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:orientation="horizontal">
+
+                    <FrameLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_marginHorizontal="@dimen/iscs_space_5"
+                        android:layout_marginTop="@dimen/iscs_space_5"
+                        android:layout_weight="1"
+                        android:background="@drawable/common_card_bg">
+
+                        <FrameLayout
+                            android:id="@+id/preview_layout"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:visibility="visible">
+
+                            <TextureView
+                                android:id="@+id/preview"
+                                android:layout_width="match_parent"
+                                android:layout_height="match_parent" />
+
+
+                            <com.grkj.shared.widget.FaceOverlayView
+                                android:id="@+id/face_overlay_view"
+                                android:layout_width="match_parent"
+                                android:layout_height="match_parent" />
+
+                            <FrameLayout
+                                android:layout_width="match_parent"
+                                android:layout_height="wrap_content"
+                                android:layout_gravity="bottom"
+                                android:background="?attr/colorTransparentHalf">
+
+                                <TextView
+                                    android:id="@+id/count_down_tip"
+                                    android:layout_width="wrap_content"
+                                    android:layout_height="wrap_content"
+                                    android:layout_gravity="center"
+                                    android:paddingVertical="@dimen/iscs_space_2"
+                                    android:gravity="center"
+                                    app:i18nKey='@{"detect_face_tip"}'
+                                    android:textColor="@color/dialogxColorBlue"
+                                    android:textSize="@dimen/iscs_text_xl"
+                                    android:textStyle="bold"
+                                    android:visibility="gone"
+                                    tools:text="检测到人脸,即将拍摄" />
+
+                                <TextView
+                                    android:id="@+id/tip_tv"
+                                    android:layout_width="match_parent"
+                                    android:layout_height="wrap_content"
+                                    android:layout_gravity="center|bottom"
+                                    android:paddingVertical="10dp"
+                                    android:gravity="center"
+                                    app:i18nKey='@{"only_one_person_allowed"}'
+                                    android:textColor="?attr/colorStatusRed"
+                                    android:textSize="@dimen/iscs_text_xl"
+                                    android:visibility="gone"
+                                    tools:text="请保证画面中只有自己" />
+                            </FrameLayout>
+
+                        </FrameLayout>
+
+                        <ImageView
+                            android:id="@+id/image"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:scaleType="centerCrop" />
+
+                    </FrameLayout>
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_marginHorizontal="@dimen/iscs_space_5"
+                        android:layout_marginTop="@dimen/iscs_space_4"
+                        android:layout_weight="1"
+                        android:background="@drawable/common_card_bg"
+                        android:orientation="vertical"
+                        android:padding="@dimen/iscs_space_1">
+
+                        <LinearLayout
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:gravity="center_vertical"
+                            android:orientation="horizontal">
+
+                            <ImageView
+                                android:layout_width="@dimen/iscs_icon_size_sm"
+                                android:layout_height="@dimen/iscs_icon_size_sm"
+                                android:background="@mipmap/tip" />
+
+                            <TextView
+                                style="@style/CommonTextView"
+                                android:layout_marginLeft="@dimen/iscs_space_1"
+                                app:i18nKey='@{"capture_tip_title"}'
+                                android:textColor="?attr/colorTextPrimary"
+                                android:textSize="@dimen/iscs_text_sm" />
+                        </LinearLayout>
+
+                        <TextView
+                            style="@style/CommonTextView"
+                            android:layout_marginTop="@dimen/iscs_space_2"
+                            android:gravity="left"
+                            app:i18nKey='@{"capture_tip_content"}'
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_sm" />
+
+                    </LinearLayout>
+                </LinearLayout>
+
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingVertical="@dimen/iscs_space_4">
+
+                    <TextView
+                        android:id="@+id/confirm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_confirm"
+                        android:drawableLeft="@mipmap/icon_confirm"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:drawableTint="?attr/colorWhite"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:paddingVertical="@dimen/iscs_space_2"
+                        app:i18nKey='@{"confirm"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/recapture"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent" />
+
+                    <TextView
+                        android:id="@+id/recapture"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:backgroundTint="@color/dialogxColorBlue"
+                        android:drawableLeft="@drawable/icon_camera"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:drawableTint="?attr/colorWhite"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:paddingVertical="@dimen/iscs_space_2"
+                        app:i18nKey='@{"recapture"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/cancel"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/confirm"
+                        app:layout_constraintTop_toTopOf="@id/confirm" />
+
+                    <TextView
+                        android:id="@+id/cancel"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:drawableLeft="@drawable/icon_close"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:drawableTint="?attr/colorWhite"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:paddingVertical="@dimen/iscs_space_2"
+                        app:i18nKey='@{"cancel"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/recapture"
+                        app:layout_constraintTop_toTopOf="@id/recapture" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
+
+            </LinearLayout>
+        </FrameLayout>
+    </LinearLayout>
+</layout>

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

@@ -0,0 +1,425 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/iscs_space_2"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2">
+
+            <ImageView
+                android:layout_width="@dimen/title_icon_size"
+                android:layout_height="@dimen/title_icon_size"
+                android:tint="?attr/colorPrimary"
+                app:skinSrc='@{"chalkboard-user.svg"}' />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_weight="1"
+                app:i18nKey='@{"user_info_title"}'
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="5dp"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:drawableLeft="@mipmap/icon_back"
+                android:drawableTint="?attr/colorPrimary"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                app:i18nKey='@{"back"}'
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorBlack" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <LinearLayout
+                android:id="@+id/show_user_info_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
+
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/iscs_space_4"
+                    android:gravity="center_vertical"
+                    android:orientation="horizontal"
+                    android:paddingHorizontal="100dp">
+
+
+                    <ImageView
+                        android:id="@+id/avatar"
+                        android:layout_width="@dimen/avatar_user_info_size"
+                        android:layout_height="@dimen/avatar_user_info_size"
+                        android:layout_marginTop="@dimen/iscs_space_2"
+                        android:background="@drawable/oval_shape"
+                        android:clipToOutline="true"
+                        android:scaleType="centerCrop"
+                        android:src="@drawable/icon_avatar"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent" />
+
+                    <ImageView
+                        android:id="@+id/delete_avatar"
+                        android:layout_width="@dimen/iscs_icon_size_md"
+                        android:layout_height="@dimen/iscs_icon_size_md"
+                        android:layout_marginTop="@dimen/iscs_space_2"
+                        android:tint="?attr/colorPrimary"
+                        app:layout_constraintEnd_toEndOf="@+id/avatar"
+                        app:layout_constraintStart_toStartOf="@+id/avatar"
+                        app:layout_constraintTop_toBottomOf="@+id/avatar"
+                        app:skinSrc='@{"icon_delete_circle.png"}' />
+                    <TextView
+                        android:id="@+id/username_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_2"
+                        app:i18nKey='@{"user_name"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toBottomOf="@+id/delete_avatar" />
+
+                    <TextView
+                        android:id="@+id/username"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_common_input"
+                        android:enabled="false"
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/username_tv"
+                        app:layout_constraintTop_toTopOf="@+id/username_tv" />
+
+                    <TextView
+                        android:id="@+id/nickname_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_2"
+                        app:i18nKey='@{"nickname"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintEnd_toEndOf="@+id/username_tv"
+                        app:layout_constraintTop_toBottomOf="@+id/username_tv" />
+
+                    <EditText
+                        android:id="@+id/nickname_et"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_common_input"
+                        app:i18nHint='@{"please_input_nickname"}'
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/nickname_tv"
+                        app:layout_constraintTop_toTopOf="@+id/nickname_tv" />
+
+
+                    <TextView
+                        android:id="@+id/phone_tv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_2"
+                        app:i18nKey='@{"phone"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintEnd_toEndOf="@+id/nickname_tv"
+                        app:layout_constraintTop_toBottomOf="@+id/nickname_tv" />
+
+                    <EditText
+                        android:id="@+id/phone_et"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/bg_common_input"
+                        app:i18nHint='@{"please_input_phone"}'
+                        android:maxLines="1"
+                        android:paddingHorizontal="@dimen/iscs_space_2"
+                        android:paddingVertical="2dp"
+                        android:singleLine="true"
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/phone_tv"
+                        app:layout_constraintTop_toTopOf="@+id/phone_tv" />
+
+                </androidx.constraintlayout.widget.ConstraintLayout>
+
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:layout_weight="1" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                    android:padding="@dimen/iscs_space_2">
+
+                    <View
+                        android:layout_width="0dp"
+                        android:layout_height="@dimen/divider_line_space"
+                        android:layout_weight="1" />
+
+                    <TextView
+                        android:id="@+id/confirm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/common_btn_confirm"
+                        android:drawableLeft="@mipmap/icon_confirm"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        app:i18nKey='@{"confirm"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md" />
+
+                    <TextView
+                        android:id="@+id/cancel"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="@dimen/iscs_space_2"
+                        android:background="@drawable/common_btn_cancel"
+                        android:drawableLeft="@mipmap/icon_cancel"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:gravity="center"
+                        android:minHeight="@dimen/common_btn_height"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        app:i18nKey='@{"cancel"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md" />
+                </LinearLayout>
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/face_set_layout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:visibility="gone">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:orientation="horizontal">
+
+                    <FrameLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_marginHorizontal="@dimen/iscs_space_5"
+                        android:layout_marginTop="@dimen/iscs_space_5"
+                        android:layout_weight="1"
+                        android:background="@drawable/common_card_bg">
+
+                        <FrameLayout
+                            android:id="@+id/preview_layout"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:visibility="visible">
+
+                            <TextureView
+                                android:id="@+id/preview"
+                                android:layout_width="match_parent"
+                                android:layout_height="match_parent" />
+
+
+                            <com.grkj.shared.widget.FaceOverlayView
+                                android:id="@+id/face_overlay_view"
+                                android:layout_width="match_parent"
+                                android:layout_height="match_parent" />
+
+                            <FrameLayout
+                                android:layout_width="match_parent"
+                                android:layout_height="wrap_content"
+                                android:layout_gravity="bottom"
+                                android:background="?attr/colorTransparentHalf">
+
+                                <TextView
+                                    android:id="@+id/count_down_tip"
+                                    android:layout_width="wrap_content"
+                                    android:layout_height="wrap_content"
+                                    android:layout_gravity="center"
+                                    android:gravity="center"
+                                    android:paddingVertical="@dimen/iscs_space_2"
+                                    app:i18nKey='@{"detect_face_tip"}'
+                                    android:textColor="@color/dialogxColorBlue"
+                                    android:textSize="@dimen/iscs_text_xl"
+                                    android:textStyle="bold"
+                                    android:visibility="gone"
+                                    tools:text="检测到人脸,即将拍摄" />
+
+                                <TextView
+                                    android:id="@+id/tip_tv"
+                                    android:layout_width="match_parent"
+                                    android:layout_height="wrap_content"
+                                    android:layout_gravity="center|bottom"
+                                    android:gravity="center"
+                                    android:paddingVertical="10dp"
+                                    app:i18nKey='@{"only_one_person_allowed"}'
+                                    android:textColor="?attr/colorStatusRed"
+                                    android:textSize="@dimen/iscs_text_xl"
+                                    android:visibility="gone"
+                                    tools:text="请保证画面中只有自己" />
+                            </FrameLayout>
+
+                        </FrameLayout>
+
+                        <ImageView
+                            android:id="@+id/image"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:scaleType="centerCrop" />
+
+                    </FrameLayout>
+
+                    <LinearLayout
+                        android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_marginHorizontal="@dimen/iscs_space_5"
+                        android:layout_marginTop="@dimen/iscs_space_4"
+                        android:layout_weight="1"
+                        android:background="@drawable/common_card_bg"
+                        android:orientation="vertical"
+                        android:padding="@dimen/iscs_space_1">
+
+                        <LinearLayout
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:gravity="center_vertical"
+                            android:orientation="horizontal">
+
+                            <ImageView
+                                android:layout_width="@dimen/iscs_icon_size_sm"
+                                android:layout_height="@dimen/iscs_icon_size_sm"
+                                android:background="@mipmap/tip" />
+
+                            <TextView
+                                style="@style/CommonTextView"
+                                android:layout_marginLeft="@dimen/iscs_space_1"
+                                app:i18nKey='@{"capture_tip_title"}'
+                                android:textColor="?attr/colorTextPrimary"
+                                android:textSize="@dimen/iscs_text_sm" />
+                        </LinearLayout>
+
+                        <TextView
+                            style="@style/CommonTextView"
+                            android:layout_marginTop="@dimen/iscs_space_2"
+                            android:gravity="left"
+                            app:i18nKey='@{"capture_tip_content"}'
+                            android:textColor="?attr/colorTextPrimary"
+                            android:textSize="@dimen/iscs_text_sm" />
+
+                    </LinearLayout>
+                </LinearLayout>
+
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingVertical="@dimen/iscs_space_4">
+
+                    <TextView
+                        android:id="@+id/set_avatar_confirm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_confirm"
+                        android:drawableLeft="@mipmap/icon_confirm"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:drawableTint="?attr/colorWhite"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:paddingVertical="@dimen/iscs_space_2"
+                        app:i18nKey='@{"confirm"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/recapture"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent" />
+
+                    <TextView
+                        android:id="@+id/recapture"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:backgroundTint="@color/dialogxColorBlue"
+                        android:drawableLeft="@drawable/icon_camera"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:drawableTint="?attr/colorWhite"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:paddingVertical="@dimen/iscs_space_2"
+                        app:i18nKey='@{"recapture"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        android:visibility="gone"
+                        app:layout_constraintEnd_toStartOf="@+id/set_avatar_cancel"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/set_avatar_confirm"
+                        app:layout_constraintTop_toTopOf="@id/set_avatar_confirm" />
+
+                    <TextView
+                        android:id="@+id/set_avatar_cancel"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:background="@drawable/common_btn_cancel"
+                        android:drawableLeft="@drawable/icon_close"
+                        android:drawablePadding="@dimen/iscs_space_2"
+                        android:drawableTint="?attr/colorWhite"
+                        android:paddingHorizontal="@dimen/iscs_space_4"
+                        android:paddingVertical="@dimen/iscs_space_2"
+                        app:i18nKey='@{"cancel"}'
+                        android:textColor="?attr/colorTextPrimary"
+                        android:textSize="@dimen/iscs_text_md"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintHorizontal_bias="0.5"
+                        app:layout_constraintStart_toEndOf="@+id/recapture"
+                        app:layout_constraintTop_toTopOf="@id/recapture" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
+
+            </LinearLayout>
+        </FrameLayout>
+    </LinearLayout>
+</layout>

+ 37 - 0
app/src/main/res/layout-land/item_home_menu.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:orientation="vertical"
+        android:paddingHorizontal="@dimen/iscs_space_4"
+        android:paddingVertical="@dimen/iscs_space_2">
+
+        <cn.bingoogolapple.badgeview.BGABadgeFrameLayout
+            android:id="@+id/home_menu_layout"
+            android:layout_width="@dimen/home_menu_item_iv_layout_size"
+            android:layout_height="@dimen/home_menu_item_iv_layout_size"
+            android:background="@drawable/bg_card_color_menu_bg_radius_md"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+
+            <ImageView
+                android:id="@+id/home_menu_iv"
+                android:layout_width="@dimen/home_menu_item_iv_size"
+                android:layout_height="@dimen/home_menu_item_iv_size"
+                android:layout_gravity="center"
+                android:scaleType="fitCenter"
+                android:tint="?attr/colorHomeMenuIVTint" />
+        </cn.bingoogolapple.badgeview.BGABadgeFrameLayout>
+
+        <TextView
+            android:id="@+id/home_menu_tv"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_1"
+            android:textColor="?attr/colorHomeMenuTextColor"
+            android:textSize="@dimen/iscs_text_sm" />
+    </LinearLayout>
+</layout>

+ 36 - 0
app/src/main/res/layout-land/item_home_quick_entrance.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:orientation="vertical"
+        android:background="?attr/selectableItemBackground"
+        android:paddingHorizontal="@dimen/iscs_space_4">
+
+        <cn.bingoogolapple.badgeview.BGABadgeFrameLayout
+            android:id="@+id/quick_entrance_layout"
+            android:layout_width="@dimen/home_item_quick_entrance_iv_layout"
+            android:layout_height="@dimen/home_item_quick_entrance_iv_layout"
+            android:background="@drawable/bg_card_color_menu_bg_radius_md">
+
+            <ImageView
+                android:id="@+id/home_menu_iv"
+                android:layout_width="@dimen/home_item_quick_entrance_iv"
+                android:layout_height="@dimen/home_item_quick_entrance_iv"
+                android:tint="?attr/colorPrimary"
+                android:layout_gravity="center" />
+        </cn.bingoogolapple.badgeview.BGABadgeFrameLayout>
+
+        <TextView
+            android:id="@+id/home_menu_tv"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_1"
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_xs"
+            tools:text="创建作业" />
+    </LinearLayout>
+</layout>

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

@@ -12,7 +12,7 @@
             android:layout_width="@dimen/login_circle_view_size"
             android:layout_height="@dimen/login_circle_view_size"
             android:layout_alignParentRight="true"
-            android:layout_marginTop="@dimen/iscs_space_2"
+            android:layout_marginTop="@dimen/iscs_space_4"
             android:layout_marginRight="@dimen/iscs_space_2"
             android:background="@drawable/login_tip_circle"
             android:visibility="gone" />

+ 55 - 0
app/src/main/res/layout-land/item_quick_entrance_config.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/iscs_space_2"
+        android:background="?attr/selectableItemBackground"
+        android:gravity="center_horizontal"
+        android:orientation="vertical"
+        android:paddingHorizontal="@dimen/iscs_space_4">
+
+        <FrameLayout
+            android:layout_width="@dimen/home_item_quick_entrance_iv_layout"
+            android:layout_height="@dimen/home_item_quick_entrance_iv_layout"
+            android:background="@drawable/bg_card_color_menu_bg_radius_md">
+
+            <ImageView
+                android:id="@+id/home_menu_iv"
+                android:layout_width="@dimen/home_item_quick_entrance_iv"
+                android:layout_height="@dimen/home_item_quick_entrance_iv"
+                android:tint="?attr/colorPrimary"
+                android:layout_gravity="center" />
+
+            <ImageView
+                android:id="@+id/add"
+                android:layout_width="@dimen/common_badge_icon_size"
+                android:layout_height="@dimen/common_badge_icon_size"
+                android:layout_gravity="right|top"
+                android:src="@drawable/icon_add"
+                android:tint="?attr/colorStatusGreen"
+                android:visibility="gone" />
+
+            <ImageView
+                android:id="@+id/remove"
+                android:layout_width="@dimen/common_badge_icon_size"
+                android:layout_height="@dimen/common_badge_icon_size"
+                android:layout_gravity="right|top"
+                android:src="@drawable/icon_remove"
+                android:tint="?attr/colorStatusRed"
+                android:visibility="gone" />
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/home_menu_tv"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_1"
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_xs"
+            tools:text="创建作业" />
+    </LinearLayout>
+</layout>

+ 54 - 0
app/src/main/res/layout-land/item_quick_entrance_not_config.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/iscs_space_2"
+        android:gravity="center_horizontal"
+        android:paddingHorizontal="@dimen/iscs_space_4"
+        android:orientation="vertical">
+
+        <FrameLayout
+            android:layout_width="@dimen/home_item_quick_entrance_iv_layout"
+            android:layout_height="@dimen/home_item_quick_entrance_iv_layout"
+            android:background="@drawable/bg_card_color_menu_bg_radius_md">
+
+            <ImageView
+                android:id="@+id/home_menu_iv"
+                android:layout_width="@dimen/home_item_quick_entrance_iv"
+                android:layout_height="@dimen/home_item_quick_entrance_iv"
+                android:tint="?attr/colorPrimary"
+                android:layout_gravity="center" />
+
+            <ImageView
+                android:id="@+id/add"
+                android:layout_width="@dimen/common_badge_icon_size"
+                android:layout_height="@dimen/common_badge_icon_size"
+                android:layout_gravity="right|top"
+                android:src="@drawable/icon_add"
+                android:tint="?attr/colorStatusGreen"
+                android:visibility="gone" />
+
+            <ImageView
+                android:id="@+id/remove"
+                android:layout_width="@dimen/common_badge_icon_size"
+                android:layout_height="@dimen/common_badge_icon_size"
+                android:layout_gravity="right|top"
+                android:src="@drawable/icon_remove"
+                android:tint="?attr/colorStatusRed"
+                android:visibility="gone" />
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/home_menu_tv"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_1"
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_xs"
+            tools:text="创建作业" />
+    </LinearLayout>
+</layout>

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

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

+ 0 - 9
app/src/main/res/layout/activity_material_exchange.xml

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

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

@@ -0,0 +1,46 @@
+<?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">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="@dimen/iscs_space_2"
+        android:layout_marginHorizontal="@dimen/iscs_space_5"
+        android:background="@drawable/common_card_bg"
+        android:gravity="center"
+        android:orientation="vertical"
+        android:paddingVertical="@dimen/iscs_space_4">
+
+        <ImageView
+            android:layout_width="@dimen/login_method_item_layout_width"
+            android:layout_height="@dimen/login_method_item_layout_width"
+            android:layout_gravity="center"
+            app:skinSrc='@{"fingerprint.svg"}'
+            android:tint="?attr/colorPrimary" />
+
+        <TextView
+            android:id="@+id/press_tip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_2"
+            app:i18nKey='@{"fingerprint_scan_tip"}'
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_md" />
+
+        <TextView
+            android:id="@+id/cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_4"
+            android:background="@drawable/common_btn_cancel"
+            android:drawableLeft="@drawable/icon_close"
+            android:drawablePadding="@dimen/iscs_space_2"
+            android:drawableTint="?attr/colorWhite"
+            android:paddingHorizontal="@dimen/iscs_space_4"
+            android:paddingVertical="@dimen/iscs_space_2"
+            app:i18nKey='@{"cancel"}'
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_md" />
+    </LinearLayout>
+</layout>

+ 233 - 0
app/src/main/res/layout/dialog_add_role.xml

@@ -0,0 +1,233 @@
+<?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">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/dialog_common_root_height_large"
+        android:layout_marginHorizontal="@dimen/iscs_space_5"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2"
+            android:paddingVertical="@dimen/iscs_title_normal_padding_vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"role_manage_add_title"}' />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:src="@drawable/icon_close"
+                android:tint="?attr/colorPrimary" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorDivider" />
+
+        <com.grkj.ui_base.widget.FormLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:minWidth="0dp"
+            android:clipToPadding="false"
+            android:orientation="vertical"
+            android:padding="@dimen/dialog_content_normal_padding_horizontal"
+            app:columnSpacing="@dimen/iscs_space_2"
+            app:rowSpacing="@dimen/iscs_space_2">
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/role_name_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"role_manage_role_name"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <EditText
+                android:id="@+id/role_name_et"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:minWidth="@dimen/iscs_input_min_width"
+                android:layout_marginRight="@dimen/dialog_content_normal_padding_horizontal"
+                android:background="@drawable/bg_common_input"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="@dimen/iscs_input_padding_vertical"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="field"
+                app:i18nHint='@{"please_input_role_name"}' />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/role_key_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"role_manage_permission_string"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <EditText
+                android:id="@+id/role_key_et"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_marginRight="@dimen/dialog_content_normal_padding_horizontal"
+                android:background="@drawable/bg_common_input"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="@dimen/iscs_input_padding_vertical"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="field"
+                app:i18nHint='@{"please_input_permission_characters"}' />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"manage_filter_status"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:formRole="field">
+
+                <com.google.android.material.radiobutton.MaterialRadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/iscs_space_2"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"user_manage_filter_activate"}'
+                    app:useMaterialThemeColors="true" />
+
+                <com.google.android.material.radiobutton.MaterialRadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/iscs_space_2"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"user_manage_filter_deactivate"}'
+                    app:useMaterialThemeColors="true" />
+            </RadioGroup>
+
+            <TextView
+                android:id="@+id/function_permission_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"manage_role_function_permission"}' />
+
+            <LinearLayout
+                android:id="@+id/function_permission_operation_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:orientation="horizontal"
+                app:formRole="field">
+
+                <com.google.android.material.checkbox.MaterialCheckBox
+                    android:id="@+id/expand_collapse"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"expand_collapse"}' />
+
+                <com.google.android.material.checkbox.MaterialCheckBox
+                    android:id="@+id/all_selected"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/iscs_space_2"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"all_select_not_all_select"}' />
+            </LinearLayout>
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/role_list_rv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_marginRight="@dimen/dialog_content_normal_padding_horizontal"
+                android:background="@drawable/common_card_bg"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:layout_marginBottom="@dimen/iscs_space_2"/>
+        </com.grkj.ui_base.widget.FormLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:layout_gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/iscs_space_2">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_confirm"
+                android:drawableLeft="@mipmap/icon_confirm"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"confirm"}' />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_cancel"
+                android:drawableLeft="@mipmap/icon_cancel"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"cancel"}' />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 213 - 0
app/src/main/res/layout/dialog_add_user.xml

@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/dialog_common_root_height_large"
+        android:layout_marginHorizontal="@dimen/iscs_space_5"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2"
+            android:paddingVertical="@dimen/iscs_space_1">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"user_manage_new_user_title"}' />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:src="@drawable/icon_close"
+                android:tint="?attr/colorPrimary" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorBlack" />
+
+        <com.grkj.ui_base.widget.FormLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:minWidth="0dp"
+            android:orientation="vertical"
+            android:padding="@dimen/dialog_content_normal_padding_horizontal"
+            app:columnSpacing="@dimen/iscs_space_2"
+            app:rowSpacing="@dimen/iscs_space_2">
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/username_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"user_name"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <EditText
+                android:id="@+id/username_et"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:minWidth="@dimen/iscs_input_min_width"
+                android:background="@drawable/bg_common_input"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="field"
+                app:i18nHint='@{"please_input_username"}' />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/nickname_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"nickname"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <EditText
+                android:id="@+id/nickname_et"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/bg_common_input"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="field"
+                app:i18nHint='@{"please_input_nickname"}' />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/role_title_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"user_manage_role"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <TextView
+                android:id="@+id/role_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/bg_common_input"
+                android:drawableRight="@mipmap/icon_drop_down"
+                android:ellipsize="end"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="field"
+                app:i18nHint='@{"please_select_role"}' />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"manage_filter_status"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:orientation="horizontal"
+                app:formRole="field">
+
+                <com.google.android.material.radiobutton.MaterialRadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"user_manage_filter_activate"}'
+                    app:useMaterialThemeColors="true" />
+
+                <com.google.android.material.radiobutton.MaterialRadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/iscs_space_2"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"user_manage_filter_deactivate"}'
+                    app:useMaterialThemeColors="true" />
+            </RadioGroup>
+        </com.grkj.ui_base.widget.FormLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:layout_gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/iscs_space_2">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_confirm"
+                android:drawableLeft="@mipmap/icon_confirm"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"confirm"}' />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_cancel"
+                android:drawableLeft="@mipmap/icon_cancel"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"cancel"}' />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 30 - 0
app/src/main/res/layout/dialog_drop_down_list.xml

@@ -0,0 +1,30 @@
+<?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">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/bg_text_drop_down"
+        android:orientation="vertical">
+
+        <EditText
+            android:id="@+id/search_key"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/iscs_space_2"
+            android:background="@drawable/bg_common_input"
+            app:i18nHint='@{"please_input_key_word"}'
+            android:maxLines="1"
+            android:paddingHorizontal="@dimen/iscs_space_2"
+            android:singleLine="true"
+            android:textSize="@dimen/iscs_text_md"
+            android:visibility="gone" />
+
+        <com.grkj.ui_base.widget.MaxHeightRecyclerView
+            android:id="@+id/drop_down_rv"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:maxHeight="@dimen/login_method_item_layout_height" />
+    </LinearLayout>
+</layout>

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff