JaycePC hai 1 semana
achega
ad710df9a2
Modificáronse 100 ficheiros con 5137 adicións e 0 borrados
  1. 78 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 126 0
      app/build.gradle
  4. BIN=BIN
      app/libs/armeabi-v7a/libfacp.so
  5. BIN=BIN
      app/libs/armeabi-v7a/libota.so
  6. BIN=BIN
      app/libs/armeabi-v7a/libutil.so
  7. BIN=BIN
      app/libs/feasyblue.jar
  8. BIN=BIN
      app/libs/sdkapi.jar
  9. 21 0
      app/proguard-rules.pro
  10. 26 0
      app/src/androidTest/java/xn/hxp/ExampleInstrumentedTest.java
  11. 97 0
      app/src/main/AndroidManifest.xml
  12. 48 0
      app/src/main/java/xn/hxp/base/App.java
  13. 195 0
      app/src/main/java/xn/hxp/base/BaseActivity.java
  14. 76 0
      app/src/main/java/xn/hxp/base/BaseActivityHelp.java
  15. 67 0
      app/src/main/java/xn/hxp/base/BaseFragment.java
  16. 73 0
      app/src/main/java/xn/hxp/base/BaseFragmentHelp.java
  17. 17 0
      app/src/main/java/xn/hxp/event/UpdateTimeEvent.java
  18. 72 0
      app/src/main/java/xn/hxp/http/HttpData.java
  19. 32 0
      app/src/main/java/xn/hxp/http/HttpTool.java
  20. 200 0
      app/src/main/java/xn/hxp/http/RequestHandler.java
  21. 27 0
      app/src/main/java/xn/hxp/http/RequestServer.java
  22. 30 0
      app/src/main/java/xn/hxp/http/api/GetBaseConfigApi.java
  23. 30 0
      app/src/main/java/xn/hxp/http/api/GetCabinetListApi.java
  24. 37 0
      app/src/main/java/xn/hxp/http/api/GetCardLoginApi.java
  25. 44 0
      app/src/main/java/xn/hxp/http/api/GetReportApi.java
  26. 48 0
      app/src/main/java/xn/hxp/http/api/GetScanCodeApi.java
  27. 32 0
      app/src/main/java/xn/hxp/http/api/PostFaceByPicApi.java
  28. 27 0
      app/src/main/java/xn/hxp/http/exception/ResultException.java
  29. 14 0
      app/src/main/java/xn/hxp/http/exception/TokenException.java
  30. 27 0
      app/src/main/java/xn/hxp/receiver/MinuteTickReceiver.java
  31. 61 0
      app/src/main/java/xn/hxp/room/RoomTool.java
  32. 188 0
      app/src/main/java/xn/hxp/room/bean/BaseConfig.java
  33. 83 0
      app/src/main/java/xn/hxp/room/bean/DeviceInfo.java
  34. 64 0
      app/src/main/java/xn/hxp/room/bean/LabInfo.java
  35. 133 0
      app/src/main/java/xn/hxp/room/bean/UserInfo.java
  36. 255 0
      app/src/main/java/xn/hxp/room/bean/cabinet/Cabinet.java
  37. 158 0
      app/src/main/java/xn/hxp/room/bean/cabinet/CabinetDoor.java
  38. 57 0
      app/src/main/java/xn/hxp/room/bean/cabinet/CabinetDoorAdmin.java
  39. 74 0
      app/src/main/java/xn/hxp/room/bean/cabinet/CabinetDoorLock.java
  40. 29 0
      app/src/main/java/xn/hxp/room/dao/BaseConfigDao.java
  41. 28 0
      app/src/main/java/xn/hxp/room/dao/DeviceInfoDao.java
  42. 29 0
      app/src/main/java/xn/hxp/room/dao/LabInfoDao.java
  43. 29 0
      app/src/main/java/xn/hxp/room/dao/UserInfoDao.java
  44. 34 0
      app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDao.java
  45. 34 0
      app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDoorAdminDao.java
  46. 34 0
      app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDoorDao.java
  47. 34 0
      app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDoorLockDao.java
  48. 25 0
      app/src/main/java/xn/hxp/room/tool/BaseConfigTool.java
  49. 43 0
      app/src/main/java/xn/hxp/room/tool/DeviceInfoTool.java
  50. 23 0
      app/src/main/java/xn/hxp/room/tool/LabInfoTool.java
  51. 29 0
      app/src/main/java/xn/hxp/room/tool/UserInfoTool.java
  52. 29 0
      app/src/main/java/xn/hxp/room/tool/cabinet/CabinetDoorAdminTool.java
  53. 29 0
      app/src/main/java/xn/hxp/room/tool/cabinet/CabinetDoorLockTool.java
  54. 31 0
      app/src/main/java/xn/hxp/room/tool/cabinet/CabinetDoorTool.java
  55. 29 0
      app/src/main/java/xn/hxp/room/tool/cabinet/CabinetTool.java
  56. 127 0
      app/src/main/java/xn/hxp/ui/login/LoginActivity.java
  57. 182 0
      app/src/main/java/xn/hxp/ui/login/LoginActivityHelper.java
  58. 24 0
      app/src/main/java/xn/hxp/ui/login/callback/OnFaceScanResultCallback.java
  59. 73 0
      app/src/main/java/xn/hxp/ui/login/ext/BitmapExt.java
  60. 28 0
      app/src/main/java/xn/hxp/ui/login/fragment/LoginCardFragment.java
  61. 74 0
      app/src/main/java/xn/hxp/ui/login/fragment/LoginFaceFragment.java
  62. 73 0
      app/src/main/java/xn/hxp/ui/login/fragment/LoginQrFragment.java
  63. 68 0
      app/src/main/java/xn/hxp/ui/main/MainActivity.java
  64. 12 0
      app/src/main/java/xn/hxp/ui/main/MainActivityHelper.java
  65. 120 0
      app/src/main/java/xn/hxp/ui/main/MainActivityLHelper.java
  66. 54 0
      app/src/main/java/xn/hxp/ui/main/MainActivityRHelper.java
  67. 27 0
      app/src/main/java/xn/hxp/ui/main/MainActivityTitleHelper.java
  68. 123 0
      app/src/main/java/xn/hxp/ui/main/adapter/CabinetAdapter.java
  69. 39 0
      app/src/main/java/xn/hxp/ui/main/fragment/CabinetFragment.java
  70. 116 0
      app/src/main/java/xn/hxp/ui/start/SettingActivity.java
  71. 168 0
      app/src/main/java/xn/hxp/ui/start/StartActivity.java
  72. 93 0
      app/src/main/java/xn/hxp/util/AudioPlayerTool.java
  73. 79 0
      app/src/main/java/xn/hxp/util/CardReaderHelper.java
  74. 60 0
      app/src/main/java/xn/hxp/util/Tool.java
  75. 10 0
      app/src/main/java/xn/hxp/view/DialogTool.java
  76. 68 0
      app/src/main/java/xn/hxp/view/ble/BleScanAdapter.java
  77. 95 0
      app/src/main/java/xn/hxp/view/ble/BleSelectorDialog.java
  78. 225 0
      app/src/main/java/xn/hxp/view/ble/BluetoothWeighDialog.java
  79. 91 0
      app/src/main/java/xn/hxp/view/ble/SppTool.java
  80. 101 0
      app/src/main/java/xn/hxp/view/dialog/LoginVerifySuccessDialog.java
  81. BIN=BIN
      app/src/main/res/drawable-hdpi/app_logo.png
  82. BIN=BIN
      app/src/main/res/drawable-hdpi/bg.webp
  83. BIN=BIN
      app/src/main/res/drawable-hdpi/ble_mac_hint.png
  84. BIN=BIN
      app/src/main/res/drawable-hdpi/dialog_login_verify_success_address.webp
  85. BIN=BIN
      app/src/main/res/drawable-hdpi/dialog_login_verify_success_lab.webp
  86. BIN=BIN
      app/src/main/res/drawable-hdpi/dialog_login_verify_success_pic.webp
  87. BIN=BIN
      app/src/main/res/drawable-hdpi/dialog_login_verify_success_school.webp
  88. BIN=BIN
      app/src/main/res/drawable-hdpi/dialog_msg.png
  89. BIN=BIN
      app/src/main/res/drawable-hdpi/login_card_pic.webp
  90. BIN=BIN
      app/src/main/res/drawable-hdpi/login_face_pic.webp
  91. BIN=BIN
      app/src/main/res/drawable-hdpi/login_hint_pic.webp
  92. BIN=BIN
      app/src/main/res/drawable-hdpi/main_cabinet.png
  93. BIN=BIN
      app/src/main/res/drawable-hdpi/main_cabinet_next.webp
  94. BIN=BIN
      app/src/main/res/drawable-hdpi/main_claim_card_bg.webp
  95. BIN=BIN
      app/src/main/res/drawable-hdpi/main_discard_card_bg.webp
  96. BIN=BIN
      app/src/main/res/drawable-hdpi/main_inquire_card_bg.webp
  97. BIN=BIN
      app/src/main/res/drawable-hdpi/main_label_card_bg.webp
  98. BIN=BIN
      app/src/main/res/drawable-hdpi/main_return_card_bg.webp
  99. BIN=BIN
      app/src/main/res/drawable-hdpi/main_save_card_bg.webp
  100. 0 0
      app/src/main/res/drawable-hdpi/main_save_logo.webp

+ 78 - 0
.gitignore

@@ -0,0 +1,78 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+build/
+
+# Gradle files
+.gradle/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/libraries
+
+# Keystore files
+*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (gcm, firebase)
+google-services.json
+
+# Freeline
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+.svn/
+
+# lint reports
+lint-results*.xml
+lint-results*.html
+
+# IDEA
+*.iws
+*.ipr
+*.idea/
+
+# Mac OS
+.DS_Store
+
+# Windows thumbnail db
+Thumbs.db
+/app/release/*
+.cxx

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 126 - 0
app/build.gradle

@@ -0,0 +1,126 @@
+plugins {
+    alias(libs.plugins.android.application)
+    alias(libs.plugins.kotlin.android)
+    alias(libs.plugins.ksp)
+    alias(libs.plugins.room)
+    alias(libs.plugins.google.protobuf)
+}
+
+android {
+    namespace 'xn.hxp'
+    compileSdk 35
+
+    defaultConfig {
+        applicationId "xn.hxp"
+        minSdk 24
+        targetSdk 35
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        room {
+            schemaDirectory("$projectDir/schemas")
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_21
+        targetCompatibility JavaVersion.VERSION_21
+    }
+
+    kotlinOptions {
+        jvmTarget = '21'
+    }
+
+    viewBinding {
+        enabled = true
+    }
+
+    ksp {
+        arg("rxhttp_rxjava", libs.versions.rxjava)
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.aar'])
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation libs.androidx.core.ktx
+    implementation libs.appcompat
+    implementation libs.material
+    implementation libs.activity
+    implementation libs.constraintlayout
+    testImplementation libs.junit
+    androidTestImplementation libs.ext.junit
+    androidTestImplementation libs.espresso.core
+
+    // 异步加载布局
+    implementation libs.asynclayoutinflater
+    // 替换java集合 对内存有部分优化
+    implementation libs.collection
+    // 简化多线程和异步任务的管理
+    implementation libs.concurrent.futures
+    // 替代SharedPreferences更为强大
+    implementation libs.datastore.preferences
+    implementation libs.datastore.preferences.rxjava3
+    // Preferences DataStore(键值对)
+    implementation libs.datastore.preferences
+    // Proto DataStore(类型安全)
+    implementation libs.datastore
+    implementation libs.protobuf.javalite
+
+    // RxJava
+    implementation libs.rxjava
+    // RxAndroid
+    implementation libs.rxandroid
+
+    // 官方更推荐的数据库操作库
+    implementation libs.androidx.room.runtime
+    // 如果后面用到了KT请访问 https://kotlinlang.org/docs/ksp-quickstart.html 使用KSP  ksp "androidx.room:room-compiler:$room_version"
+    ksp libs.androidx.room.compiler
+    implementation libs.androidx.room.rxjava3
+    implementation libs.androidx.room.paging
+
+    // 实用的工具库
+    implementation libs.utilcodex
+
+    // 网络请求框架
+    // noinspection UseTomlInstead,GradleDependency
+    implementation 'com.github.getActivity:EasyHttp:13.0'
+    // noinspection UseTomlInstead,GradleDependency
+    implementation 'com.squareup.okhttp3:okhttp:3.12.13'
+    // gson
+    implementation libs.gson
+
+    implementation libs.lottie
+
+    // 人脸检测
+    implementation libs.mlkit.common
+    implementation libs.mlkit.face.detection
+    // zxing-lite
+    implementation libs.zxing.lite
+
+
+    // 弹框工具
+    implementation libs.dialogx
+
+    implementation libs.flexbox
+    // 权限申请
+    implementation libs.xxpermissions
+    // 吐司框架
+    implementation libs.toaster
+    // 图片加载
+    implementation libs.glide
+
+    implementation libs.eventbus
+
+    implementation libs.bannerviewpager
+
+
+}

BIN=BIN
app/libs/armeabi-v7a/libfacp.so


BIN=BIN
app/libs/armeabi-v7a/libota.so


BIN=BIN
app/libs/armeabi-v7a/libutil.so


BIN=BIN
app/libs/feasyblue.jar


BIN=BIN
app/libs/sdkapi.jar


+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
app/src/androidTest/java/xn/hxp/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package xn.hxp;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("xn.hxp", appContext.getPackageName());
+    }
+}

+ 97 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission
+        android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission
+        android:name="android.permission.INSTALL_PACKAGES"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission
+        android:name="android.permission.READ_LOGS"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.USES_POLICY_FORCE_LOCK" />
+    <uses-permission
+        android:name="android.permission.WRITE_SECURE_SETTINGS"
+        tools:ignore="ProtectedPermissions" />
+
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+    <uses-feature android:name="android.hardware.camera.any" /> <!-- 连接网络权限,用于执行云端语音能力 -->
+    <uses-permission android:name="android.permission.INTERNET" /> <!-- 获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
+    <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 读取网络信息状态 -->
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 获取当前wifi状态 -->
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 允许程序改变网络连接状态 -->
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <!-- 读取手机信息权限 -->
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 读取联系人权限,上传联系人需要用到此权限 -->
+    <uses-permission android:name="android.permission.READ_CONTACTS" /> <!-- 外存储写权限,构建语法需要用到此权限 -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 外存储读权限,构建语法需要用到此权限 -->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 配置权限,用来记录应用配置信息 -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission
+        android:name="android.permission.WRITE_SETTINGS"
+        tools:ignore="ProtectedPermissions" /> <!-- 手机定位信息,用来为语义等功能提供定位,提供更精准的服务 -->
+    <!-- 定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 -->
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
+    <uses-feature android:name="android.hardware.usb.host" />
+    <uses-feature android:name="android.hardware.usb.accessory" /> <!-- //android 6.0+以上需要进行动态权限申请 -->
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 安卓13 -->
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+    <uses-permission android:name="BLUETOOTH_CONNECT" />
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+    <uses-permission
+        android:name="android.permission.BATTERY_STATS"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission
+        android:name="android.permission.MANAGE_USB"
+        tools:ignore="ProtectedPermissions" /> <!-- 添加串口访问权限 -->
+    <uses-permission
+        android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application
+        android:name=".base.App"
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.化学品"
+        tools:targetApi="31">
+        <activity
+            android:name=".ui.login.LoginActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.main.MainActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.start.SettingActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.start.StartActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>

+ 48 - 0
app/src/main/java/xn/hxp/base/App.java

@@ -0,0 +1,48 @@
+package xn.hxp.base;
+
+import android.app.Application;
+
+import com.blankj.utilcode.util.Utils;
+import com.hjq.http.EasyConfig;
+import com.hjq.toast.Toaster;
+import com.kongzue.dialogx.DialogX;
+import com.kongzue.dialogx.util.TextInfo;
+
+import okhttp3.OkHttpClient;
+import xn.hxp.R;
+import xn.hxp.http.RequestHandler;
+import xn.hxp.http.RequestServer;
+import xn.hxp.room.tool.UserInfoTool;
+
+public class App extends Application {
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        DialogX.init(this);
+        DialogX.globalTheme = DialogX.THEME.DARK;
+        DialogX.implIMPLMode = DialogX.IMPL_MODE.WINDOW;
+        DialogX.cancelable = false;
+        DialogX.cancelableTipDialog = false;
+        DialogX.tipProgressColor = getColor(R.color.appColor);
+        DialogX.enterAnimDuration = 0;
+        DialogX.exitAnimDuration = 0;
+
+        TextInfo textInfo = new TextInfo();
+        textInfo.setFontColor(getColor(R.color.appColor));
+        textInfo.setFontSizeUnit(TextInfo.FONT_SIZE_UNIT.SP);
+        textInfo.setFontSize(20);
+        DialogX.tipTextInfo = textInfo;
+        DialogX.globalHoverWindow = true;
+
+        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
+        RequestServer requestServer = new RequestServer();
+        EasyConfig.with(okHttpClient).setLogEnabled(true).setServer(requestServer).setHandler(new RequestHandler(Utils.getApp())).into();
+
+        // 吐司框架
+        Toaster.init(this);
+
+        // 初始化登录状态
+        UserInfoTool.INSTANCE.deleteAll();
+    }
+}

+ 195 - 0
app/src/main/java/xn/hxp/base/BaseActivity.java

@@ -0,0 +1,195 @@
+package xn.hxp.base;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.activity.EdgeToEdge;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.viewbinding.ViewBinding;
+
+import com.blankj.utilcode.util.TimeUtils;
+import com.bumptech.glide.Glide;
+import com.hjq.http.config.IRequestApi;
+import com.hjq.http.listener.OnHttpListener;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import xn.hxp.R;
+import xn.hxp.databinding.IncludeTitleBinding;
+import xn.hxp.event.UpdateTimeEvent;
+import xn.hxp.http.HttpTool;
+import xn.hxp.room.bean.BaseConfig;
+import xn.hxp.room.tool.BaseConfigTool;
+
+public abstract class BaseActivity extends AppCompatActivity implements OnHttpListener<Object> {
+    private BaseActivityHelp baseActivityHelp;
+    private final AtomicBoolean isCountCdTime = new AtomicBoolean(false);
+    private int cdTime = 60;
+    private IncludeTitleBinding includeTitleBinding;
+    private BaseConfig baseConfig;
+
+    private final CountDownTimer countDownTimer = new CountDownTimer(1000, 1000) {
+        @Override
+        public void onTick(long millisUntilFinished) {
+
+        }
+
+        @Override
+        public void onFinish() {
+            if (!isFinishing() && !isDestroyed() && isCountCdTime.get()) {
+                cdTime(cdTime);
+                cdTime--;
+                if (cdTime <= 0) {
+                    cdFinish();
+                } else {
+                    countDownTimer.start();
+                }
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        baseActivityHelp = new BaseActivityHelp(this);
+        EdgeToEdge.enable(this);
+        ViewBinding binding = setViewBinding();
+        View main = binding.getRoot();
+        ViewGroup mainVG = (ViewGroup) binding.getRoot();
+        mainVG.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        View titleView = main.findViewById(R.id.includeTitle);
+        baseConfig = BaseConfigTool.INSTANCE.getBaseConfig();
+        if (null != titleView) {
+            includeTitleBinding = IncludeTitleBinding.bind(titleView);
+            // 学院图标
+            String circularLogo = baseConfig.getCircularLogo();
+            if (!TextUtils.isEmpty(circularLogo)) {
+                Glide.with(includeTitleBinding.titlePic).load(HttpTool.INSTANCE.checkUrl(circularLogo)).into(includeTitleBinding.titlePic);
+            }
+            // 学院名称
+            String deptName = baseConfig.getDeptName();
+            includeTitleBinding.titleName.setText(TextUtils.isEmpty(deptName) ? "" : deptName);
+            // 日期
+            TimeUtils.getNowString(new SimpleDateFormat("M月d日  EEEE  HH:mm", Locale.getDefault()));
+
+        }
+        setContentView(main);
+        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+            return insets;
+        });
+        onInit();
+        countDownTimer.start();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        isCountCdTime.set(true);
+        EventBus.getDefault().register(this);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        isCountCdTime.set(false);
+        EventBus.getDefault().unregister(this);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        isCountCdTime.set(false);
+    }
+
+    @Override
+    public void onUserInteraction() {
+        super.onUserInteraction();
+        if (null != baseConfig) {
+            cdTime = baseConfig.getBackTime();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        baseActivityHelp.onDestroy();
+        isCountCdTime.set(false);
+        countDownTimer.cancel();
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    public void updateTime(UpdateTimeEvent event) {
+        if (null != includeTitleBinding) {
+            String nowString = TimeUtils.getNowString(new SimpleDateFormat("M月d日  EEEE  HH:mm", Locale.getDefault()));
+            includeTitleBinding.timeDate.setText(TextUtils.isEmpty(event.getTime()) ? nowString : event.getTime());
+        }
+    }
+
+    protected abstract ViewBinding setViewBinding();
+
+    protected abstract void onInit();
+
+    protected abstract void cdFinish();
+
+    protected abstract void cdTime(int cd);
+
+    public void showLoading(String msg) {
+        baseActivityHelp.showLoading(msg);
+    }
+
+    public void dismissLoading() {
+        baseActivityHelp.dismissLoading();
+    }
+
+    public void showToast(String msg) {
+        baseActivityHelp.showToast(msg);
+    }
+
+    public void showSuccess(String msg) {
+        baseActivityHelp.showSuccess(msg);
+    }
+
+    public void showErr(String msg) {
+        baseActivityHelp.showErr(msg);
+    }
+
+    public void showErr(String msg, String hint) {
+        baseActivityHelp.showErr(msg, hint);
+    }
+
+    @Override
+    public void onHttpStart(@NonNull IRequestApi api) {
+        showLoading("请求中...");
+    }
+
+    @Override
+    public void onHttpSuccess(@NonNull Object result) {
+
+    }
+
+    @Override
+    public void onHttpFail(@NonNull Throwable throwable) {
+        showErr(throwable.getMessage());
+    }
+
+    @Override
+    public void onHttpEnd(@NonNull IRequestApi api) {
+        dismissLoading();
+    }
+}

+ 76 - 0
app/src/main/java/xn/hxp/base/BaseActivityHelp.java

@@ -0,0 +1,76 @@
+package xn.hxp.base;
+
+import com.blankj.utilcode.util.StringUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.hjq.toast.ToastParams;
+import com.hjq.toast.Toaster;
+import com.hjq.toast.style.CustomToastStyle;
+import com.kongzue.dialogx.DialogX;
+import com.kongzue.dialogx.dialogs.PopTip;
+import com.kongzue.dialogx.dialogs.WaitDialog;
+
+import java.util.concurrent.TimeUnit;
+
+import xn.hxp.R;
+
+public class BaseActivityHelp {
+    private BaseActivity baseActivity;
+    private ThreadUtils.SimpleTask<Object> cdDismissTask;
+    private ThreadUtils.SimpleTask<Object> cdErrDismissTask;
+
+    public BaseActivityHelp(BaseActivity baseActivity) {
+        this.baseActivity = baseActivity;
+    }
+
+    public void onDestroy() {
+        ThreadUtils.cancel(cdDismissTask);
+    }
+
+    public void showLoading(String msg) {
+        ThreadUtils.cancel(cdDismissTask);
+        if (StringUtils.isEmpty(msg)) {
+            msg = "请稍等...";
+        }
+        WaitDialog.show(msg).setCancelable(DialogX.cancelable);
+        ThreadUtils.executeByCachedWithDelay(cdDismissTask = new ThreadUtils.SimpleTask<Object>() {
+            @Override
+            public Object doInBackground() throws Throwable {
+                return null;
+            }
+
+            @Override
+            public void onSuccess(Object result) {
+                WaitDialog.dismiss();
+            }
+        }, 20, TimeUnit.SECONDS);
+    }
+
+    public void dismissLoading() {
+        ThreadUtils.cancel(cdDismissTask);
+        WaitDialog.dismiss();
+    }
+
+    public void showToast(String msg) {
+        Toaster.setView(R.layout.toast);
+        Toaster.show(msg);
+    }
+
+    public void showErr(String msg) {
+        Toaster.setView(R.layout.toast_err);
+        Toaster.show(msg);
+    }
+
+    public void showSuccess(String msg) {
+        Toaster.setView(R.layout.toast_success);
+        Toaster.show(msg);
+    }
+
+    public void showErr(String msg, String hint) {
+        PopTip.show(msg).iconError();
+        PopTip.show(hint);
+    }
+
+    public void showWarn() {
+
+    }
+}

+ 67 - 0
app/src/main/java/xn/hxp/base/BaseFragment.java

@@ -0,0 +1,67 @@
+package xn.hxp.base;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.viewbinding.ViewBinding;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+
+public abstract class BaseFragment extends Fragment {
+    private BaseFragmentHelp baseFragmentHelp;
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        return setViewBinding(inflater, container).getRoot();
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        baseFragmentHelp = new BaseFragmentHelp(this);
+        onInit();
+    }
+
+    protected abstract ViewBinding setViewBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container);
+
+    protected abstract void onInit();
+
+    protected void showLoading(String msg) {
+        baseFragmentHelp.showLoading(msg);
+    }
+
+    protected void dismissLoading() {
+        baseFragmentHelp.dismissLoading();
+    }
+
+    public void showToast(String msg) {
+        baseFragmentHelp.showToast(msg);
+    }
+
+    public void showSuccess(String msg) {
+        baseFragmentHelp.showSuccess(msg);
+    }
+
+    public void showErr(String msg) {
+        baseFragmentHelp.showErr(msg);
+    }
+
+    public void showErr(String msg, String hint) {
+        baseFragmentHelp.showErr(msg, hint);
+    }
+
+    @Override
+    public void onDestroyView() {
+        if (null != baseFragmentHelp) {
+            baseFragmentHelp.onDestroy();
+            baseFragmentHelp = null;
+        }
+        super.onDestroyView();
+    }
+}

+ 73 - 0
app/src/main/java/xn/hxp/base/BaseFragmentHelp.java

@@ -0,0 +1,73 @@
+package xn.hxp.base;
+
+import com.blankj.utilcode.util.StringUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.hjq.toast.Toaster;
+import com.kongzue.dialogx.DialogX;
+import com.kongzue.dialogx.dialogs.PopTip;
+import com.kongzue.dialogx.dialogs.WaitDialog;
+
+import java.util.concurrent.TimeUnit;
+
+import xn.hxp.R;
+
+public class BaseFragmentHelp {
+    private BaseFragment baseFragment;
+    private ThreadUtils.SimpleTask<Object> cdDismissTask;
+
+    public BaseFragmentHelp(BaseFragment baseFragment) {
+        this.baseFragment = baseFragment;
+    }
+
+    public void onDestroy() {
+
+    }
+
+    public void showLoading(String msg) {
+        ThreadUtils.cancel(cdDismissTask);
+        if (StringUtils.isEmpty(msg)) {
+            msg = "请稍等...";
+        }
+        WaitDialog.show(msg).setCancelable(DialogX.cancelable);
+        ThreadUtils.executeByCachedWithDelay(cdDismissTask = new ThreadUtils.SimpleTask<Object>() {
+            @Override
+            public Object doInBackground() throws Throwable {
+                return null;
+            }
+
+            @Override
+            public void onSuccess(Object result) {
+                WaitDialog.dismiss();
+            }
+        }, 20, TimeUnit.SECONDS);
+    }
+
+    public void dismissLoading() {
+        ThreadUtils.cancel(cdDismissTask);
+        WaitDialog.dismiss();
+    }
+
+    public void showToast(String msg) {
+        Toaster.setView(R.layout.toast);
+        Toaster.show(msg);
+    }
+
+    public void showErr(String msg) {
+        Toaster.setView(R.layout.toast_err);
+        Toaster.show(msg);
+    }
+
+    public void showSuccess(String msg) {
+        Toaster.setView(R.layout.toast_err);
+        Toaster.show(msg);
+    }
+
+    public void showErr(String msg, String hint) {
+        PopTip.show(msg).iconError();
+        PopTip.show(hint);
+    }
+
+    public void showWarn() {
+
+    }
+}

+ 17 - 0
app/src/main/java/xn/hxp/event/UpdateTimeEvent.java

@@ -0,0 +1,17 @@
+package xn.hxp.event;
+
+public class UpdateTimeEvent {
+    private String time;
+
+    public UpdateTimeEvent(String time) {
+        this.time = time;
+    }
+
+    public String getTime() {
+        return time;
+    }
+
+    public void setTime(String time) {
+        this.time = time;
+    }
+}

+ 72 - 0
app/src/main/java/xn/hxp/http/HttpData.java

@@ -0,0 +1,72 @@
+package xn.hxp.http;
+
+import androidx.annotation.Nullable;
+
+import java.util.Map;
+
+public class HttpData<T> {
+
+    /**
+     * 响应头
+     */
+    @Nullable
+    private Map<String, String> responseHeaders;
+
+    /**
+     * 返回码
+     */
+    private int code;
+    /**
+     * 提示语
+     */
+    private String message;
+    /**
+     * 数据
+     */
+    @Nullable
+    private T data;
+
+    public void setResponseHeaders(@Nullable Map<String, String> responseHeaders) {
+        this.responseHeaders = responseHeaders;
+    }
+
+    @Nullable
+    public Map<String, String> getResponseHeaders() {
+        return responseHeaders;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    @Nullable
+    public T getData() {
+        return data;
+    }
+
+    /**
+     * 是否请求成功
+     */
+    public boolean isRequestSuccess() {
+        // 这里为了兼容 WanAndroid 接口才这样写,但是一般情况下不建议这么设计
+        // 因为 int 的默认值就是 0,这样就会导致,后台返回结果码为 0 和没有返回的效果是一样的
+        // 本质上其实不一样,没有返回结果码本身就是一种错误数据结构,理论上应该走失败的回调
+        // 因为这里会判断是否等于 0,所以就会导致原本走失败的回调,结果走了成功的回调
+        // 所以在定义错误码协议的时候,请不要将后台返回的某个成功码或者失败码的值设计成 0
+        // 如果你的项目已经出现了这种情况,可以尝试将结果码的数据类型从 int 修改成 Integer
+        // 这样就可以通过结果码是否等于 null 来判断后台是否返回了,当然这样也有一些弊端
+        // 后面外层在使用这个结果码的时候,要先对 Integer 对象进行一次判空,否则会出现空指针异常
+        return getCode() == 200;
+    }
+
+    /**
+     * 是否 Token 失效
+     */
+    public boolean isTokenInvalidation() {
+        return getCode() == 401;
+    }
+}

+ 32 - 0
app/src/main/java/xn/hxp/http/HttpTool.java

@@ -0,0 +1,32 @@
+package xn.hxp.http;
+
+import android.net.Uri;
+import android.util.Log;
+
+import com.blankj.utilcode.util.LogUtils;
+
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.tool.DeviceInfoTool;
+
+public enum HttpTool {
+    INSTANCE;
+
+    private DeviceInfo deviceInfo;
+
+    HttpTool() {
+        deviceInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+    }
+
+    public String checkUrl(String url) {
+        try {
+            String uriStr = url;
+            if (!url.matches("https?://.*") && !url.matches("http?://.*")) {
+                uriStr = Uri.parse(deviceInfo.getBaseUrl() + "/api/" + url).toString();
+            }
+            return uriStr;
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
+            return url;
+        }
+    }
+}

+ 200 - 0
app/src/main/java/xn/hxp/http/RequestHandler.java

@@ -0,0 +1,200 @@
+package xn.hxp.http;
+
+import android.app.Application;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import androidx.annotation.NonNull;
+
+import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.google.gson.JsonSyntaxException;
+import com.hjq.http.EasyLog;
+import com.hjq.http.config.IRequestHandler;
+import com.hjq.http.exception.CancelException;
+import com.hjq.http.exception.DataException;
+import com.hjq.http.exception.FileMd5Exception;
+import com.hjq.http.exception.HttpException;
+import com.hjq.http.exception.NetworkException;
+import com.hjq.http.exception.NullBodyException;
+import com.hjq.http.exception.ResponseException;
+import com.hjq.http.exception.ServerException;
+import com.hjq.http.exception.TimeoutException;
+import com.hjq.http.request.HttpRequest;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+import okhttp3.Headers;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import xn.hxp.R;
+import xn.hxp.http.exception.ResultException;
+import xn.hxp.http.exception.TokenException;
+import xn.hxp.ui.start.StartActivity;
+
+public class RequestHandler implements IRequestHandler {
+    private final Application mApplication;
+
+    public RequestHandler(Application application) {
+        mApplication = application;
+    }
+
+    @NonNull
+    @Override
+    public Object requestSuccess(@NonNull HttpRequest<?> httpRequest, @NonNull Response response, @NonNull Type type) throws Throwable {
+        if (Response.class.equals(type)) {
+            return response;
+        }
+
+        if (!response.isSuccessful()) {
+            throw new ResponseException(String.format(mApplication.getString(R.string.http_response_error),
+                    response.code(), response.message()), response);
+        }
+
+        if (Headers.class.equals(type)) {
+            return response.headers();
+        }
+
+        ResponseBody body = response.body();
+        if (body == null) {
+            throw new NullBodyException(mApplication.getString(R.string.http_response_null_body));
+        }
+
+        if (ResponseBody.class.equals(type)) {
+            return body;
+        }
+
+        // 如果是用数组接收,判断一下是不是用 byte[] 类型进行接收的
+        if (type instanceof GenericArrayType) {
+            Type genericComponentType = ((GenericArrayType) type).getGenericComponentType();
+            if (byte.class.equals(genericComponentType)) {
+                return body.bytes();
+            }
+        }
+
+        if (InputStream.class.equals(type)) {
+            return body.byteStream();
+        }
+
+        if (Bitmap.class.equals(type)) {
+            return BitmapFactory.decodeStream(body.byteStream());
+        }
+
+        String text;
+        try {
+            text = body.string();
+        } catch (IOException e) {
+            // 返回结果读取异常
+            throw new DataException(mApplication.getString(R.string.http_data_explain_error), e);
+        }
+
+        // 打印这个 Json 或者文本
+        EasyLog.printJson(httpRequest, text);
+
+        if (String.class.equals(type)) {
+            return text;
+        }
+
+        final Object result;
+
+        try {
+            result = GsonUtils.fromJson(text, type);
+        } catch (JsonSyntaxException e) {
+            // 返回结果读取异常
+            throw new DataException(mApplication.getString(R.string.http_data_explain_error), e);
+        }
+
+        if (result instanceof HttpData) {
+            HttpData<?> model = (HttpData<?>) result;
+            Headers headers = response.headers();
+            int headersSize = headers.size();
+            Map<String, String> headersMap = new HashMap<>(headersSize);
+            for (int i = 0; i < headersSize; i++) {
+                headersMap.put(headers.name(i), headers.value(i));
+            }
+            // Github issue 地址:https://github.com/getActivity/EasyHttp/issues/233
+            model.setResponseHeaders(headersMap);
+
+            if (model.isRequestSuccess()) {
+                // 代表执行成功
+                return result;
+            }
+
+            if (model.isTokenInvalidation()) {
+                // 代表登录失效,需要重新登录
+                throw new TokenException(mApplication.getString(R.string.http_token_error));
+            }
+
+            // 代表执行失败
+            throw new ResultException(model.getMessage(), model);
+        }
+        return result;
+    }
+
+    @NonNull
+    @Override
+    public Throwable requestFail(@NonNull HttpRequest<?> httpRequest, @NonNull Throwable throwable) {
+        if (throwable instanceof HttpException) {
+            if (throwable instanceof TokenException) {
+                ActivityUtils.finishToActivity(StartActivity.class, false);
+            }
+            return throwable;
+        }
+
+        if (throwable instanceof SocketTimeoutException) {
+            return new TimeoutException(mApplication.getString(R.string.http_server_out_time), throwable);
+        }
+
+        if (throwable instanceof UnknownHostException) {
+            NetworkInfo info = ((ConnectivityManager) mApplication.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
+            // 判断网络是否连接
+            if (info != null && info.isConnected()) {
+                // 有连接就是服务器的问题
+                return new ServerException(mApplication.getString(R.string.http_server_error), throwable);
+            }
+            // 没有连接就是网络异常
+            return new NetworkException(mApplication.getString(R.string.http_network_error), throwable);
+        }
+
+        if (throwable instanceof IOException) {
+            // 出现该异常的两种情况
+            // 1. 调用 EasyHttp 取消请求
+            // 2. 网络请求被中断
+            return new CancelException(mApplication.getString(R.string.http_request_cancel), throwable);
+        }
+
+        return new HttpException(throwable.getMessage(), throwable);
+    }
+
+    @NonNull
+    @Override
+    public Throwable downloadFail(@NonNull HttpRequest<?> httpRequest, @NonNull Throwable throwable) {
+        if (throwable instanceof ResponseException) {
+            ResponseException responseException = ((ResponseException) throwable);
+            Response response = responseException.getResponse();
+            responseException.setMessage(String.format(mApplication.getString(R.string.http_response_error),
+                    response.code(), response.message()));
+            return responseException;
+        } else if (throwable instanceof NullBodyException) {
+            NullBodyException nullBodyException = ((NullBodyException) throwable);
+            nullBodyException.setMessage(mApplication.getString(R.string.http_response_null_body));
+            return nullBodyException;
+        } else if (throwable instanceof FileMd5Exception) {
+            FileMd5Exception fileMd5Exception = ((FileMd5Exception) throwable);
+            fileMd5Exception.setMessage(mApplication.getString(R.string.http_response_md5_error));
+            return fileMd5Exception;
+        }
+        return requestFail(httpRequest, throwable);
+    }
+}

+ 27 - 0
app/src/main/java/xn/hxp/http/RequestServer.java

@@ -0,0 +1,27 @@
+package xn.hxp.http;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.config.IRequestApi;
+import com.hjq.http.config.IRequestBodyStrategy;
+import com.hjq.http.config.IRequestServer;
+import com.hjq.http.model.RequestBodyType;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.DeviceInfo;
+
+public class RequestServer implements IRequestServer {
+    @NonNull
+    @Override
+    public String getHost() {
+//        DeviceInfo sqlDeviceInfo = RoomTool.getInstance().deviceInfoDao().getDeviceInfo();
+        return "http://192.168.1.8";
+    }
+
+
+    @NonNull
+    @Override
+    public IRequestBodyStrategy getBodyType() {
+        return RequestBodyType.JSON;
+    }
+}

+ 30 - 0
app/src/main/java/xn/hxp/http/api/GetBaseConfigApi.java

@@ -0,0 +1,30 @@
+package xn.hxp.http.api;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.config.IRequestApi;
+
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.util.Tool;
+
+public class GetBaseConfigApi implements IRequestApi {
+    @NonNull
+    @Override
+    public String getApi() {
+        return "/api/chemical/aio/basicConfig";
+    }
+
+    private String subId;
+
+    public String getSubId() {
+        return subId;
+    }
+
+    public void setSubId(String subId) {
+        this.subId = subId;
+    }
+
+    public GetBaseConfigApi(String subjectId) {
+        setSubId(subjectId);
+    }
+}

+ 30 - 0
app/src/main/java/xn/hxp/http/api/GetCabinetListApi.java

@@ -0,0 +1,30 @@
+package xn.hxp.http.api;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.config.IRequestApi;
+
+/**
+ * 获取实验室柜子
+ */
+public class GetCabinetListApi implements IRequestApi {
+    @NonNull
+    @Override
+    public String getApi() {
+        return "/api/chemical/aio/getCabinetBySubId";
+    }
+
+    private String subId;
+
+    public GetCabinetListApi(String subId) {
+        this.subId = subId;
+    }
+
+    public String getSubId() {
+        return subId;
+    }
+
+    public void setSubId(String subId) {
+        this.subId = subId;
+    }
+}

+ 37 - 0
app/src/main/java/xn/hxp/http/api/GetCardLoginApi.java

@@ -0,0 +1,37 @@
+package xn.hxp.http.api;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.config.IRequestApi;
+
+public class GetCardLoginApi implements IRequestApi {
+    private String cardNum;
+    private String subId;
+
+    @NonNull
+    @Override
+    public String getApi() {
+        return "/api/auth/getCardNum";
+    }
+
+    public GetCardLoginApi(String cardNum, String subId) {
+        this.cardNum = cardNum;
+        this.subId = subId;
+    }
+
+    public String getCardNum() {
+        return cardNum;
+    }
+
+    public void setCardNum(String cardNum) {
+        this.cardNum = cardNum;
+    }
+
+    public String getSubId() {
+        return subId;
+    }
+
+    public void setSubId(String subId) {
+        this.subId = subId;
+    }
+}

+ 44 - 0
app/src/main/java/xn/hxp/http/api/GetReportApi.java

@@ -0,0 +1,44 @@
+package xn.hxp.http.api;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.config.IRequestApi;
+
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.util.Tool;
+
+public class GetReportApi implements IRequestApi {
+    @NonNull
+    @Override
+    public String getApi() {
+        return "/api/iot/aio/report";
+    }
+
+    private final String deviceNo = Tool.INSTANCE.getSerialNumber();
+    private final String code = "aio_chemical";
+    private final String version = "100";
+
+    public static final class Bean {
+        private boolean needUpgrade;
+        private LabInfo labInfo;
+
+        public boolean isNeedUpgrade() {
+            return needUpgrade;
+        }
+
+        public void setNeedUpgrade(boolean needUpgrade) {
+            this.needUpgrade = needUpgrade;
+        }
+
+        public LabInfo getLabInfo() {
+            return labInfo;
+        }
+
+        public void setLabInfo(LabInfo labInfo) {
+            this.labInfo = labInfo;
+        }
+
+
+    }
+
+}

+ 48 - 0
app/src/main/java/xn/hxp/http/api/GetScanCodeApi.java

@@ -0,0 +1,48 @@
+package xn.hxp.http.api;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.config.IRequestApi;
+
+public class GetScanCodeApi implements IRequestApi {
+    @NonNull
+    @Override
+    public String getApi() {
+        return "/api/auth/getScanCode";
+    }
+
+    private String macId;
+    private String code;
+    private String subId;
+
+
+    public GetScanCodeApi(String macId, String code, String subId) {
+        this.macId = macId;
+        this.code = code;
+        this.subId = subId;
+    }
+
+    public String getMacId() {
+        return macId;
+    }
+
+    public void setMacId(String macId) {
+        this.macId = macId;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getSubId() {
+        return subId;
+    }
+
+    public void setSubId(String subId) {
+        this.subId = subId;
+    }
+}

+ 32 - 0
app/src/main/java/xn/hxp/http/api/PostFaceByPicApi.java

@@ -0,0 +1,32 @@
+package xn.hxp.http.api;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.config.IRequestApi;
+import com.hjq.http.config.IRequestBodyStrategy;
+import com.hjq.http.config.IRequestType;
+import com.hjq.http.model.RequestBodyType;
+
+import java.io.File;
+
+public class PostFaceByPicApi implements IRequestApi, IRequestType {
+    private File file;
+    private String subId;
+
+    public PostFaceByPicApi(File inputStream, String subjectId) {
+        this.file = inputStream;
+        this.subId = subjectId;
+    }
+
+    @NonNull
+    @Override
+    public String getApi() {
+        return "/api/auth/getFaceByPic";
+    }
+
+    @NonNull
+    @Override
+    public IRequestBodyStrategy getBodyType() {
+        return RequestBodyType.FORM;
+    }
+}

+ 27 - 0
app/src/main/java/xn/hxp/http/exception/ResultException.java

@@ -0,0 +1,27 @@
+package xn.hxp.http.exception;
+
+import androidx.annotation.NonNull;
+
+import com.hjq.http.exception.HttpException;
+
+import xn.hxp.http.HttpData;
+
+public final class ResultException extends HttpException {
+
+    private final HttpData<?> mData;
+
+    public ResultException(String message, HttpData<?> data) {
+        super(message);
+        mData = data;
+    }
+
+    public ResultException(String message, Throwable cause, HttpData<?> data) {
+        super(message, cause);
+        mData = data;
+    }
+
+    @NonNull
+    public HttpData<?> getHttpData() {
+        return mData;
+    }
+}

+ 14 - 0
app/src/main/java/xn/hxp/http/exception/TokenException.java

@@ -0,0 +1,14 @@
+package xn.hxp.http.exception;
+
+import com.hjq.http.exception.HttpException;
+
+public final class TokenException extends HttpException {
+
+    public TokenException(String message) {
+        super(message);
+    }
+
+    public TokenException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

+ 27 - 0
app/src/main/java/xn/hxp/receiver/MinuteTickReceiver.java

@@ -0,0 +1,27 @@
+package xn.hxp.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.blankj.utilcode.util.TimeUtils;
+
+import org.greenrobot.eventbus.EventBus;
+
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+import xn.hxp.event.UpdateTimeEvent;
+
+/**
+ * 分钟广播
+ */
+public class MinuteTickReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
+            String nowString = TimeUtils.getNowString(new SimpleDateFormat("M月d日  EEEE  HH:mm", Locale.getDefault()));
+            EventBus.getDefault().post(new UpdateTimeEvent(nowString));
+        }
+    }
+}

+ 61 - 0
app/src/main/java/xn/hxp/room/RoomTool.java

@@ -0,0 +1,61 @@
+package xn.hxp.room;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import com.blankj.utilcode.util.Utils;
+
+import xn.hxp.room.bean.BaseConfig;
+import xn.hxp.room.bean.cabinet.Cabinet;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.bean.UserInfo;
+import xn.hxp.room.bean.cabinet.CabinetDoor;
+import xn.hxp.room.bean.cabinet.CabinetDoorAdmin;
+import xn.hxp.room.bean.cabinet.CabinetDoorLock;
+import xn.hxp.room.dao.BaseConfigDao;
+import xn.hxp.room.dao.cabinet.CabinetDao;
+import xn.hxp.room.dao.DeviceInfoDao;
+import xn.hxp.room.dao.LabInfoDao;
+import xn.hxp.room.dao.UserInfoDao;
+import xn.hxp.room.dao.cabinet.CabinetDoorAdminDao;
+import xn.hxp.room.dao.cabinet.CabinetDoorDao;
+import xn.hxp.room.dao.cabinet.CabinetDoorLockDao;
+
+@Database(entities = {
+        DeviceInfo.class,
+        LabInfo.class,
+        UserInfo.class,
+        BaseConfig.class,
+        Cabinet.class,
+        CabinetDoor.class,
+        CabinetDoorAdmin.class,
+        CabinetDoorLock.class
+}, version = 1, exportSchema = false)
+public abstract class RoomTool extends RoomDatabase {
+    private static RoomTool INSTANCE;
+
+    public static synchronized RoomTool getInstance() {
+        if (INSTANCE == null) {
+            INSTANCE = Room.databaseBuilder(Utils.getApp(), RoomTool.class, "hxp2_db").allowMainThreadQueries().build();
+        }
+        return INSTANCE;
+    }
+
+    public abstract DeviceInfoDao deviceInfoDao();
+
+    public abstract LabInfoDao labInfoDao();
+
+    public abstract UserInfoDao userInfoDao();
+
+    public abstract BaseConfigDao baseConfigDao();
+
+    public abstract CabinetDao cabinetDao();
+
+    public abstract CabinetDoorDao cabinetDoorDao();
+
+    public abstract CabinetDoorAdminDao cabinetDoorAdminDao();
+
+    public abstract CabinetDoorLockDao cabinetDoorLockDao();
+}

+ 188 - 0
app/src/main/java/xn/hxp/room/bean/BaseConfig.java

@@ -0,0 +1,188 @@
+package xn.hxp.room.bean;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class BaseConfig {
+
+    @PrimaryKey
+    private int pid;
+    private String id;
+    // 是否开启化学品申购
+    private boolean subscribe;
+
+    private String loginType;
+    private String verifyType;
+
+    // 容差百分比
+    private double vinVex;
+    // 学院图标
+    private String circularLogo;
+    // 实验室名称
+    private String subName;
+    // 分级名称
+    private String levelName;
+    // 分级颜色
+    private String levelColor;
+    // 房间编号
+    private String roomNum;
+    // 学院名称
+    private String deptName;
+    // 楼栋名称
+    private String buildName;
+    // 楼层名称
+    private String floorName;
+    // 自动返回时间
+    private int backTime;
+    // 自动注销时间
+    private int signOutTime;
+    // 弹窗关闭时间
+    private int offTime;
+    // 称重示意图
+    private String weighHintPicture;
+
+    public int getPid() {
+        return pid;
+    }
+
+    public void setPid(int pid) {
+        this.pid = pid;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public boolean isSubscribe() {
+        return subscribe;
+    }
+
+    public void setSubscribe(boolean subscribe) {
+        this.subscribe = subscribe;
+    }
+
+    public String getLoginType() {
+        return loginType;
+    }
+
+    public void setLoginType(String loginType) {
+        this.loginType = loginType;
+    }
+
+    public String getVerifyType() {
+        return verifyType;
+    }
+
+    public void setVerifyType(String verifyType) {
+        this.verifyType = verifyType;
+    }
+
+    public double getVinVex() {
+        return vinVex;
+    }
+
+    public void setVinVex(double vinVex) {
+        this.vinVex = vinVex;
+    }
+
+    public String getCircularLogo() {
+        return circularLogo;
+    }
+
+    public void setCircularLogo(String circularLogo) {
+        this.circularLogo = circularLogo;
+    }
+
+    public String getSubName() {
+        return subName;
+    }
+
+    public void setSubName(String subName) {
+        this.subName = subName;
+    }
+
+    public String getLevelName() {
+        return levelName;
+    }
+
+    public void setLevelName(String levelName) {
+        this.levelName = levelName;
+    }
+
+    public String getLevelColor() {
+        return levelColor;
+    }
+
+    public void setLevelColor(String levelColor) {
+        this.levelColor = levelColor;
+    }
+
+    public String getRoomNum() {
+        return roomNum;
+    }
+
+    public void setRoomNum(String roomNum) {
+        this.roomNum = roomNum;
+    }
+
+    public String getDeptName() {
+        return deptName;
+    }
+
+    public void setDeptName(String deptName) {
+        this.deptName = deptName;
+    }
+
+    public String getBuildName() {
+        return buildName;
+    }
+
+    public void setBuildName(String buildName) {
+        this.buildName = buildName;
+    }
+
+    public String getFloorName() {
+        return floorName;
+    }
+
+    public void setFloorName(String floorName) {
+        this.floorName = floorName;
+    }
+
+    public int getBackTime() {
+        return backTime;
+    }
+
+    public void setBackTime(int backTime) {
+        this.backTime = backTime;
+    }
+
+    public int getSignOutTime() {
+        return signOutTime;
+    }
+
+    public void setSignOutTime(int signOutTime) {
+        this.signOutTime = signOutTime;
+    }
+
+    public int getOffTime() {
+        return offTime;
+    }
+
+    public void setOffTime(int offTime) {
+        this.offTime = offTime;
+    }
+
+    public String getWeighHintPicture() {
+        return weighHintPicture;
+    }
+
+    public void setWeighHintPicture(String weighHintPicture) {
+        this.weighHintPicture = weighHintPicture;
+    }
+}

+ 83 - 0
app/src/main/java/xn/hxp/room/bean/DeviceInfo.java

@@ -0,0 +1,83 @@
+package xn.hxp.room.bean;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class DeviceInfo {
+    @PrimaryKey
+    private int id;
+    // http://192.168.1.8
+    private String baseUrl = "http://172.16.0.65";
+    private String sn;
+    private String ip;
+    // 是否前置摄像头
+    private boolean isFrontCamera;
+    private String adminPwd;
+    private String bleMac;
+    private String bleName;
+
+    public String getBleName() {
+        return bleName;
+    }
+
+    public void setBleName(String bleName) {
+        this.bleName = bleName;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public boolean isFrontCamera() {
+        return isFrontCamera;
+    }
+
+    public void setFrontCamera(boolean frontCamera) {
+        isFrontCamera = frontCamera;
+    }
+
+    public String getAdminPwd() {
+        return adminPwd;
+    }
+
+    public void setAdminPwd(String adminPwd) {
+        this.adminPwd = adminPwd;
+    }
+
+    public String getBleMac() {
+        return bleMac;
+    }
+
+    public void setBleMac(String bleMac) {
+        this.bleMac = bleMac;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+}

+ 64 - 0
app/src/main/java/xn/hxp/room/bean/LabInfo.java

@@ -0,0 +1,64 @@
+package xn.hxp.room.bean;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class LabInfo {
+    @PrimaryKey
+    private int id;
+
+    private String floorId;// 楼层ID
+    private String floorName; // 楼层
+    private String subjectId;// 实验室ID
+    private String room;// 房间号
+    private String subjectName;// 实验室名字
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getFloorId() {
+        return floorId;
+    }
+
+    public void setFloorId(String floorId) {
+        this.floorId = floorId;
+    }
+
+    public String getFloorName() {
+        return floorName;
+    }
+
+    public void setFloorName(String floorName) {
+        this.floorName = floorName;
+    }
+
+    public String getSubjectId() {
+        return subjectId;
+    }
+
+    public void setSubjectId(String subjectId) {
+        this.subjectId = subjectId;
+    }
+
+    public String getRoom() {
+        return room;
+    }
+
+    public void setRoom(String room) {
+        this.room = room;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+}

+ 133 - 0
app/src/main/java/xn/hxp/room/bean/UserInfo.java

@@ -0,0 +1,133 @@
+package xn.hxp.room.bean;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class UserInfo {
+    @PrimaryKey
+    private int id;
+    // 过期时间
+    private String expiresIn;
+    /**
+     * 账号类型
+     * -1 超级管理员
+     * 0 管理员
+     * 1 普通用户
+     */
+    private int accountType;
+    // 商户ID
+    private int tenantId;
+
+    // 是否初始密码
+    private boolean isInitPasswd;
+    // "用户类型:0-系统-,1-教职工,2-学生,3-大屏"
+    /**
+     * 用户类型
+     * 0 系统
+     * 1 教职工
+     * 2 学生
+     * 3 大屏
+     */
+    private int userType;
+    // 头像
+    private String avatar;
+
+    // 用户名字
+    private String userName;
+
+    // 用户ID
+    private long userId;
+    // 账号
+    private String account;
+    // token
+    private String token;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getExpiresIn() {
+        return expiresIn;
+    }
+
+    public void setExpiresIn(String expiresIn) {
+        this.expiresIn = expiresIn;
+    }
+
+    public int getAccountType() {
+        return accountType;
+    }
+
+    public void setAccountType(int accountType) {
+        this.accountType = accountType;
+    }
+
+    public int getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(int tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public boolean isInitPasswd() {
+        return isInitPasswd;
+    }
+
+    public void setInitPasswd(boolean initPasswd) {
+        isInitPasswd = initPasswd;
+    }
+
+    public int getUserType() {
+        return userType;
+    }
+
+    public void setUserType(int userType) {
+        this.userType = userType;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(long userId) {
+        this.userId = userId;
+    }
+
+    public String getAccount() {
+        return account;
+    }
+
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+}

+ 255 - 0
app/src/main/java/xn/hxp/room/bean/cabinet/Cabinet.java

@@ -0,0 +1,255 @@
+package xn.hxp.room.bean.cabinet;
+
+import androidx.room.Entity;
+import androidx.room.Ignore;
+import androidx.room.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+public class Cabinet {
+    @PrimaryKey
+    // ("机柜id")
+    private long cabinetId;
+    // ("机柜名称")
+    private String cabinetName;
+    // ("学院id")
+    private long deptId;
+    // ("学院名称")
+    private String deptName;
+    // ("楼栋id")
+    private long buildId;
+    // ("楼栋名称")
+    private String buildName;
+    // ("楼层id")
+    private long floorId;
+    // ("楼层名称")
+    private String floorName;
+    // ("实验室id")
+    private long subId;
+    // ("实验室名称")
+    private String subName;
+    // ("房间编号")
+    private String roomNum;
+    // ("安全员信息")
+    private String safeInfo;
+    // ("摄像头id")
+    private long cameraId;
+    // ("采集器id")
+    private long collectorId;
+    // ("柜门数量")
+    private int doorNum;
+    // ("柜锁数量")
+    private int lockNum;
+    // ("化学品数量")
+    private int chemicalNum;
+    // ("创建人id")
+    private long createBy;
+    // ("创建人姓名")
+    private String createName;
+    // ("创建时间")
+    private String createTime;
+    // ("修改人id")
+    private long updateBy;
+    // ("修改人姓名")
+    private String updateName;
+    // ("修改时间")
+    private String updateTime;
+
+    // ("柜门列表") 该字段不会映射到数据库,只是为了方便解析
+    @Ignore
+    private List<CabinetDoor> cabinetDoorVoList = new ArrayList<>();
+
+    public long getCabinetId() {
+        return cabinetId;
+    }
+
+    public void setCabinetId(long cabinetId) {
+        this.cabinetId = cabinetId;
+    }
+
+    public String getCabinetName() {
+        return cabinetName;
+    }
+
+    public void setCabinetName(String cabinetName) {
+        this.cabinetName = cabinetName;
+    }
+
+    public long getDeptId() {
+        return deptId;
+    }
+
+    public void setDeptId(long deptId) {
+        this.deptId = deptId;
+    }
+
+    public String getDeptName() {
+        return deptName;
+    }
+
+    public void setDeptName(String deptName) {
+        this.deptName = deptName;
+    }
+
+    public long getBuildId() {
+        return buildId;
+    }
+
+    public void setBuildId(long buildId) {
+        this.buildId = buildId;
+    }
+
+    public String getBuildName() {
+        return buildName;
+    }
+
+    public void setBuildName(String buildName) {
+        this.buildName = buildName;
+    }
+
+    public long getFloorId() {
+        return floorId;
+    }
+
+    public void setFloorId(long floorId) {
+        this.floorId = floorId;
+    }
+
+    public String getFloorName() {
+        return floorName;
+    }
+
+    public void setFloorName(String floorName) {
+        this.floorName = floorName;
+    }
+
+    public long getSubId() {
+        return subId;
+    }
+
+    public void setSubId(long subId) {
+        this.subId = subId;
+    }
+
+    public String getSubName() {
+        return subName;
+    }
+
+    public void setSubName(String subName) {
+        this.subName = subName;
+    }
+
+    public String getRoomNum() {
+        return roomNum;
+    }
+
+    public void setRoomNum(String roomNum) {
+        this.roomNum = roomNum;
+    }
+
+    public String getSafeInfo() {
+        return safeInfo;
+    }
+
+    public void setSafeInfo(String safeInfo) {
+        this.safeInfo = safeInfo;
+    }
+
+    public long getCameraId() {
+        return cameraId;
+    }
+
+    public void setCameraId(long cameraId) {
+        this.cameraId = cameraId;
+    }
+
+    public long getCollectorId() {
+        return collectorId;
+    }
+
+    public void setCollectorId(long collectorId) {
+        this.collectorId = collectorId;
+    }
+
+    public int getDoorNum() {
+        return doorNum;
+    }
+
+    public void setDoorNum(int doorNum) {
+        this.doorNum = doorNum;
+    }
+
+    public int getLockNum() {
+        return lockNum;
+    }
+
+    public void setLockNum(int lockNum) {
+        this.lockNum = lockNum;
+    }
+
+    public int getChemicalNum() {
+        return chemicalNum;
+    }
+
+    public void setChemicalNum(int chemicalNum) {
+        this.chemicalNum = chemicalNum;
+    }
+
+    public long getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(long createBy) {
+        this.createBy = createBy;
+    }
+
+    public String getCreateName() {
+        return createName;
+    }
+
+    public void setCreateName(String createName) {
+        this.createName = createName;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public long getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(long updateBy) {
+        this.updateBy = updateBy;
+    }
+
+    public String getUpdateName() {
+        return updateName;
+    }
+
+    public void setUpdateName(String updateName) {
+        this.updateName = updateName;
+    }
+
+    public String getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(String updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public List<CabinetDoor> getCabinetDoorVoList() {
+        return cabinetDoorVoList;
+    }
+
+    public void setCabinetDoorVoList(List<CabinetDoor> cabinetDoorVoList) {
+        this.cabinetDoorVoList = cabinetDoorVoList;
+    }
+}

+ 158 - 0
app/src/main/java/xn/hxp/room/bean/cabinet/CabinetDoor.java

@@ -0,0 +1,158 @@
+package xn.hxp.room.bean.cabinet;
+
+import androidx.room.Entity;
+import androidx.room.Ignore;
+import androidx.room.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+public class CabinetDoor {
+    // ("主键id")
+    @PrimaryKey
+    private long doorId;
+
+    // ("关键机柜id")
+    private long cabinetId;
+
+    // ("柜门名称")
+    private String doorName;
+
+    // ("门里面的层数")
+    private int doorLayers;
+
+    // ("机柜唯一id,用于化学品入库绑定唯一编码")
+
+    private long doorUniqueId;
+
+    // ("开始方式:1无锁,2是智能锁,3是钥匙柜")
+    private int unlockingMethod;
+
+    // ("化学品库存数量")
+    private int stockNum;
+
+    // ("化学品使用数量")
+    private int useNum;
+
+    // ("是否管控")
+    private boolean isControl;
+
+    // ("是否双人认证")
+    private boolean verify;
+
+    // ("是否开启关闭")
+    private boolean openOrClose;
+
+    // ("管理员列表") 该字段不会映射到数据库,只是为了方便解析
+    @Ignore
+    private List<CabinetDoorAdmin> cabinetAdminVoList = new ArrayList<>();
+
+    // ("机柜锁列表") 该字段不会映射到数据库,只是为了方便解析
+    @Ignore
+    private List<CabinetDoorLock> cabinetLockVoList = new ArrayList<>();
+
+    public long getDoorId() {
+        return doorId;
+    }
+
+    public void setDoorId(long doorId) {
+        this.doorId = doorId;
+    }
+
+    public long getCabinetId() {
+        return cabinetId;
+    }
+
+    public void setCabinetId(long cabinetId) {
+        this.cabinetId = cabinetId;
+    }
+
+    public String getDoorName() {
+        return doorName;
+    }
+
+    public void setDoorName(String doorName) {
+        this.doorName = doorName;
+    }
+
+    public int getDoorLayers() {
+        return doorLayers;
+    }
+
+    public void setDoorLayers(int doorLayers) {
+        this.doorLayers = doorLayers;
+    }
+
+    public long getDoorUniqueId() {
+        return doorUniqueId;
+    }
+
+    public void setDoorUniqueId(long doorUniqueId) {
+        this.doorUniqueId = doorUniqueId;
+    }
+
+    public int getUnlockingMethod() {
+        return unlockingMethod;
+    }
+
+    public void setUnlockingMethod(int unlockingMethod) {
+        this.unlockingMethod = unlockingMethod;
+    }
+
+    public int getStockNum() {
+        return stockNum;
+    }
+
+    public void setStockNum(int stockNum) {
+        this.stockNum = stockNum;
+    }
+
+    public int getUseNum() {
+        return useNum;
+    }
+
+    public void setUseNum(int useNum) {
+        this.useNum = useNum;
+    }
+
+    public boolean isControl() {
+        return isControl;
+    }
+
+    public void setControl(boolean control) {
+        isControl = control;
+    }
+
+    public boolean isVerify() {
+        return verify;
+    }
+
+    public void setVerify(boolean verify) {
+        this.verify = verify;
+    }
+
+    public boolean isOpenOrClose() {
+        return openOrClose;
+    }
+
+    public void setOpenOrClose(boolean openOrClose) {
+        this.openOrClose = openOrClose;
+    }
+
+    public List<CabinetDoorAdmin> getCabinetAdminVoList() {
+        return cabinetAdminVoList;
+    }
+
+    public void setCabinetAdminVoList(List<CabinetDoorAdmin> cabinetAdminVoList) {
+        this.cabinetAdminVoList = cabinetAdminVoList;
+    }
+
+    public List<CabinetDoorLock> getCabinetLockVoList() {
+        return cabinetLockVoList;
+    }
+
+    public void setCabinetLockVoList(List<CabinetDoorLock> cabinetLockVoList) {
+        this.cabinetLockVoList = cabinetLockVoList;
+    }
+}

+ 57 - 0
app/src/main/java/xn/hxp/room/bean/cabinet/CabinetDoorAdmin.java

@@ -0,0 +1,57 @@
+package xn.hxp.room.bean.cabinet;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+/**
+ * 柜门管理员
+ */
+@Entity
+public class CabinetDoorAdmin {
+    @PrimaryKey
+    private long doorId;
+    private long userId;
+    private String userName;
+    private String phone;
+    private String subId;
+
+    public long getDoorId() {
+        return doorId;
+    }
+
+    public void setDoorId(long doorId) {
+        this.doorId = doorId;
+    }
+
+    public long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(long userId) {
+        this.userId = userId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getSubId() {
+        return subId;
+    }
+
+    public void setSubId(String subId) {
+        this.subId = subId;
+    }
+}

+ 74 - 0
app/src/main/java/xn/hxp/room/bean/cabinet/CabinetDoorLock.java

@@ -0,0 +1,74 @@
+package xn.hxp.room.bean.cabinet;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class CabinetDoorLock {
+    // ("柜门id")
+    @PrimaryKey
+    private long doorId;
+
+    // ("柜锁id")
+    private long lockId;
+
+    // ("锁类型:2是智能锁,3是钥匙柜")
+    private int unlockingMethod;
+
+    // ("锁名称")
+    private String lockName;
+
+    // ("锁编号")
+    private String lockNum;
+
+    // ("柜格编号:1,2,3 参考数据")
+    private String cabinetLattice;
+
+    public long getDoorId() {
+        return doorId;
+    }
+
+    public void setDoorId(long doorId) {
+        this.doorId = doorId;
+    }
+
+    public long getLockId() {
+        return lockId;
+    }
+
+    public void setLockId(long lockId) {
+        this.lockId = lockId;
+    }
+
+    public int getUnlockingMethod() {
+        return unlockingMethod;
+    }
+
+    public void setUnlockingMethod(int unlockingMethod) {
+        this.unlockingMethod = unlockingMethod;
+    }
+
+    public String getLockName() {
+        return lockName;
+    }
+
+    public void setLockName(String lockName) {
+        this.lockName = lockName;
+    }
+
+    public String getLockNum() {
+        return lockNum;
+    }
+
+    public void setLockNum(String lockNum) {
+        this.lockNum = lockNum;
+    }
+
+    public String getCabinetLattice() {
+        return cabinetLattice;
+    }
+
+    public void setCabinetLattice(String cabinetLattice) {
+        this.cabinetLattice = cabinetLattice;
+    }
+}

+ 29 - 0
app/src/main/java/xn/hxp/room/dao/BaseConfigDao.java

@@ -0,0 +1,29 @@
+package xn.hxp.room.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import xn.hxp.room.bean.BaseConfig;
+import xn.hxp.room.bean.LabInfo;
+
+@Dao
+public interface BaseConfigDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(BaseConfig baseConfig);
+
+    @Query("SELECT * FROM baseConfig")
+    BaseConfig getBaseConfig();
+
+    @Update
+    void update(BaseConfig baseConfig);
+
+    @Delete
+    void delete(BaseConfig baseConfig);
+
+    @Query("DELETE FROM baseConfig")
+    void deleteAll();
+}

+ 28 - 0
app/src/main/java/xn/hxp/room/dao/DeviceInfoDao.java

@@ -0,0 +1,28 @@
+package xn.hxp.room.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import xn.hxp.room.bean.DeviceInfo;
+
+@Dao
+public interface DeviceInfoDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(DeviceInfo deviceInfo);
+
+    @Query("SELECT * FROM deviceinfo")
+    DeviceInfo getDeviceInfo();
+
+    @Update
+    void update(DeviceInfo deviceInfo);
+
+    @Delete
+    void delete(DeviceInfo deviceInfo);
+
+    @Query("DELETE FROM deviceInfo")
+    void deleteAll();
+}

+ 29 - 0
app/src/main/java/xn/hxp/room/dao/LabInfoDao.java

@@ -0,0 +1,29 @@
+package xn.hxp.room.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.bean.LabInfo;
+
+@Dao
+public interface LabInfoDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(LabInfo labInfo);
+
+    @Query("SELECT * FROM labinfo")
+    LabInfo getLabInfo();
+
+    @Update
+    void update(LabInfo labInfo);
+
+    @Delete
+    void delete(LabInfo labInfo);
+
+    @Query("DELETE FROM labinfo")
+    void deleteAll();
+}

+ 29 - 0
app/src/main/java/xn/hxp/room/dao/UserInfoDao.java

@@ -0,0 +1,29 @@
+package xn.hxp.room.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.bean.UserInfo;
+
+@Dao
+public interface UserInfoDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(UserInfo userInfo);
+
+    @Query("SELECT * FROM userInfo")
+    UserInfo getUser();
+
+    @Update
+    void update(UserInfo userInfo);
+
+    @Delete
+    void delete(UserInfo userInfo);
+
+    @Query("DELETE FROM userInfo")
+    void deleteAll();
+}

+ 34 - 0
app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDao.java

@@ -0,0 +1,34 @@
+package xn.hxp.room.dao.cabinet;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import java.util.List;
+
+import xn.hxp.room.bean.cabinet.Cabinet;
+
+@Dao
+public interface CabinetDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(Cabinet cabinet);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(List<Cabinet> cabinetList);
+
+
+    @Query("SELECT * FROM cabinet")
+    List<Cabinet> getCabinetList();
+
+    @Update
+    void update(Cabinet cabinet);
+
+    @Delete
+    void delete(Cabinet cabinet);
+
+    @Query("DELETE FROM cabinet")
+    void deleteAll();
+}

+ 34 - 0
app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDoorAdminDao.java

@@ -0,0 +1,34 @@
+package xn.hxp.room.dao.cabinet;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import java.util.List;
+
+import xn.hxp.room.bean.cabinet.CabinetDoorAdmin;
+
+@Dao
+public interface CabinetDoorAdminDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(CabinetDoorAdmin cabinetDoorAdmin);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(List<CabinetDoorAdmin> cabinetDoorList);
+
+
+    @Query("SELECT * FROM cabinetDoorAdmin")
+    List<CabinetDoorAdmin> getCabinetDoorAdminList();
+
+    @Update
+    void update(CabinetDoorAdmin cabinetDoorAdmin);
+
+    @Delete
+    void delete(CabinetDoorAdmin cabinetDoorAdmin);
+
+    @Query("DELETE FROM cabinetDoorAdmin")
+    void deleteAll();
+}

+ 34 - 0
app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDoorDao.java

@@ -0,0 +1,34 @@
+package xn.hxp.room.dao.cabinet;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import java.util.List;
+
+import xn.hxp.room.bean.cabinet.CabinetDoor;
+
+@Dao
+public interface CabinetDoorDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(CabinetDoor cabinetDoor);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(List<CabinetDoor> cabinetDoorList);
+
+
+    @Query("SELECT * FROM cabinetdoor")
+    List<CabinetDoor> getCabinetDoorList();
+
+    @Update
+    void update(CabinetDoor cabinetDoor);
+
+    @Delete
+    void delete(CabinetDoor cabinetDoor);
+
+    @Query("DELETE FROM cabinetdoor")
+    void deleteAll();
+}

+ 34 - 0
app/src/main/java/xn/hxp/room/dao/cabinet/CabinetDoorLockDao.java

@@ -0,0 +1,34 @@
+package xn.hxp.room.dao.cabinet;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import java.util.List;
+
+import xn.hxp.room.bean.cabinet.CabinetDoorLock;
+
+@Dao
+public interface CabinetDoorLockDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(CabinetDoorLock cabinetDoorLock);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(List<CabinetDoorLock> cabinetDoorList);
+
+
+    @Query("SELECT * FROM cabinetDoorLock")
+    List<CabinetDoorLock> getCabinetDoorLockList();
+
+    @Update
+    void update(CabinetDoorLock cabinetDoorLock);
+
+    @Delete
+    void delete(CabinetDoorLock cabinetDoorLock);
+
+    @Query("DELETE FROM cabinetDoorLock")
+    void deleteAll();
+}

+ 25 - 0
app/src/main/java/xn/hxp/room/tool/BaseConfigTool.java

@@ -0,0 +1,25 @@
+package xn.hxp.room.tool;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.BaseConfig;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.dao.BaseConfigDao;
+import xn.hxp.room.dao.LabInfoDao;
+
+public enum BaseConfigTool {
+    INSTANCE;
+
+    private BaseConfigDao baseConfigDao;
+
+    BaseConfigTool() {
+        baseConfigDao = RoomTool.getInstance().baseConfigDao();
+    }
+
+    public void insert(BaseConfig baseConfig) {
+        baseConfigDao.insert(baseConfig);
+    }
+
+    public BaseConfig getBaseConfig() {
+        return baseConfigDao.getBaseConfig();
+    }
+}

+ 43 - 0
app/src/main/java/xn/hxp/room/tool/DeviceInfoTool.java

@@ -0,0 +1,43 @@
+package xn.hxp.room.tool;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.NetworkUtils;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.dao.DeviceInfoDao;
+import xn.hxp.util.Tool;
+
+public enum DeviceInfoTool {
+    INSTANCE;
+
+    private DeviceInfoDao deviceInfoDao;
+
+    DeviceInfoTool() {
+        deviceInfoDao = RoomTool.getInstance().deviceInfoDao();
+        init();
+    }
+
+    private void init() {
+        DeviceInfo deviceInfo = deviceInfoDao.getDeviceInfo();
+        if (null == deviceInfo) {
+            deviceInfo = new DeviceInfo();
+            deviceInfo.setBaseUrl(AppUtils.isAppDebug() ? "http://192.168.1.8" : "http://172.16.0.65");
+            deviceInfo.setAdminPwd("hxp");
+            deviceInfo.setIp(NetworkUtils.getIPAddress(true));
+            deviceInfo.setSn(Tool.INSTANCE.getSerialNumber());
+            deviceInfo.setFrontCamera(true);
+            deviceInfoDao.insert(deviceInfo);
+        }
+    }
+
+    public DeviceInfo getDeviceInfo() {
+        return deviceInfoDao.getDeviceInfo();
+    }
+
+    public void insert(DeviceInfo deviceInfo) {
+        deviceInfoDao.insert(deviceInfo);
+    }
+
+}

+ 23 - 0
app/src/main/java/xn/hxp/room/tool/LabInfoTool.java

@@ -0,0 +1,23 @@
+package xn.hxp.room.tool;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.dao.LabInfoDao;
+
+public enum LabInfoTool {
+    INSTANCE;
+
+    private LabInfoDao labInfoDao;
+
+    LabInfoTool() {
+        labInfoDao = RoomTool.getInstance().labInfoDao();
+    }
+
+    public void insert(LabInfo labInfo) {
+        labInfoDao.insert(labInfo);
+    }
+
+    public LabInfo getLabInfo() {
+        return labInfoDao.getLabInfo();
+    }
+}

+ 29 - 0
app/src/main/java/xn/hxp/room/tool/UserInfoTool.java

@@ -0,0 +1,29 @@
+package xn.hxp.room.tool;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.bean.UserInfo;
+import xn.hxp.room.dao.LabInfoDao;
+import xn.hxp.room.dao.UserInfoDao;
+
+public enum UserInfoTool {
+    INSTANCE;
+
+    private UserInfoDao userInfoDao;
+
+    UserInfoTool() {
+        userInfoDao = RoomTool.getInstance().userInfoDao();
+    }
+
+    public void insert(UserInfo userInfo) {
+        userInfoDao.insert(userInfo);
+    }
+
+    public void deleteAll() {
+        userInfoDao.deleteAll();
+    }
+
+    public UserInfo getUserInfo() {
+        return userInfoDao.getUser();
+    }
+}

+ 29 - 0
app/src/main/java/xn/hxp/room/tool/cabinet/CabinetDoorAdminTool.java

@@ -0,0 +1,29 @@
+package xn.hxp.room.tool.cabinet;
+
+import java.util.List;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.cabinet.CabinetDoorAdmin;
+import xn.hxp.room.dao.cabinet.CabinetDoorAdminDao;
+
+public enum CabinetDoorAdminTool {
+    INSTANCE;
+
+    private CabinetDoorAdminDao cabinetDoorAdminDao;
+
+    CabinetDoorAdminTool() {
+        cabinetDoorAdminDao = RoomTool.getInstance().cabinetDoorAdminDao();
+    }
+
+    public void insert(List<CabinetDoorAdmin> cabinetDoorList) {
+        cabinetDoorAdminDao.insert(cabinetDoorList);
+    }
+
+    public void deleteAll() {
+        cabinetDoorAdminDao.deleteAll();
+    }
+
+    public List<CabinetDoorAdmin> getCabinetDoorAdminList() {
+        return cabinetDoorAdminDao.getCabinetDoorAdminList();
+    }
+}

+ 29 - 0
app/src/main/java/xn/hxp/room/tool/cabinet/CabinetDoorLockTool.java

@@ -0,0 +1,29 @@
+package xn.hxp.room.tool.cabinet;
+
+import java.util.List;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.cabinet.CabinetDoorLock;
+import xn.hxp.room.dao.cabinet.CabinetDoorLockDao;
+
+public enum CabinetDoorLockTool {
+    INSTANCE;
+
+    private CabinetDoorLockDao cabinetDoorLockDao;
+
+    CabinetDoorLockTool() {
+        cabinetDoorLockDao = RoomTool.getInstance().cabinetDoorLockDao();
+    }
+
+    public void insert(List<CabinetDoorLock> cabinetDoorLockList) {
+        cabinetDoorLockDao.insert(cabinetDoorLockList);
+    }
+
+    public void deleteAll() {
+        cabinetDoorLockDao.deleteAll();
+    }
+
+    public List<CabinetDoorLock> getCabinetDoorLockList() {
+        return cabinetDoorLockDao.getCabinetDoorLockList();
+    }
+}

+ 31 - 0
app/src/main/java/xn/hxp/room/tool/cabinet/CabinetDoorTool.java

@@ -0,0 +1,31 @@
+package xn.hxp.room.tool.cabinet;
+
+import java.util.List;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.cabinet.Cabinet;
+import xn.hxp.room.bean.cabinet.CabinetDoor;
+import xn.hxp.room.dao.cabinet.CabinetDao;
+import xn.hxp.room.dao.cabinet.CabinetDoorDao;
+
+public enum CabinetDoorTool {
+    INSTANCE;
+
+    private CabinetDoorDao cabinetDoorDao;
+
+    CabinetDoorTool() {
+        cabinetDoorDao = RoomTool.getInstance().cabinetDoorDao();
+    }
+
+    public void insert(List<CabinetDoor> cabinetDoorList) {
+        cabinetDoorDao.insert(cabinetDoorList);
+    }
+
+    public void deleteAll() {
+        cabinetDoorDao.deleteAll();
+    }
+
+    public List<CabinetDoor> getCabinetDoorList() {
+        return cabinetDoorDao.getCabinetDoorList();
+    }
+}

+ 29 - 0
app/src/main/java/xn/hxp/room/tool/cabinet/CabinetTool.java

@@ -0,0 +1,29 @@
+package xn.hxp.room.tool.cabinet;
+
+import java.util.List;
+
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.cabinet.Cabinet;
+import xn.hxp.room.dao.cabinet.CabinetDao;
+
+public enum CabinetTool {
+    INSTANCE;
+
+    private CabinetDao cabinetDao;
+
+    CabinetTool() {
+        cabinetDao = RoomTool.getInstance().cabinetDao();
+    }
+
+    public void insert(List<Cabinet> cabinetList) {
+        cabinetDao.insert(cabinetList);
+    }
+
+    public void deleteAll() {
+        cabinetDao.deleteAll();
+    }
+
+    public List<Cabinet> getCabinetList() {
+        return cabinetDao.getCabinetList();
+    }
+}

+ 127 - 0
app/src/main/java/xn/hxp/ui/login/LoginActivity.java

@@ -0,0 +1,127 @@
+package xn.hxp.ui.login;
+
+import android.os.CountDownTimer;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.viewbinding.ViewBinding;
+
+import com.blankj.utilcode.util.FragmentUtils;
+import com.blankj.utilcode.util.LogUtils;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import xn.hxp.R;
+import xn.hxp.base.BaseActivity;
+import xn.hxp.databinding.ActivityLoginBinding;
+import xn.hxp.ui.login.callback.OnFaceScanResultCallback;
+import xn.hxp.ui.login.fragment.LoginCardFragment;
+import xn.hxp.ui.login.fragment.LoginFaceFragment;
+import xn.hxp.ui.login.fragment.LoginQrFragment;
+import xn.hxp.util.AudioPlayerTool;
+import xn.hxp.util.CardReaderHelper;
+
+public class LoginActivity extends BaseActivity {
+    private ActivityLoginBinding binding;
+    private FragmentManager fragmentManager;
+    protected LoginFaceFragment loginFaceFragment;
+    protected OnFaceScanResultCallback onFaceScanResultCallback;
+    private LoginActivityHelper loginActivityHelper;
+    private LoginCardFragment loginCardFragment;
+    private LoginQrFragment loginQrFragment;
+    protected CardReaderHelper cardReaderHelper;
+    protected CountDownTimer scanCodeCDT;
+
+    // 是否已经登录成功
+    protected final AtomicBoolean isLogged = new AtomicBoolean(false);
+    // 是否正在刷卡
+    protected final AtomicBoolean isCarding = new AtomicBoolean(false);
+    // 是否正在扫码
+    protected final AtomicBoolean isScanning = new AtomicBoolean(false);
+
+    @Override
+    protected ViewBinding setViewBinding() {
+        return binding = ActivityLoginBinding.inflate(getLayoutInflater());
+    }
+
+    @Override
+    protected void onInit() {
+        binding.includeTitle.loginLL.setVisibility(View.GONE);
+        loginActivityHelper = new LoginActivityHelper(this, binding, cardReaderHelper);
+
+        fragmentManager = getSupportFragmentManager();
+        loginFaceFragment = new LoginFaceFragment(onFaceScanResultCallback);
+        loginCardFragment = new LoginCardFragment();
+        loginQrFragment = new LoginQrFragment();
+        FragmentUtils.add(fragmentManager, loginFaceFragment, binding.coordinatorFL.getId());
+
+        binding.card.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                isScanning.set(false);
+                binding.face.setVisibility(View.VISIBLE);
+                binding.card.setVisibility(View.GONE);
+                binding.qr.setVisibility(View.VISIBLE);
+                FragmentUtils.replace(fragmentManager, loginCardFragment, binding.coordinatorFL.getId());
+                binding.hint.setText("请在刷卡区域进行刷卡验证");
+            }
+        });
+
+        binding.face.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                isScanning.set(false);
+                binding.face.setVisibility(View.GONE);
+                binding.card.setVisibility(View.VISIBLE);
+                binding.qr.setVisibility(View.VISIBLE);
+                binding.hint.setText("请正对屏幕并使脸位于取景框内\n请保持光线充足,避免光照过强或过弱");
+                FragmentUtils.replace(fragmentManager, loginFaceFragment, binding.coordinatorFL.getId());
+                AudioPlayerTool.getInstance().play(R.raw.login_face_verify_hint);
+            }
+        });
+
+        binding.qr.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                isScanning.set(true);
+                binding.face.setVisibility(View.VISIBLE);
+                binding.card.setVisibility(View.VISIBLE);
+                binding.qr.setVisibility(View.GONE);
+                binding.hint.setText("请打开微信扫描屏幕二维码进行扫码验证");
+                FragmentUtils.replace(fragmentManager, loginQrFragment, binding.coordinatorFL.getId());
+            }
+        });
+
+        // 人脸识别提醒
+        AudioPlayerTool.getInstance().play(R.raw.login_face_verify_hint);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (!isCarding.get()) {
+            cardReaderHelper.handleKeyEvent(event);
+        }
+        return super.dispatchKeyEvent(event);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        cardReaderHelper.release();
+        if (scanCodeCDT != null) {
+            scanCodeCDT.cancel();
+        }
+    }
+
+    @Override
+    protected void cdFinish() {
+        finish();
+    }
+
+    @Override
+    protected void cdTime(int cd) {
+        binding.back.setText("返回" + cd + "s");
+    }
+}

+ 182 - 0
app/src/main/java/xn/hxp/ui/login/LoginActivityHelper.java

@@ -0,0 +1,182 @@
+package xn.hxp.ui.login;
+
+import android.annotation.SuppressLint;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.os.CountDownTimer;
+
+import androidx.annotation.NonNull;
+
+import com.blankj.utilcode.util.ImageUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.TimeUtils;
+import com.google.mlkit.vision.face.Face;
+import com.hjq.http.EasyHttp;
+import com.hjq.http.listener.OnHttpListener;
+import com.hjq.http.listener.OnUpdateListener;
+import com.king.camera.scan.AnalyzeResult;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.Locale;
+
+import xn.hxp.R;
+import xn.hxp.databinding.ActivityLoginBinding;
+import xn.hxp.http.HttpData;
+import xn.hxp.http.api.GetCardLoginApi;
+import xn.hxp.http.api.GetScanCodeApi;
+import xn.hxp.http.api.PostFaceByPicApi;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.bean.UserInfo;
+import xn.hxp.room.tool.DeviceInfoTool;
+import xn.hxp.room.tool.LabInfoTool;
+import xn.hxp.room.tool.UserInfoTool;
+import xn.hxp.ui.login.callback.OnFaceScanResultCallback;
+import xn.hxp.util.AudioPlayerTool;
+import xn.hxp.util.CardReaderHelper;
+import xn.hxp.view.dialog.LoginVerifySuccessDialog;
+
+
+public class LoginActivityHelper {
+    private LoginActivity loginActivity;
+    private ActivityLoginBinding binding;
+    private DeviceInfo deviceInfo;
+    private LabInfo labInfo;
+
+    public LoginActivityHelper(LoginActivity loginActivity, ActivityLoginBinding binding, CardReaderHelper cardReaderHelper) {
+        this.loginActivity = loginActivity;
+        this.binding = binding;
+        deviceInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+        labInfo = LabInfoTool.INSTANCE.getLabInfo();
+        // 人脸登录
+        loginActivity.onFaceScanResultCallback = new OnFaceScanResultCallback() {
+            @Override
+            public void onScanResultCallback(@NonNull AnalyzeResult<List<Face>> result) {
+                faceResultHandle(result.getBitmap());
+            }
+        };
+
+        // 刷卡登录
+        loginActivity.cardReaderHelper = new CardReaderHelper(new CardReaderHelper.OnCardReadListener() {
+            @Override
+            public void onCardRead(String cardData) {
+                cardResultHandle(cardData);
+            }
+        });
+
+        // 扫码检查
+        loginActivity.scanCodeCDT = new CountDownTimer(3000, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+
+            }
+
+            @Override
+            public void onFinish() {
+                if (loginActivity.isScanning.get()) {
+                    scanResultHandle();
+                    return;
+                }
+                loginActivity.scanCodeCDT.start();
+            }
+        };
+        loginActivity.scanCodeCDT.start();
+
+    }
+
+    private void scanResultHandle() {
+        EasyHttp.get(loginActivity).api(new GetScanCodeApi(deviceInfo.getSn(), TimeUtils.getNowString(new SimpleDateFormat("HHmmssSSS", Locale.getDefault())), labInfo.getSubjectId())).request(new OnHttpListener<HttpData<UserInfo>>() {
+            @Override
+            public void onHttpSuccess(@NonNull HttpData<UserInfo> result) {
+                if (result.isRequestSuccess()) {
+                    UserInfo userInfo = result.getData();
+                    if (null != userInfo && !loginActivity.isLogged.get()) {
+                        loginActivity.isScanning.set(false);
+                        UserInfoTool.INSTANCE.insert(userInfo);
+                        LoginVerifySuccessDialog loginVerifySuccessDialog = new LoginVerifySuccessDialog(loginActivity, 3);
+                        loginVerifySuccessDialog.setOnDismissListener(dialog -> loginActivity.finish());
+                        loginVerifySuccessDialog.show();
+                        loginActivity.isLogged.set(true);
+                        return;
+                    }
+                }
+                loginActivity.scanCodeCDT.start();
+                loginActivity.showErr(result.getMessage());
+            }
+
+            @Override
+            public void onHttpFail(@NonNull Throwable throwable) {
+                loginActivity.scanCodeCDT.start();
+                loginActivity.showErr(throwable.getMessage());
+            }
+        });
+    }
+
+    private void cardResultHandle(String cardData) {
+        loginActivity.isCarding.set(true);
+        EasyHttp.get(loginActivity).api(new GetCardLoginApi(cardData, labInfo.getSubjectId())).request(new OnHttpListener<HttpData<UserInfo>>() {
+            @Override
+            public void onHttpSuccess(@NonNull HttpData<UserInfo> result) {
+                if (result.isRequestSuccess()) {
+                    UserInfo userInfo = result.getData();
+                    if (null != userInfo && !loginActivity.isLogged.get()) {
+
+                        UserInfoTool.INSTANCE.insert(userInfo);
+                        LoginVerifySuccessDialog loginVerifySuccessDialog = new LoginVerifySuccessDialog(loginActivity, 3);
+                        loginVerifySuccessDialog.setOnDismissListener(dialog -> loginActivity.finish());
+                        loginVerifySuccessDialog.show();
+                        loginActivity.isLogged.set(true);
+                        return;
+                    }
+                }
+                loginActivity.showErr(result.getMessage());
+                loginActivity.isCarding.set(false);
+            }
+
+            @Override
+            public void onHttpFail(@NonNull Throwable throwable) {
+                loginActivity.isCarding.set(false);
+                loginActivity.showErr(throwable.getMessage());
+            }
+        });
+    }
+
+    private void faceResultHandle(Bitmap bitmap) {
+        @SuppressLint("SdCardPath") String saveFile = "/sdcard/DCIM/face.jpg";
+        ImageUtils.save(bitmap, saveFile, Bitmap.CompressFormat.JPEG);
+        File faceFile = new File(saveFile);
+        EasyHttp.post(loginActivity).api(new PostFaceByPicApi(faceFile, labInfo.getSubjectId())).request(new OnUpdateListener<HttpData<UserInfo>>() {
+            @Override
+            public void onUpdateProgressChange(int progress) {
+                LogUtils.d(progress);
+            }
+
+            @Override
+            public void onUpdateSuccess(@NonNull HttpData<UserInfo> result) {
+                if (result.isRequestSuccess()) {
+                    UserInfo userInfo = result.getData();
+                    if (null != userInfo && !loginActivity.isLogged.get()) {
+                        UserInfoTool.INSTANCE.insert(userInfo);
+                        LoginVerifySuccessDialog loginVerifySuccessDialog = new LoginVerifySuccessDialog(loginActivity, 3, faceFile);
+                        loginVerifySuccessDialog.setOnDismissListener(dialog -> loginActivity.finish());
+                        loginVerifySuccessDialog.show();
+                        loginActivity.isLogged.set(true);
+                    }
+                    return;
+                }
+                loginActivity.showErr(result.getMessage());
+                ThreadUtils.runOnUiThreadDelayed(() -> loginActivity.loginFaceFragment.setAnalyzeImage(true), 1500);
+            }
+
+            @Override
+            public void onUpdateFail(@NonNull Throwable throwable) {
+                loginActivity.showErr(throwable.getMessage());
+                ThreadUtils.runOnUiThreadDelayed(() -> loginActivity.loginFaceFragment.setAnalyzeImage(true), 1500);
+            }
+        });
+    }
+
+}

+ 24 - 0
app/src/main/java/xn/hxp/ui/login/callback/OnFaceScanResultCallback.java

@@ -0,0 +1,24 @@
+package xn.hxp.ui.login.callback;
+
+import androidx.annotation.NonNull;
+
+import com.google.mlkit.vision.face.Face;
+import com.king.camera.scan.AnalyzeResult;
+
+import java.util.List;
+
+public interface OnFaceScanResultCallback {
+    /**
+     * 扫描结果回调
+     *
+     * @param result 扫描结果
+     */
+    void onScanResultCallback(@NonNull AnalyzeResult<List<Face>> result);
+
+    /**
+     * 扫描结果识别失败时触发此回调方法
+     */
+    default void onScanResultFailure() {
+
+    }
+}

+ 73 - 0
app/src/main/java/xn/hxp/ui/login/ext/BitmapExt.java

@@ -0,0 +1,73 @@
+package xn.hxp.ui.login.ext;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ImageDecoder;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.MediaStore;
+
+import com.king.logx.LogX;
+
+import java.io.IOException;
+
+public class BitmapExt {
+
+    public interface DrawRectBlock {
+        void drawBlock(Canvas canvas, Paint paint);
+    }
+
+    public static Bitmap drawRect(Bitmap source, DrawRectBlock block) {
+        if (source == null) return null;
+
+        // 创建新的 Bitmap 对象 [9,10](@ref)
+        Bitmap result = Bitmap.createBitmap(
+                source.getWidth(),
+                source.getHeight(),
+                Bitmap.Config.ARGB_8888
+        );
+
+        try {
+            Canvas canvas = new Canvas(result);
+            canvas.drawBitmap(source, 0f, 0f, null);
+
+            Paint paint = new Paint();
+            paint.setStrokeWidth(6f);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setColor(Color.RED);
+
+            block.drawBlock(canvas, paint);
+            canvas.save();
+            canvas.restore();
+        } catch (Exception e) {
+            LogX.w(e);
+        }
+        return result;
+    }
+
+    public static Bitmap getBitmap(Context context, Uri uri) {
+        try {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                ImageDecoder.Source source = ImageDecoder.createSource(
+                        context.getContentResolver(),
+                        uri
+                );
+                return ImageDecoder.decodeBitmap(source).copy(
+                        Bitmap.Config.ARGB_8888,
+                        true
+                );
+            } else {
+                return MediaStore.Images.Media.getBitmap(
+                        context.getContentResolver(),
+                        uri
+                );
+            }
+        } catch (IOException e) {
+            LogX.w(e);
+            return null;
+        }
+    }
+}

+ 28 - 0
app/src/main/java/xn/hxp/ui/login/fragment/LoginCardFragment.java

@@ -0,0 +1,28 @@
+package xn.hxp.ui.login.fragment;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.viewbinding.ViewBinding;
+
+import xn.hxp.base.BaseFragment;
+import xn.hxp.databinding.FragmentLoginCardBinding;
+
+public class LoginCardFragment extends BaseFragment {
+    private FragmentLoginCardBinding binding;
+
+    @Override
+    protected ViewBinding setViewBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
+        binding = FragmentLoginCardBinding.inflate(inflater);
+        return binding;
+    }
+
+    @Override
+    protected void onInit() {
+
+    }
+
+
+}

+ 74 - 0
app/src/main/java/xn/hxp/ui/login/fragment/LoginFaceFragment.java

@@ -0,0 +1,74 @@
+package xn.hxp.ui.login.fragment;
+
+import android.graphics.ImageFormat;
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.core.CameraSelector;
+
+import com.blankj.utilcode.util.LogUtils;
+import com.google.mlkit.vision.face.Face;
+import com.king.camera.scan.AnalyzeResult;
+import com.king.camera.scan.CameraScan;
+import com.king.camera.scan.config.CameraConfig;
+import com.king.camera.scan.config.CameraConfigFactory;
+import com.king.mlkit.vision.face.FaceCameraScanFragment;
+
+import java.util.List;
+
+import xn.hxp.R;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.tool.DeviceInfoTool;
+import xn.hxp.ui.login.callback.OnFaceScanResultCallback;
+
+public class LoginFaceFragment extends FaceCameraScanFragment {
+    private OnFaceScanResultCallback onFaceScanResultCallback;
+
+    public LoginFaceFragment(OnFaceScanResultCallback onFaceScanResultCallback) {
+        this.onFaceScanResultCallback = onFaceScanResultCallback;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+    }
+
+    @Override
+    public void initCameraScan(@NonNull CameraScan<List<Face>> cameraScan) {
+        super.initCameraScan(cameraScan);
+        // 相机配置
+        DeviceInfo deviceInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+        LogUtils.json(deviceInfo);
+        cameraScan.setPlayBeep(true) // 设置是否播放音效,默认为false
+                .setVibrate(true) // 设置是否震动,默认为false
+                .setCameraConfig(CameraConfigFactory.createDefaultCameraConfig(requireContext(),
+                        deviceInfo.isFrontCamera() ? CameraSelector.LENS_FACING_FRONT : CameraSelector.LENS_FACING_BACK)) // 设置相机配置信息
+                .setNeedTouchZoom(false) // 支持多指触摸捏合缩放,默认为true
+                .setAnalyzeImage(true); // 设置是否分析图片,默认为true。如果设置为false,相当于关闭了扫描识别功能
+    }
+
+    @Override
+    public int getLayoutId() {
+        return R.layout.fragment_login_face;
+    }
+
+    @Override
+    public void onScanResultCallback(@NonNull AnalyzeResult<List<Face>> result) {
+        getCameraScan().setAnalyzeImage(false);
+        onFaceScanResultCallback.onScanResultCallback(result);
+    }
+
+    @Override
+    public void onScanResultFailure() {
+        super.onScanResultFailure();
+        onFaceScanResultCallback.onScanResultFailure();
+    }
+
+    public void setAnalyzeImage(boolean isAnalyze) {
+        getCameraScan().setAnalyzeImage(isAnalyze);
+    }
+}

+ 73 - 0
app/src/main/java/xn/hxp/ui/login/fragment/LoginQrFragment.java

@@ -0,0 +1,73 @@
+package xn.hxp.ui.login.fragment;
+
+import android.graphics.Bitmap;
+import android.os.CountDownTimer;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.viewbinding.ViewBinding;
+
+import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.TimeUtils;
+import com.hjq.http.EasyHttp;
+import com.hjq.http.listener.OnHttpListener;
+import com.king.zxing.util.CodeUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+import xn.hxp.base.BaseFragment;
+import xn.hxp.databinding.FragmentLoginQrBinding;
+import xn.hxp.http.HttpData;
+import xn.hxp.http.api.GetScanCodeApi;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.bean.UserInfo;
+import xn.hxp.room.tool.DeviceInfoTool;
+import xn.hxp.room.tool.LabInfoTool;
+
+public class LoginQrFragment extends BaseFragment {
+    private FragmentLoginQrBinding binding;
+    private DeviceInfo deviceInfo;
+    private LabInfo labInfo;
+
+
+    @Override
+    protected ViewBinding setViewBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
+        return binding = FragmentLoginQrBinding.inflate(inflater);
+    }
+
+    @Override
+    protected void onInit() {
+        ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Bitmap>() {
+            @Override
+            public Bitmap doInBackground() throws Throwable {
+                deviceInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+                labInfo = LabInfoTool.INSTANCE.getLabInfo();
+                String text = deviceInfo.getBaseUrl() + "/api/?code=" + TimeUtils.getNowString(new SimpleDateFormat("HHmmssSSS", Locale.getDefault())) + "&subId=" + labInfo.getSubjectId() + "&type=11&macId=" + deviceInfo.getSn();
+                return CodeUtils.createQRCode(text, 200);
+            }
+
+            @Override
+            public void onSuccess(Bitmap result) {
+                if (null != result) {
+                    binding.qr.setImageBitmap(result);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+}

+ 68 - 0
app/src/main/java/xn/hxp/ui/main/MainActivity.java

@@ -0,0 +1,68 @@
+package xn.hxp.ui.main;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.view.View;
+
+import androidx.viewbinding.ViewBinding;
+
+import com.blankj.utilcode.util.ActivityUtils;
+
+import xn.hxp.base.BaseActivity;
+import xn.hxp.databinding.ActivityMainBinding;
+import xn.hxp.databinding.IncludeMainLBinding;
+import xn.hxp.databinding.IncludeMainRBinding;
+import xn.hxp.receiver.MinuteTickReceiver;
+import xn.hxp.ui.start.SettingActivity;
+
+public class MainActivity extends BaseActivity {
+
+    private ActivityMainBinding binding;
+    private MainActivityLHelper mainActivityLHelper;
+    private MainActivityRHelper mainActivityRHelper;
+    private MainActivityTitleHelper mainActivityTitleHelper;
+
+    private MinuteTickReceiver minuteTickReceiver;
+
+    @Override
+    protected ViewBinding setViewBinding() {
+        return binding = ActivityMainBinding.inflate(getLayoutInflater());
+    }
+
+    @Override
+    protected void onInit() {
+        mainActivityLHelper = new MainActivityLHelper(this, binding.includeL);
+        mainActivityRHelper = new MainActivityRHelper(this, binding.includeR);
+        mainActivityTitleHelper = new MainActivityTitleHelper(this, binding.includeTitle);
+
+        // 进入配置页面
+        binding.includeTitle.titlePic.setOnLongClickListener(v -> {
+            ActivityUtils.startActivity(SettingActivity.class);
+            return true;
+        });
+
+        // 注册分钟广播
+        IntentFilter filter = new IntentFilter(Intent.ACTION_TIME_TICK);
+        minuteTickReceiver = new MinuteTickReceiver();
+        registerReceiver(minuteTickReceiver, filter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        // 解绑分钟广播
+        if (minuteTickReceiver != null) {
+            unregisterReceiver(minuteTickReceiver);
+        }
+    }
+
+    @Override
+    protected void cdFinish() {
+
+    }
+
+    @Override
+    protected void cdTime(int cd) {
+
+    }
+}

+ 12 - 0
app/src/main/java/xn/hxp/ui/main/MainActivityHelper.java

@@ -0,0 +1,12 @@
+package xn.hxp.ui.main;
+
+import com.blankj.utilcode.util.NetworkUtils;
+
+import okhttp3.HttpUrl;
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.dao.DeviceInfoDao;
+
+public class MainActivityHelper {
+
+}

+ 120 - 0
app/src/main/java/xn/hxp/ui/main/MainActivityLHelper.java

@@ -0,0 +1,120 @@
+package xn.hxp.ui.main;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.blankj.utilcode.util.ThreadUtils;
+import com.hjq.http.EasyHttp;
+import com.hjq.http.listener.OnHttpListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xn.hxp.databinding.IncludeMainLBinding;
+import xn.hxp.http.HttpData;
+import xn.hxp.http.api.GetCabinetListApi;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.bean.cabinet.Cabinet;
+import xn.hxp.room.bean.cabinet.CabinetDoor;
+import xn.hxp.room.bean.cabinet.CabinetDoorAdmin;
+import xn.hxp.room.bean.cabinet.CabinetDoorLock;
+import xn.hxp.room.tool.LabInfoTool;
+import xn.hxp.room.tool.cabinet.CabinetDoorAdminTool;
+import xn.hxp.room.tool.cabinet.CabinetDoorLockTool;
+import xn.hxp.room.tool.cabinet.CabinetDoorTool;
+import xn.hxp.room.tool.cabinet.CabinetTool;
+import xn.hxp.ui.main.adapter.CabinetAdapter;
+
+public class MainActivityLHelper {
+    private final MainActivity mainActivity;
+    private final IncludeMainLBinding bindingL;
+    private final LabInfo labInfo;
+    private final List<Cabinet> cabinetList = new ArrayList<>();
+
+    public MainActivityLHelper(MainActivity mainActivity, IncludeMainLBinding includeMainLBinding) {
+        this.mainActivity = mainActivity;
+        this.bindingL = includeMainLBinding;
+        labInfo = LabInfoTool.INSTANCE.getLabInfo();
+        bindingL.cabinetVP.registerLifecycleObserver(mainActivity.getLifecycle()).setAdapter(new CabinetAdapter()).create(cabinetList);
+        updateCabinet();
+
+        bindingL.last.setOnClickListener(v -> bindingL.cabinetVP.previousPage());
+        bindingL.next.setOnClickListener(v -> bindingL.cabinetVP.nextPage());
+    }
+
+    /**
+     * 更新柜子
+     */
+    private void updateCabinet() {
+        mainActivity.showLoading("正在查询实验室柜信息...");
+        EasyHttp.get(mainActivity).api(new GetCabinetListApi(labInfo.getSubjectId())).request(new OnHttpListener<HttpData<List<Cabinet>>>() {
+            @Override
+            public void onHttpSuccess(@NonNull HttpData<List<Cabinet>> result) {
+                if (result.isRequestSuccess()) {
+                    List<Cabinet> postCabinetList = result.getData();
+                    if (null != postCabinetList) {
+                        cabinetList.clear();
+                        cabinetList.addAll(postCabinetList);
+                        ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
+                            @Override
+                            public Object doInBackground() throws Throwable {
+                                // 化学品柜入库
+                                CabinetTool.INSTANCE.insert(cabinetList);
+                                for (int i = 0; i < cabinetList.size(); i++) {
+                                    Cabinet cabinet = cabinetList.get(i);
+                                    List<CabinetDoor> cabinetDoorList = cabinet.getCabinetDoorVoList();
+                                    if (null != cabinetDoorList) {
+                                        // 化学品柜门入库
+                                        CabinetDoorTool.INSTANCE.insert(cabinetDoorList);
+                                        for (int j = 0; j < cabinetDoorList.size(); j++) {
+                                            CabinetDoor cabinetDoor = cabinetDoorList.get(j);
+                                            // 化学品柜门管理员入库
+                                            List<CabinetDoorAdmin> cabinetAdminVoList = cabinetDoor.getCabinetAdminVoList();
+                                            if (cabinetAdminVoList != null) {
+                                                CabinetDoorAdminTool.INSTANCE.insert(cabinetAdminVoList);
+                                            }
+                                            // 化学品柜锁入库
+                                            List<CabinetDoorLock> cabinetDoorLockList = cabinetDoor.getCabinetLockVoList();
+                                            if (cabinetDoorLockList != null) {
+                                                CabinetDoorLockTool.INSTANCE.insert(cabinetDoorLockList);
+                                            }
+                                        }
+                                    }
+                                }
+                                return null;
+                            }
+
+                            @Override
+                            public void onSuccess(Object result) {
+                                if (cabinetList.size() > 1) {
+                                    bindingL.last.setVisibility(View.VISIBLE);
+                                    bindingL.next.setVisibility(View.VISIBLE);
+                                } else {
+                                    bindingL.last.setVisibility(View.GONE);
+                                    bindingL.next.setVisibility(View.GONE);
+                                }
+                                bindingL.cabinetVP.refreshData(cabinetList);
+                                mainActivity.dismissLoading();
+                            }
+
+                            @Override
+                            public void onFail(Throwable t) {
+                                super.onFail(t);
+                                mainActivity.dismissLoading();
+                            }
+                        });
+                    }
+                } else {
+                    mainActivity.dismissLoading();
+                }
+            }
+
+            @Override
+            public void onHttpFail(@NonNull Throwable throwable) {
+                mainActivity.dismissLoading();
+                mainActivity.showErr(throwable.getMessage());
+            }
+        });
+    }
+}

+ 54 - 0
app/src/main/java/xn/hxp/ui/main/MainActivityRHelper.java

@@ -0,0 +1,54 @@
+package xn.hxp.ui.main;
+
+import android.view.View;
+
+import com.blankj.utilcode.util.ActivityUtils;
+
+import xn.hxp.databinding.IncludeMainRBinding;
+import xn.hxp.room.RoomTool;
+import xn.hxp.room.bean.UserInfo;
+import xn.hxp.room.dao.DeviceInfoDao;
+import xn.hxp.room.tool.UserInfoTool;
+import xn.hxp.ui.login.LoginActivity;
+
+public class MainActivityRHelper implements View.OnClickListener {
+    private MainActivity mainActivity;
+    private IncludeMainRBinding bindingR;
+    private UserInfo userInfo;
+
+    public MainActivityRHelper(MainActivity mainActivity, IncludeMainRBinding includeMainRBinding) {
+        this.mainActivity = mainActivity;
+        this.bindingR = includeMainRBinding;
+        setCardClick();
+    }
+
+    private void setCardClick() {
+        // 存储
+        bindingR.save.setOnClickListener(this);
+        // 废弃
+        bindingR.discard.setOnClickListener(this);
+        // 查询
+        bindingR.inquire.setOnClickListener(this);
+        // 标签
+        bindingR.label.setOnClickListener(this);
+        // 领用
+        bindingR.claim.setOnClickListener(this);
+        // 归还
+        bindingR.returnAIV.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        userInfo = UserInfoTool.INSTANCE.getUserInfo();
+        if (null == userInfo) {
+            ActivityUtils.startActivity(LoginActivity.class);
+        } else {
+            // 存储
+            if (v == bindingR.save) {
+            }
+            // 废弃
+            else if (v == bindingR.discard) {
+            }
+        }
+    }
+}

+ 27 - 0
app/src/main/java/xn/hxp/ui/main/MainActivityTitleHelper.java

@@ -0,0 +1,27 @@
+package xn.hxp.ui.main;
+
+import android.text.TextUtils;
+
+import com.bumptech.glide.Glide;
+
+import xn.hxp.databinding.IncludeTitleBinding;
+import xn.hxp.http.HttpTool;
+import xn.hxp.room.bean.BaseConfig;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.tool.BaseConfigTool;
+import xn.hxp.room.tool.LabInfoTool;
+
+public class MainActivityTitleHelper {
+    private MainActivity mainActivity;
+    private IncludeTitleBinding binding;
+    private LabInfo labInfo;
+    private BaseConfig baseConfig;
+
+    public MainActivityTitleHelper(MainActivity mainActivity, IncludeTitleBinding binding) {
+        this.mainActivity = mainActivity;
+        this.binding = binding;
+        labInfo = LabInfoTool.INSTANCE.getLabInfo();
+        baseConfig = BaseConfigTool.INSTANCE.getBaseConfig();
+
+    }
+}

+ 123 - 0
app/src/main/java/xn/hxp/ui/main/adapter/CabinetAdapter.java

@@ -0,0 +1,123 @@
+package xn.hxp.ui.main.adapter;
+
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import androidx.appcompat.widget.AppCompatTextView;
+
+import com.kongzue.dialogx.dialogs.PopTip;
+import com.zhpan.bannerview.BaseBannerAdapter;
+import com.zhpan.bannerview.BaseViewHolder;
+import com.zhpan.bannerview.utils.BannerUtils;
+
+import java.util.List;
+
+import xn.hxp.R;
+import xn.hxp.room.bean.cabinet.Cabinet;
+import xn.hxp.room.bean.cabinet.CabinetDoor;
+import xn.hxp.room.bean.cabinet.CabinetDoorAdmin;
+
+public class CabinetAdapter extends BaseBannerAdapter<Cabinet> {
+    private final StringBuffer stringBuffer = new StringBuffer();
+
+    @Override
+    protected void bindData(BaseViewHolder<Cabinet> holder, Cabinet data, int position, int pageSize) {
+        int adapterPosition = holder.getAdapterPosition();
+        int realPosition = BannerUtils.getRealPosition(position, adapterPosition);
+
+        List<CabinetDoor> cabinetDoorList = data.getCabinetDoorVoList();
+        // 柜门名字
+        AppCompatTextView cabinetNameTV = holder.findViewById(R.id.cabinetName);
+        cabinetNameTV.setText(data.getCabinetName());
+        // 左柜门布局
+        RelativeLayout doorRLL = holder.findViewById(R.id.doorRLL);
+        doorRLL.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                CabinetDoor cabinetDoor = cabinetDoorList.get(0);
+                PopTip.show("点击左柜门:" + data.getCabinetName() + "__" + cabinetDoor.getDoorName());
+            }
+        });
+        // 右柜门布局
+        RelativeLayout doorRLR = holder.findViewById(R.id.doorRLR);
+        doorRLR.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                CabinetDoor cabinetDoor = cabinetDoorList.get(1);
+                PopTip.show("点击右柜门:" + data.getCabinetName() + "__" + cabinetDoor.getDoorName());
+            }
+        });
+        if (null != cabinetDoorList && !cabinetDoorList.isEmpty()) {
+            // 左柜门
+            doorRLL.setVisibility(View.VISIBLE);
+            CabinetDoor cabinetDoorL = cabinetDoorList.get(0);
+            // 左柜门名字
+            AppCompatTextView doorName1 = holder.findViewById(R.id.doorName1);
+            doorName1.setText(TextUtils.isEmpty(cabinetDoorL.getDoorName()) ? "" : cabinetDoorL.getDoorName());
+            // 左柜门化学品总数
+            AppCompatTextView doorHxpSize1 = holder.findViewById(R.id.doorHxpSize1);
+            doorHxpSize1.setText(String.valueOf(cabinetDoorL.getStockNum()));
+            // 左柜门管理员
+            AppCompatTextView doorAdmin1 = holder.findViewById(R.id.doorAdmin1);
+            List<CabinetDoorAdmin> cabinetDoorAdminLList = cabinetDoorL.getCabinetAdminVoList();
+            if (null != cabinetDoorAdminLList && !cabinetDoorAdminLList.isEmpty()) {
+                stringBuffer.setLength(0);
+                for (int i = 0; i < cabinetDoorAdminLList.size(); i++) {
+                    CabinetDoorAdmin cabinetDoorAdmin = cabinetDoorAdminLList.get(i);
+                    String userName = cabinetDoorAdmin.getUserName();
+                    if (!TextUtils.isEmpty(userName)) {
+                        stringBuffer.append(userName);
+                        if (i != cabinetDoorAdminLList.size() - 1) {  // 不是最后一个元素
+                            stringBuffer.append("\n");  // 添加分隔符
+                        }
+                    }
+                }
+                doorAdmin1.setText(stringBuffer.toString());
+            } else {
+                doorAdmin1.setText("");
+            }
+
+            if (cabinetDoorList.size() > 1) {
+                // 右柜门
+                doorRLR.setVisibility(View.VISIBLE);
+                CabinetDoor cabinetDoorR = cabinetDoorList.get(1);
+                // 右柜门名字
+                AppCompatTextView doorName2 = holder.findViewById(R.id.doorName2);
+                doorName2.setText(TextUtils.isEmpty(cabinetDoorR.getDoorName()) ? "" : cabinetDoorR.getDoorName());
+                // 右柜门化学品总数
+                AppCompatTextView doorHxpSize2 = holder.findViewById(R.id.doorHxpSize2);
+                doorHxpSize2.setText(String.valueOf(cabinetDoorR.getStockNum()));
+                // 右柜门管理员
+                AppCompatTextView doorAdmin2 = holder.findViewById(R.id.doorAdmin2);
+                List<CabinetDoorAdmin> cabinetDoorAdminRList = cabinetDoorR.getCabinetAdminVoList();
+                if (null != cabinetDoorAdminRList && !cabinetDoorAdminRList.isEmpty()) {
+                    stringBuffer.setLength(0);
+                    for (int i = 0; i < cabinetDoorAdminRList.size(); i++) {
+                        CabinetDoorAdmin cabinetDoorAdmin = cabinetDoorAdminRList.get(i);
+                        String userName = cabinetDoorAdmin.getUserName();
+                        if (!TextUtils.isEmpty(userName)) {
+                            stringBuffer.append(userName);
+                            if (i != cabinetDoorAdminRList.size() - 1) {  // 不是最后一个元素
+                                stringBuffer.append("\n");  // 添加分隔符
+                            }
+                        }
+                    }
+                    doorAdmin2.setText(stringBuffer.toString());
+                } else {
+                    doorAdmin2.setText("");
+                }
+            } else {
+                doorRLR.setVisibility(View.GONE);
+            }
+        } else {
+            doorRLL.setVisibility(View.GONE);
+            doorRLR.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public int getLayoutId(int viewType) {
+        return R.layout.fragment_main_cabinet;
+    }
+}

+ 39 - 0
app/src/main/java/xn/hxp/ui/main/fragment/CabinetFragment.java

@@ -0,0 +1,39 @@
+package xn.hxp.ui.main.fragment;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.viewbinding.ViewBinding;
+
+import xn.hxp.base.BaseFragment;
+import xn.hxp.databinding.FragmentMainCabinetBinding;
+import xn.hxp.room.bean.cabinet.Cabinet;
+import xn.hxp.ui.main.MainActivity;
+
+public class CabinetFragment extends BaseFragment {
+    private FragmentMainCabinetBinding binding;
+    private MainActivity mainActivity;
+    private Cabinet cabinet;
+
+    public CabinetFragment(MainActivity mainActivity, Cabinet cabinet) {
+        this.cabinet = cabinet;
+        this.mainActivity = mainActivity;
+    }
+
+    @Override
+    protected ViewBinding setViewBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
+        binding = FragmentMainCabinetBinding.inflate(inflater);
+        return binding;
+    }
+
+    @Override
+    protected void onInit() {
+        updateCabinet(cabinet);
+    }
+
+    public void updateCabinet(Cabinet cabinet) {
+
+    }
+}

+ 116 - 0
app/src/main/java/xn/hxp/ui/start/SettingActivity.java

@@ -0,0 +1,116 @@
+package xn.hxp.ui.start;
+
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.NetworkUtils;
+import com.blankj.utilcode.util.SPUtils;
+import com.blankj.utilcode.util.ToastUtils;
+import com.kongzue.dialogx.dialogs.WaitDialog;
+
+import xn.hxp.databinding.ActivitySettingBinding;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.tool.DeviceInfoTool;
+import xn.hxp.util.Tool;
+import xn.hxp.view.ble.BleSelectorDialog;
+import xn.hxp.view.ble.SppTool;
+
+public class SettingActivity extends AppCompatActivity {
+
+    private ActivitySettingBinding binding;
+    private DeviceInfo deviceInfo;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = ActivitySettingBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+        deviceInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+
+        binding.varBT.setText("版本:" + AppUtils.getAppVersionName());
+        binding.snBT.setText("SN:" + Tool.INSTANCE.getSerialNumber());
+        binding.ipBT.setText(NetworkUtils.getIPAddress(true));
+        binding.rebootBT.setOnClickListener(v -> Tool.INSTANCE.cmd("reboot"));
+        binding.fileBrowserBT.setOnClickListener(v -> Tool.INSTANCE.openFileBrowser());
+        binding.settingBT.setOnClickListener(v -> Tool.INSTANCE.openSetting());
+        binding.barSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> Tool.INSTANCE.showBar(isChecked));
+        binding.httpUriET.setText(AppUtils.isAppDebug() ? "http://192.168.1.8" : "http://172.16.0.65");
+        binding.bleET.setText(TextUtils.isEmpty(deviceInfo.getBleMac()) ? "" : deviceInfo.getBleMac());
+        binding.bleNameTV.setText(TextUtils.isEmpty(deviceInfo.getBleName()) ? "" : deviceInfo.getBleName());
+
+        if (deviceInfo.isFrontCamera()) {
+            // 默认前置相机
+            binding.cameraRG.check(binding.frontRB.getId());
+        } else {
+            // 后置相机
+            binding.cameraRG.check(binding.backRB.getId());
+        }
+        // 管理员密码
+        binding.oldPasswordET.setText(TextUtils.isEmpty(deviceInfo.getAdminPwd()) ? "hxp" : deviceInfo.getAdminPwd());
+        // 域名配置
+        if (!TextUtils.isEmpty(deviceInfo.getBaseUrl())) {
+            binding.httpUriET.setText(deviceInfo.getBaseUrl());
+        }
+        // 测试地址输入
+        binding.httpUriTestBT.setOnClickListener(v -> binding.httpUriET.setText("http://192.168.1.8/api/"));
+        // 西农地址输入
+        binding.httpUriXnBT.setOnClickListener(v -> binding.httpUriET.setText("http://172.16.0.65/api/"));
+        // 蓝牙扫描
+        binding.scanBT.setOnClickListener(v -> {
+            BleSelectorDialog bleSelectorDialog = new BleSelectorDialog(SettingActivity.this);
+            bleSelectorDialog.setOnDismissListener(dialog -> {
+                DeviceInfo sqlInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+                binding.bleET.setText(TextUtils.isEmpty(sqlInfo.getBleMac()) ? "" : sqlInfo.getBleMac());
+                binding.bleNameTV.setText(TextUtils.isEmpty(sqlInfo.getBleName()) ? "" : sqlInfo.getBleName());
+                SppTool.INSTANCE.disconnect();
+            });
+
+            bleSelectorDialog.show();
+        });
+        // 保存
+        binding.save.setOnClickListener(v -> save());
+    }
+
+
+    @Override
+    public void onBackPressed() {
+    }
+
+    private void save() {
+        try {
+            Editable httpUriETText = binding.httpUriET.getText();
+            if (null == httpUriETText || TextUtils.isEmpty(httpUriETText)) {
+                binding.httpUriET.setError("请输入Http服务地址");
+                return;
+            }
+            Editable oldPasswordETText = binding.oldPasswordET.getText();
+            if (null == oldPasswordETText || TextUtils.isEmpty(oldPasswordETText)) {
+                binding.oldPasswordET.setError("请输入管理员密码");
+                return;
+            }
+            CharSequence bleTVText = binding.bleET.getText();
+            if (null == bleTVText || TextUtils.isEmpty(bleTVText)) {
+                binding.bleET.setError("请配置蓝牙称设备");
+                return;
+            }
+
+            deviceInfo.setBleMac(bleTVText.toString());
+            deviceInfo.setFrontCamera(binding.frontRB.isChecked());
+            deviceInfo.setAdminPwd(oldPasswordETText.toString());
+            deviceInfo.setBaseUrl(httpUriETText.toString());
+            WaitDialog.show("稍后重启App");
+            DeviceInfoTool.INSTANCE.insert(deviceInfo);
+            binding.backRB.postDelayed(() -> AppUtils.relaunchApp(true), 1500);
+        } catch (Exception e) {
+            ToastUtils.showLong("参数异常无法保存,请核对参数!");
+            LogUtils.e(Log.getStackTraceString(e));
+        }
+    }
+}

+ 168 - 0
app/src/main/java/xn/hxp/ui/start/StartActivity.java

@@ -0,0 +1,168 @@
+package xn.hxp.ui.start;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.NetworkUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.hjq.http.EasyHttp;
+import com.hjq.http.model.ResponseClass;
+import com.hjq.permissions.Permission;
+import com.hjq.permissions.XXPermissions;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import okhttp3.HttpUrl;
+import xn.hxp.databinding.ActivityStartBinding;
+import xn.hxp.http.HttpData;
+import xn.hxp.http.api.GetBaseConfigApi;
+import xn.hxp.http.api.GetReportApi;
+import xn.hxp.room.bean.BaseConfig;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.bean.LabInfo;
+import xn.hxp.room.tool.BaseConfigTool;
+import xn.hxp.room.tool.DeviceInfoTool;
+import xn.hxp.room.tool.LabInfoTool;
+import xn.hxp.ui.main.MainActivity;
+import xn.hxp.util.Tool;
+
+public class StartActivity extends AppCompatActivity {
+    private ActivityStartBinding binding;
+    private DeviceInfo deviceInfo;
+    private CountDownTimer requestPermissionCdTimer;
+    private LabInfo labInfo;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = ActivityStartBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+
+        deviceInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+        // ip
+        String ip = deviceInfo.getIp();
+        binding.ip.setText(TextUtils.isEmpty(ip) ? "" : ip);
+        // 版本
+        binding.version.setText(AppUtils.getAppVersionName());
+
+        requestPermissionCdTimer = new CountDownTimer(1000, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+
+            }
+
+            @Override
+            public void onFinish() {
+                requestPermission();
+            }
+        };
+        requestPermissionCdTimer.start();
+    }
+
+    private void requestPermission() {
+        if (!XXPermissions.isGrantedPermissions(this, Permission.READ_EXTERNAL_STORAGE)
+                || !XXPermissions.isGrantedPermissions(this, Permission.WRITE_EXTERNAL_STORAGE)
+                || !XXPermissions.isGrantedPermissions(this, Permission.READ_PHONE_STATE)
+                || !XXPermissions.isGrantedPermissions(this, Permission.RECORD_AUDIO)
+                || !XXPermissions.isGrantedPermissions(this, Permission.SYSTEM_ALERT_WINDOW)
+        ) {
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.READ_EXTERNAL_STORAGE");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.WRITE_EXTERNAL_STORAGE");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.READ_PHONE_STATE");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.RECORD_AUDIO");
+            Tool.INSTANCE.cmd("pm grant " + AppUtils.getAppPackageName() + " android.permission.SYSTEM_ALERT_WINDOW");
+            requestPermissionCdTimer.start();
+        } else {
+            requestPermissionCdTimer.cancel();
+            initView();
+        }
+    }
+
+    private void initView() {
+        binding.logo.setOnLongClickListener(v -> {
+            ActivityUtils.startActivity(SettingActivity.class);
+            finish();
+            return true;
+        });
+
+        ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
+            @Override
+            public Object doInBackground() throws Throwable {
+                // 检查网络
+                isAvailableByPing();
+                ThreadUtils.runOnUiThread(() -> binding.hint.setText("获取实验室信息"));
+                // 获取实验室信息
+                getLabInfo();
+                ThreadUtils.runOnUiThread(() -> binding.hint.setText("获取基础信息"));
+                // 获取基础信息
+                getLabConfig();
+                ThreadUtils.runOnUiThread(() -> binding.hint.setText("初始化已完成"));
+                return null;
+            }
+
+            @Override
+            public void onSuccess(Object result) {
+                ActivityUtils.startActivity(MainActivity.class);
+                finish();
+            }
+        });
+    }
+
+    @WorkerThread
+    private void getLabConfig() throws Throwable {
+        try {
+            HttpData<BaseConfig> data = EasyHttp.get(StartActivity.this).api(new GetBaseConfigApi(labInfo.getSubjectId())).execute(new ResponseClass<>() {
+            });
+
+            if (data.isRequestSuccess()) {
+                BaseConfig baseConfig = data.getData();
+                if (null != baseConfig) {
+                    BaseConfigTool.INSTANCE.insert(baseConfig);
+                    return;
+                }
+            }
+        } catch (Exception e) {
+            LogUtils.e(e.getMessage());
+        }
+        Thread.sleep(1000L * ThreadLocalRandom.current().nextInt(10, 20));
+        getLabConfig();
+    }
+
+    @WorkerThread
+    private void getLabInfo() throws Throwable {
+        try {
+            HttpData<GetReportApi.Bean> data = EasyHttp.get(StartActivity.this).api(new GetReportApi()).execute(new ResponseClass<>() {
+            });
+            if (data.isRequestSuccess()) {
+                GetReportApi.Bean bean = data.getData();
+                if (null != bean) {
+                    labInfo = bean.getLabInfo();
+                    if (null != labInfo) {
+                        LabInfoTool.INSTANCE.insert(labInfo);
+                        return;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            LogUtils.e(e.getMessage());
+        }
+        Thread.sleep(1000L * ThreadLocalRandom.current().nextInt(10, 20));
+        getLabInfo();
+    }
+
+    @WorkerThread
+    private void isAvailableByPing() throws InterruptedException {
+        if (!NetworkUtils.isAvailableByPing(HttpUrl.get(deviceInfo.getBaseUrl()).host())) {
+            Thread.sleep(1000L * ThreadLocalRandom.current().nextInt(10, 20));
+            isAvailableByPing();
+        }
+    }
+}

+ 93 - 0
app/src/main/java/xn/hxp/util/AudioPlayerTool.java

@@ -0,0 +1,93 @@
+package xn.hxp.util;
+
+import android.media.MediaPlayer;
+import android.util.Log;
+
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.Utils;
+
+/**
+ * 播放音频
+ */
+public class AudioPlayerTool {
+    private static AudioPlayerTool instance;
+    private MediaPlayer mediaPlayer;
+    private int currentAudioResId = 0;
+
+    private AudioPlayerTool() {
+        // 私有构造函数,防止外部创建实例
+    }
+
+    public static interface OnPlayListener {
+        void ended();
+    }
+
+    public static synchronized AudioPlayerTool getInstance() {
+        if (instance == null) {
+            instance = new AudioPlayerTool();
+        }
+        return instance;
+    }
+
+    public synchronized void play(int audioResId) {
+        if (mediaPlayer != null && currentAudioResId == audioResId) {
+            if (!mediaPlayer.isPlaying()) {
+                mediaPlayer.start();
+            }
+            return;
+        }
+        stop();
+        try {
+            mediaPlayer = MediaPlayer.create(Utils.getApp(), audioResId);
+            if (mediaPlayer != null) {
+                mediaPlayer.start();
+                currentAudioResId = audioResId;
+                mediaPlayer.setOnCompletionListener(mp -> stop());
+            } else {
+                LogUtils.e("Failed to create MediaPlayer for resource ID: " + audioResId);
+            }
+        } catch (Exception e) {
+            LogUtils.e("Error playing audio resource: " + audioResId, Log.getStackTraceString(e));
+        }
+    }
+
+    public synchronized void play(int audioResId, OnPlayListener onPlayListener) {
+        if (mediaPlayer != null && currentAudioResId == audioResId) {
+            if (!mediaPlayer.isPlaying()) {
+                mediaPlayer.start();
+            }
+            return;
+        }
+        stop();
+        try {
+            mediaPlayer = MediaPlayer.create(Utils.getApp(), audioResId);
+            if (mediaPlayer != null) {
+                mediaPlayer.start();
+                currentAudioResId = audioResId;
+                mediaPlayer.setOnCompletionListener(mp -> {
+                    stop();
+                    onPlayListener.ended();
+                });
+            } else {
+                LogUtils.e("Failed to create MediaPlayer for resource ID: " + audioResId);
+            }
+        } catch (Exception e) {
+            LogUtils.e("Error playing audio resource: " + audioResId, Log.getStackTraceString(e));
+        }
+    }
+
+    public void stop() {
+        if (mediaPlayer != null) {
+            if (mediaPlayer.isPlaying()) {
+                mediaPlayer.stop();
+            }
+            mediaPlayer.release();
+            mediaPlayer = null;
+            currentAudioResId = 0;
+        }
+    }
+
+    public boolean isPlaying() {
+        return mediaPlayer != null && mediaPlayer.isPlaying();
+    }
+}

+ 79 - 0
app/src/main/java/xn/hxp/util/CardReaderHelper.java

@@ -0,0 +1,79 @@
+package xn.hxp.util;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.KeyEvent;
+
+public class CardReaderHelper {
+    private static final String TAG = "CardReader";
+    private static final long TIMEOUT_THRESHOLD = 50; // 50ms无新事件视为刷卡结束
+
+    private final StringBuilder currentString = new StringBuilder();
+    private long lastEventTime = 0;
+    private final Handler handler;
+    private final OnCardReadListener listener;
+    private final Runnable timeoutTask = this::dispatchResult;
+
+    public interface OnCardReadListener {
+        void onCardRead(String cardData);
+    }
+
+    public CardReaderHelper(OnCardReadListener listener) {
+        this.listener = listener;
+        this.handler = new Handler(Looper.getMainLooper());
+    }
+
+    public void handleKeyEvent(KeyEvent event) {
+        // 1. 只处理按键按下事件
+        if (event.getAction() != KeyEvent.ACTION_DOWN) return;
+
+        // 2. 获取当前时间和字符
+        long eventTime = event.getEventTime();
+        char inputChar = convertKeyCodeToChar(event);
+        if (inputChar == 0) return; // 忽略无效字符
+
+        // 3. 判断是否新刷卡操作
+        if (eventTime - lastEventTime > 100) { // 100ms间隔视为新操作
+            currentString.setLength(0);
+        }
+
+        // 4. 追加字符并重置超时
+        currentString.append(inputChar);
+        lastEventTime = eventTime;
+        resetTimeout();
+    }
+
+    private char convertKeyCodeToChar(KeyEvent event) {
+        // 获取MetaState(处理Shift等修饰键)
+        int metaState = event.getMetaState();
+
+        // 转换为Unicode字符(支持数字/字母/符号)
+        int unicodeChar = event.getUnicodeChar(metaState);
+
+        // 过滤特殊功能键(如ENTER/BACK)
+        if (unicodeChar >= 32 && unicodeChar <= 126) { // 可打印ASCII范围
+            return (char) unicodeChar;
+        }
+        return 0;
+    }
+
+    private void resetTimeout() {
+        handler.removeCallbacks(timeoutTask);
+        handler.postDelayed(timeoutTask, TIMEOUT_THRESHOLD);
+    }
+
+    private void dispatchResult() {
+        if (currentString.length() > 0) {
+            Log.d(TAG, "Card read complete: " + currentString);
+            if (listener != null) {
+                listener.onCardRead(currentString.toString());
+            }
+            currentString.setLength(0);
+        }
+    }
+
+    public void release() {
+        handler.removeCallbacksAndMessages(null);
+    }
+}

+ 60 - 0
app/src/main/java/xn/hxp/util/Tool.java

@@ -0,0 +1,60 @@
+package xn.hxp.util;
+
+import com.blankj.utilcode.util.AppUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.Utils;
+import com.lztek.toolkit.Lztek;
+
+import java.util.Locale;
+
+public enum Tool {
+    INSTANCE;
+    private Lztek lztek;
+
+    Tool() {
+        lztek = Lztek.create(Utils.getApp());
+    }
+
+    public void cmd(String cmd) {
+        LogUtils.d(cmd);
+        lztek.suExec(cmd);
+    }
+
+    public void reboot() {
+        lztek.hardReboot();
+    }
+
+    public void reStartApp() {
+        exitApp();
+        cmd("monkey -p " + AppUtils.getAppPackageName() + " -c android.intent.category.LAUNCHER 1");
+    }
+
+    public void openFileBrowser() {
+        openApp("display.interactive.filebrowser");
+    }
+
+    public void exitApp() {
+        cmd("am force-stop " + AppUtils.getAppPackageName());
+    }
+
+    public void openSetting() {
+        openApp("com.android.settings");
+    }
+
+    public void openApp(String packageName) {
+        cmd("monkey -p " + packageName + " -c android.intent.category.LAUNCHER 1");
+    }
+
+    public void setBluetooth(boolean isEnable) {
+        cmd("settings put global bluetooth_on " + (isEnable ? "1" : "0"));
+    }
+
+    public String getSerialNumber() {
+        String mac = lztek.getEthMac().toUpperCase(Locale.getDefault());
+        return mac.replace(":", "");
+    }
+
+    public void showBar(boolean isShow) {
+        lztek.navigationBarSlideShow(isShow);
+    }
+}

+ 10 - 0
app/src/main/java/xn/hxp/view/DialogTool.java

@@ -0,0 +1,10 @@
+package xn.hxp.view;
+
+public enum DialogTool {
+    INSTANCE;
+
+    public void showLoginSuccess(int delayed) {
+
+    }
+
+}

+ 68 - 0
app/src/main/java/xn/hxp/view/ble/BleScanAdapter.java

@@ -0,0 +1,68 @@
+package xn.hxp.view.ble;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.List;
+
+import xn.hxp.databinding.ItemBleBinding;
+
+public class BleScanAdapter extends BaseAdapter {
+    private Context context;
+    private List<BluetoothDevice> deviceList;
+
+    public BleScanAdapter(Context context, List<BluetoothDevice> deviceList) {
+        this.context = context;
+        this.deviceList = deviceList;
+    }
+
+    @Override
+    public int getCount() {
+        return null == deviceList ? 0 : deviceList.size();
+    }
+
+    @Override
+    public BluetoothDevice getItem(int position) {
+        return deviceList.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ViewHolder viewHolder;
+        if (convertView == null) {
+            ItemBleBinding binding = ItemBleBinding.inflate(LayoutInflater.from(context), parent, false);
+            convertView = binding.getRoot();
+            viewHolder = new ViewHolder(binding);
+            convertView.setTag(viewHolder);
+        } else {
+            viewHolder = (ViewHolder) convertView.getTag();
+        }
+        BluetoothDevice bleDevice = getItem(position);
+        if (null != bleDevice) {
+            String mac = bleDevice.getAddress();
+            @SuppressLint("MissingPermission") String name = bleDevice.getName();
+            viewHolder.binding.bleMac.setText(TextUtils.isEmpty(mac) ? "" : mac);
+            viewHolder.binding.bleName.setText(TextUtils.isEmpty(name) ? "" : name);
+        }
+        return convertView;
+    }
+
+    static class ViewHolder {
+        ItemBleBinding binding;
+
+        public ViewHolder(ItemBleBinding binding) {
+            this.binding = binding;
+        }
+    }
+}

+ 95 - 0
app/src/main/java/xn/hxp/view/ble/BleSelectorDialog.java

@@ -0,0 +1,95 @@
+package xn.hxp.view.ble;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.AdapterView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatDialog;
+
+import com.feasycom.common.bean.FscDevice;
+import com.feasycom.spp.controler.FscSppCentralCallbacks;
+import com.kongzue.dialogx.dialogs.PopTip;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xn.hxp.databinding.DialogBleSelectorBinding;
+import xn.hxp.room.bean.DeviceInfo;
+import xn.hxp.room.tool.DeviceInfoTool;
+
+public class BleSelectorDialog extends AppCompatDialog {
+    private Context context;
+    private DialogBleSelectorBinding binding;
+    private List<BluetoothDevice> deviceList = new ArrayList<>();
+    private BleScanAdapter bleScanAdapter;
+    private DeviceInfo deviceInfo;
+
+    public BleSelectorDialog(@NonNull Context context) {
+        super(context);
+        this.context = context;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = DialogBleSelectorBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+        setCancelable(false);
+        setCanceledOnTouchOutside(false);
+        deviceInfo = DeviceInfoTool.INSTANCE.getDeviceInfo();
+        bleScanAdapter = new BleScanAdapter(context, deviceList);
+        binding.bleLV.setAdapter(bleScanAdapter);
+        binding.bleLV.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @SuppressLint("MissingPermission")
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                deviceInfo.setBleMac(deviceList.get(position).getAddress());
+                deviceInfo.setBleName(deviceList.get(position).getName());
+                DeviceInfoTool.INSTANCE.insert(deviceInfo);
+                dismiss();
+            }
+        });
+        binding.ok.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                binding.ok.setVisibility(View.GONE);
+                binding.cancel.setVisibility(View.VISIBLE);
+                binding.bleLV.setVisibility(View.VISIBLE);
+                binding.loadingLAV.setVisibility(View.VISIBLE);
+                binding.loadingLAV.playAnimation();
+                SppTool.INSTANCE.sacn(new FscSppCentralCallbacks() {
+                    @Override
+                    public void sppPeripheralFound(FscDevice fscDevice, int i) {
+                        if (!TextUtils.isEmpty(fscDevice.getName())) {
+                            deviceList.add(fscDevice.getDevice());
+                        }
+                        if (!deviceList.isEmpty()) {
+                            bleScanAdapter.notifyDataSetChanged();
+                            binding.loadingLAV.cancelAnimation();
+                            binding.loadingLAV.setVisibility(View.GONE);
+                        } else {
+                            PopTip.show("未找到蓝牙称!");
+                        }
+                    }
+                });
+            }
+        });
+        binding.cancel.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                dismiss();
+            }
+        });
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        binding.loadingLAV.cancelAnimation();
+    }
+}

+ 225 - 0
app/src/main/java/xn/hxp/view/ble/BluetoothWeighDialog.java

@@ -0,0 +1,225 @@
+//package xn.hxp.view.ble;
+//
+//import android.bluetooth.BluetoothDevice;
+//import android.graphics.Color;
+//import android.graphics.drawable.ColorDrawable;
+//import android.os.Bundle;
+//import android.text.Editable;
+//import android.text.TextUtils;
+//import android.view.Gravity;
+//import android.view.View;
+//import android.view.Window;
+//import android.view.WindowManager;
+//import android.widget.Toast;
+//
+//import androidx.appcompat.app.AppCompatDialog;
+//
+//import com.blankj.utilcode.util.ActivityUtils;
+//import com.blankj.utilcode.util.SPUtils;
+//import com.blankj.utilcode.util.ThreadUtils;
+//import com.bumptech.glide.Glide;
+//import com.bumptech.glide.load.engine.DiskCacheStrategy;
+//import com.bumptech.glide.request.RequestOptions;
+//import com.feasycom.common.bean.ConnectType;
+//import com.kongzue.dialogx.dialogs.PopTip;
+//import com.kongzue.dialogx.dialogs.WaitDialog;
+//
+//import org.json.JSONObject;
+//
+//import okhttp3.Response;
+//import xn.hxp.R;
+//
+//public class BluetoothWeighDialog extends AppCompatDialog {
+//    private DialogBluetoothWeighBinding binding;
+//    private final AddActivity addActivity;
+//    private final HxpChemicalVo hxpChemicalVo;
+//    private DialogCallBack dialogCallBack;
+//
+//    public BluetoothWeighDialog(AddActivity addActivity, HxpChemicalVo hxpChemicalVo, DialogCallBack dialogCallBack) {
+//        super(addActivity);
+//        this.addActivity = addActivity;
+//        this.hxpChemicalVo = hxpChemicalVo;
+//        this.dialogCallBack = dialogCallBack;
+//    }
+//
+//    @Override
+//    protected void onCreate(Bundle savedInstanceState) {
+//        super.onCreate(savedInstanceState);
+//        requestWindowFeature(Window.FEATURE_NO_TITLE);
+//        binding = DialogBluetoothWeighBinding.inflate(getLayoutInflater());
+//        setContentView(binding.getRoot());
+//        Window window = getWindow();
+//        if (null != window) {
+//            window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+//            window.setGravity(Gravity.CENTER);
+//            window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
+//        }
+//
+//        // 提示图片加载
+//        if (null != ChemicalApp.confs && ChemicalApp.confs.getWeighHintPicture() != null && !ChemicalApp.confs.getWeighHintPicture().isEmpty()) {
+//            Glide.with(addActivity)
+//                    .load(HttpConfig.Companion.getAPI_BASE_IMG_URL() + ChemicalApp.confs.getWeighHintPicture())
+//                    .placeholder(R.mipmap.img_syt_cz)
+//                    .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.AUTOMATIC))
+//                    .into(binding.hintIV);
+//        }
+//        // 净含量
+//        binding.netWtET.setText(String.valueOf(hxpChemicalVo.getNetContent()));
+//        // 规格
+//        binding.specsTV.setText(hxpChemicalVo.getSpecNum() + hxpChemicalVo.getSpecUnit());
+//        // 蓝牙称
+//        String sppMac = SPUtils.getInstance().getString("sppMac", "");
+//        if (TextUtils.isEmpty(sppMac)) {
+//            Toast.makeText(ActivityUtils.getTopActivity(), "未配置蓝牙称!", Toast.LENGTH_LONG).show();
+//        } else {
+//            SppTool.INSTANCE.connect(sppMac, new SppTool.BleSppCallback() {
+//                String weight = "";
+//
+//                @Override
+//                public void sppPeripheralConnected(BluetoothDevice bluetoothDevice, ConnectType connectType) {
+//
+//                }
+//
+//                @Override
+//                public void packetReceived(String address, String strValue) {
+//                    if (!weight.equals(strValue)) {
+//                        ThreadUtils.runOnUiThread(new Runnable() {
+//                            @Override
+//                            public void run() {
+//                                try {
+//                                    double w = Double.parseDouble(strValue);
+//                                    hxpChemicalVo.setWeigh(w);
+//                                    binding.weighTV.setText(strValue + "g");
+//                                    weight = strValue;
+//                                } catch (Exception e) {
+//                                    Toast.makeText(ActivityUtils.getTopActivity(), "蓝牙称值异常! " + weight, Toast.LENGTH_SHORT).show();
+//                                }
+//                                if (binding.weightRL.getVisibility() != View.VISIBLE) {
+//                                    binding.weightRL.setVisibility(View.VISIBLE);
+//                                }
+//                                if (binding.loadingLAV.getVisibility() == View.VISIBLE) {
+//                                    binding.loadingLAV.cancelAnimation();
+//                                    binding.loadingLAV.setVisibility(View.GONE);
+//                                }
+//                            }
+//                        });
+//                    }
+//                }
+//
+//                @Override
+//                public void sppPeripheralDisconnected(String address) {
+//
+//                }
+//            });
+//        }
+////        BleTool.INSTANCE.setBleCallback(new BleTool.BleCallback() {
+////            @Override
+////            public void onSuccess() {
+////
+////            }
+////
+////            @Override
+////            public void onNotifyFailure(Exception exception) {
+////                dialogCallBack.cancel(BluetoothWeighDialog.this, exception.getMessage());
+////            }
+////
+////            @Override
+////            public void onChanged(String weight) {
+////                try {
+////                    double w = Double.parseDouble(weight);
+////                    hxpChemicalVo.setWeigh(w);
+////                    binding.weighTV.setText(weight + "g");
+////                } catch (Exception e) {
+////                    ThreadUtils.runOnUiThread(() -> Toast.makeText(ActivityUtils.getTopActivity(), "蓝牙称值异常! " + weight, Toast.LENGTH_SHORT).show());
+////                }
+////                if (binding.weightRL.getVisibility() != View.VISIBLE) {
+////                    binding.weightRL.setVisibility(View.VISIBLE);
+////                }
+////                if (binding.loadingLAV.getVisibility() == View.VISIBLE) {
+////                    binding.loadingLAV.cancelAnimation();
+////                    binding.loadingLAV.setVisibility(View.GONE);
+////                }
+////            }
+////        });
+//        // 确认
+//        binding.confirmBT.setOnClickListener(v -> {
+//            Editable netWtETText = binding.netWtET.getText();
+//            if (null == netWtETText || TextUtils.isEmpty(netWtETText)) {
+//                binding.netWtET.setError("请输入净含量");
+//                return;
+//            }
+//            double netWt = -1.0;
+//            try {
+//                netWt = Double.parseDouble(netWtETText.toString());
+//            } catch (Exception e) {
+//                binding.netWtET.setError("请检查净含量内容正确");
+//                return;
+//            }
+//            if (netWt <= 0) {
+//                binding.netWtET.setError("请检查净含量内容正确");
+//                return;
+//            }
+//            // 净含量不可大于规格
+//            if (netWt > hxpChemicalVo.getSpecNum()) {
+//                binding.netWtET.setError("净含量不能大于规格");
+//                return;
+//            }
+//            // 净含量不可大于称重
+//            if (netWt > hxpChemicalVo.getWeigh()) {
+//                binding.netWtET.setError("净含量不能大于称重");
+//                return;
+//            }
+//            hxpChemicalVo.setNetContent(netWt);
+//            WaitDialog.show("校验中...");
+//            ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<String>() {
+//                @Override
+//                public String doInBackground() throws Throwable {
+//                    Response response = HttpTool.addStockCheck(hxpChemicalVo);
+//                    if (response.isSuccessful()) {
+//                        String json = response.body().string();
+//                        JSONObject jsonObject = new JSONObject(json);
+//                        int code = jsonObject.getInt("code");
+//                        if (code == 200) {
+//                            return "ok";
+//                        } else {
+//                            return jsonObject.getString("message");
+//                        }
+//                    } else {
+//                        return response.message();
+//                    }
+//                }
+//
+//                @Override
+//                public void onSuccess(String result) {
+//                    WaitDialog.dismiss();
+//                    if ("ok".equals(result)) {
+//                        hxpChemicalVo.setJoinType(1);
+//                        dialogCallBack.confirm(BluetoothWeighDialog.this);
+//                    } else if (null == result || result.isEmpty()) {
+//                        dialogCallBack.cancel(BluetoothWeighDialog.this, "称重失败,系统异常!");
+//                    } else {
+//                        PopTip.show(result);
+//                    }
+//                }
+//            });
+//        });
+//        setCancelable(false);
+//        setCanceledOnTouchOutside(false);
+//    }
+//
+//    @Override
+//    protected void onStop() {
+//        super.onStop();
+//        SppTool.INSTANCE.disconnect();
+//    }
+//
+//
+//    public interface DialogCallBack {
+//        // 成功
+//        void confirm(BluetoothWeighDialog bluetoothWeighDialog);
+//
+//        // 取消
+//        void cancel(BluetoothWeighDialog bluetoothWeighDialog, String msg);
+//    }
+//
+//}

+ 91 - 0
app/src/main/java/xn/hxp/view/ble/SppTool.java

@@ -0,0 +1,91 @@
+package xn.hxp.view.ble;
+
+import android.bluetooth.BluetoothDevice;
+
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.Utils;
+import com.feasycom.common.bean.ConnectType;
+import com.feasycom.spp.controler.FscSppCentralApi;
+import com.feasycom.spp.controler.FscSppCentralApiImp;
+import com.feasycom.spp.controler.FscSppCentralCallbacks;
+
+/**
+ * https://document.feasycom.com/docs/app/sdk/FeasycomSDK/CN/latest/FeasyBlue_Android.html
+ * 蓝牙操作SDK 适用于乐琪称
+ */
+public enum SppTool {
+    INSTANCE;
+
+    private FscSppCentralApi sppApi;
+
+    SppTool() {
+        sppApi = FscSppCentralApiImp.getInstance(Utils.getApp());
+        sppApi.isShowLog(true);
+        sppApi.initialize();
+    }
+
+    public interface BleSppCallback {
+        void sppPeripheralConnected(BluetoothDevice bluetoothDevice, ConnectType connectType);
+
+        void packetReceived(String address, String strValue);
+
+        void sppPeripheralDisconnected(String address);
+    }
+
+    public void sacn(FscSppCentralCallbacks fscSppCentralCallbacks) {
+        sppApi.setCallbacks(fscSppCentralCallbacks);
+        sppApi.startScan();
+    }
+
+    public void connect(String mac, BleSppCallback bleSppCallback) {
+        if (null != mac) {
+            sppApi.setCallbacks(new FscSppCentralCallbacks() {
+                @Override
+                public void sppPeripheralConnected(BluetoothDevice bluetoothDevice, ConnectType connectType) {
+                    LogUtils.d(bluetoothDevice, connectType);
+                    bleSppCallback.sppPeripheralConnected(bluetoothDevice, connectType);
+                }
+
+                /**
+                 * 收到数据
+                 * @param address 设备地址
+                 * @param strValue 字符串
+                 * @param dataHexString 十六进制
+                 * @param data 源数据
+                 */
+                @Override
+                public void packetReceived(String address, String strValue, String dataHexString, byte[] data) {
+                    LogUtils.d("蓝牙数据", strValue);
+                    strValue = strValue.replace(" ", "");
+                    strValue = strValue.replace("\r", "");
+                    strValue = strValue.replace("\n", "");
+                    strValue = strValue.trim();
+                    if (strValue.startsWith("=")) {
+                        try {
+                            String wD = new StringBuilder(strValue.replace("=", "")).reverse().toString();
+                            bleSppCallback.packetReceived(address, wD);
+                        } catch (Exception e) {
+                            LogUtils.e(e.getMessage());
+                        }
+                    } else {
+                        bleSppCallback.packetReceived(address, strValue);
+                    }
+                }
+
+                /**
+                 * 断开连接
+                 */
+                @Override
+                public void sppPeripheralDisconnected(String address) {
+                    LogUtils.d("sppPeripheralDisconnected", address);
+                    bleSppCallback.sppPeripheralDisconnected(address);
+                }
+            });
+            sppApi.connect(mac);
+        }
+    }
+
+    public void disconnect() {
+        sppApi.disconnect();
+    }
+}

+ 101 - 0
app/src/main/java/xn/hxp/view/dialog/LoginVerifySuccessDialog.java

@@ -0,0 +1,101 @@
+package xn.hxp.view.dialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatDialog;
+
+import com.blankj.utilcode.util.LogUtils;
+import com.bumptech.glide.Glide;
+
+import java.io.File;
+
+import xn.hxp.R;
+import xn.hxp.databinding.DialogLoginVerifySuccessBinding;
+import xn.hxp.http.HttpTool;
+import xn.hxp.room.bean.BaseConfig;
+import xn.hxp.room.bean.UserInfo;
+import xn.hxp.room.tool.BaseConfigTool;
+import xn.hxp.room.tool.UserInfoTool;
+import xn.hxp.util.AudioPlayerTool;
+
+public class LoginVerifySuccessDialog extends AppCompatDialog {
+    private DialogLoginVerifySuccessBinding binding;
+    private File faceFile;
+    private CountDownTimer delayedDismissCDT;
+    private int delayed = 3000;
+
+    public LoginVerifySuccessDialog(@NonNull Context context, int delayed) {
+        super(context, R.style.FullScreenDialogTheme);
+        this.delayed = delayed * 1000;
+        init();
+    }
+
+    public LoginVerifySuccessDialog(@NonNull Context context, int delayed, File faceFile) {
+        super(context, R.style.FullScreenDialogTheme);
+        this.faceFile = faceFile;
+        this.delayed = delayed * 1000;
+        init();
+    }
+
+    private void init() {
+        AudioPlayerTool.getInstance().play(R.raw.login_verify_success);
+        delayedDismissCDT = new CountDownTimer(delayed, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+                int seconds = (int) Math.ceil(millisUntilFinished / 1000.0);
+                binding.hint.setText(seconds + "秒后自动返回首页");
+            }
+
+            @Override
+            public void onFinish() {
+                if (isShowing()) {
+                    dismiss();
+                }
+            }
+        };
+        delayedDismissCDT.start();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = DialogLoginVerifySuccessBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+        setCancelable(false);
+        setCanceledOnTouchOutside(false);
+        UserInfo userInfo = UserInfoTool.INSTANCE.getUserInfo();
+        BaseConfig baseConfig = BaseConfigTool.INSTANCE.getBaseConfig();
+        try {
+            // 加载头像
+            if (null == faceFile) {
+                Glide.with(binding.userFace).load(HttpTool.INSTANCE.checkUrl(userInfo.getAvatar())).into(binding.userFace);
+            }
+            // 加载人脸
+            else {
+                LogUtils.d(faceFile);
+                Glide.with(binding.userFace).load(faceFile).into(binding.userFace);
+            }
+
+            // 登录人名字
+            binding.userName.setText(TextUtils.isEmpty(userInfo.getUserName()) ? "" : userInfo.getUserName());
+            // 学校
+            binding.schoolName.setText(TextUtils.isEmpty(baseConfig.getDeptName()) ? "" : baseConfig.getDeptName());
+            // 地址
+            String buildName = TextUtils.isEmpty(baseConfig.getBuildName()) ? "" : baseConfig.getBuildName();
+            String floorName = TextUtils.isEmpty(baseConfig.getFloorName()) ? "" : baseConfig.getFloorName();
+            binding.address.setText(buildName + floorName);
+        } catch (Exception e) {
+            LogUtils.e(e.getMessage());
+        }
+    }
+
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        delayedDismissCDT.cancel();
+    }
+}

BIN=BIN
app/src/main/res/drawable-hdpi/app_logo.png


BIN=BIN
app/src/main/res/drawable-hdpi/bg.webp


BIN=BIN
app/src/main/res/drawable-hdpi/ble_mac_hint.png


BIN=BIN
app/src/main/res/drawable-hdpi/dialog_login_verify_success_address.webp


BIN=BIN
app/src/main/res/drawable-hdpi/dialog_login_verify_success_lab.webp


BIN=BIN
app/src/main/res/drawable-hdpi/dialog_login_verify_success_pic.webp


BIN=BIN
app/src/main/res/drawable-hdpi/dialog_login_verify_success_school.webp


BIN=BIN
app/src/main/res/drawable-hdpi/dialog_msg.png


BIN=BIN
app/src/main/res/drawable-hdpi/login_card_pic.webp


BIN=BIN
app/src/main/res/drawable-hdpi/login_face_pic.webp


BIN=BIN
app/src/main/res/drawable-hdpi/login_hint_pic.webp


BIN=BIN
app/src/main/res/drawable-hdpi/main_cabinet.png


BIN=BIN
app/src/main/res/drawable-hdpi/main_cabinet_next.webp


BIN=BIN
app/src/main/res/drawable-hdpi/main_claim_card_bg.webp


BIN=BIN
app/src/main/res/drawable-hdpi/main_discard_card_bg.webp


BIN=BIN
app/src/main/res/drawable-hdpi/main_inquire_card_bg.webp


BIN=BIN
app/src/main/res/drawable-hdpi/main_label_card_bg.webp


BIN=BIN
app/src/main/res/drawable-hdpi/main_return_card_bg.webp


BIN=BIN
app/src/main/res/drawable-hdpi/main_save_card_bg.webp


+ 0 - 0
app/src/main/res/drawable-hdpi/main_save_logo.webp


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio